• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

Poor vJass' value types

Status
Not open for further replies.
Level 13
Joined
Nov 7, 2014
Messages
571
Poor vJass' value types

As you might know, vJass implements its user defined data types (called structs) as integer indices into parallel arrays, i.e the storage for each struct field is its own global array.
So if we have this example struct:
JASS:
struct Point
    real x
    real y
endstruct

...

local Point p1 = Point.create()
set p1.x = 1.0
set p1.y = 2.0

JassHelper (vJass's compiler/preprocessor) would generate the following code for it (more or less):
JASS:
globals
    real array s__Point_x // struct Point's fields,
    real array s__Point_y // with a separate array for each field
endglobals

...

local integer p1= s__Point__allocate() // p1 is an integer
set s__Point_x[p1]=1.0 // used as an index into the
set s__Point_y[p1]=2.0 // parallel arrays

If we then make another Point and we assign it to p1, and change some of its fields, we are going to modify p1's fields:
JASS:
local Point p2

set p2 = p1
set p2.x = 3.0

call BJDebugMsg(R2S(p2.x)) // 3.0
call BJDebugMsg(R2S(p1.x)) // 3.0

// =>

local integer p2

set p2=p1
set s__Point_x[p2]=3.0

call BJDebugMsg(R2S(s__Point_x[p2])) // 3.0
call BJDebugMsg(R2S(s__Point_x[p1])) // 3.0

In a sense, vJass's struct instances act as references to objects, i.e the assignment of 1 Point to another doesn't do a deep copy, only a shallow copy (an integer copy, really).
We could of course write our own copy Point function/method that creates a new Point from an existing one but now we face the problem that Jass' arrays only grow up to 8192, so we can only have 8192 Points at time (8191 really), which is plenty, but unlike some other programming languages, there is no automatic memory management in [v]Jass, i.e when we are done using our Point(s) we have to free them manually (vJass provides both a create and a destroy method), otherwise we could easily reach that 8191 array size limit (creating points in a loop, for example).

Wouldn't it be nice if vJass had struct-like thingies (user defined data types) that didn't need to be manually destroyed and the assignment of one such thingy to another did a deep copy (aka value types)? Well... yes, but no, there ain't such thingies in vJass unfortunately.

The only way, that I know, of emulating the semantics of value types in vJass is to pack their data into a single integer (which itself has value semantics). The problem with that is that there are only 32 bits in an integer (integers in Jass are signed 32 bit integers) , which is very limiting of course.
We can't repesent the above Point struct with only 32 bits (a float is 32 bits, and the Point struct has 2 of them).

The funny thing is that we can store 2 vJass Point references/instances in a single value. Because references are integers that go up to 8192, we need ceil(log(8192, 2)) = 13 bits to store them, and 13 can fit 2 times in 32.

We are going to place the bits of the 2 references like so:
JASS:
S 000 ZAAAAAAAAAAAAA ZBBBBBBBBBBBBB

S - the sign bit of the underlying integer which we always want to keep unset (equal to 0)
000 - unused bits
Z - an always 0 bit that helps with keeping the S bit unset
AAAAAAAAAAAAA - 1st reference's 13 bits
BBBBBBBBBBBBB - 2nd reference's 13 bits

The way bit packing is done (as far as I know) in non-scripting languages is with some bitwise operators/functions:
JASS:
// in C-like languages usually there are dedicated operators for doing bit manipulations:
<< -- left shift
>> -- right shift (arithmetic, >>> for logical right shift in languages without unsigned integers?)
& -- bitwise and
| -- bitwise or

We can emulate (<<) with multiplication (*), (>>) with division (/) and (|) with addition (+).
JASS:
x <<  0 | x *        0x1 | x >>  0 | x /        0x1
x <<  1 | x *        0x2 | x >>  1 | x /        0x2
x <<  2 | x *        0x4 | x >>  2 | x /        0x4
x <<  3 | x *        0x8 | x >>  3 | x /        0x8
x <<  4 | x *       0x10 | x >>  4 | x /       0x10
x <<  5 | x *       0x20 | x >>  5 | x /       0x20
x <<  6 | x *       0x40 | x >>  6 | x /       0x40
x <<  7 | x *       0x80 | x >>  7 | x /       0x80
x <<  8 | x *      0x100 | x >>  8 | x /      0x100
x <<  9 | x *      0x200 | x >>  9 | x /      0x200
x << 10 | x *      0x400 | x >> 10 | x /      0x400
x << 11 | x *      0x800 | x >> 11 | x /      0x800
x << 12 | x *     0x1000 | x >> 12 | x /     0x1000
x << 13 | x *     0x2000 | x >> 13 | x /     0x2000
x << 14 | x *     0x4000 | x >> 14 | x /     0x4000
x << 15 | x *     0x8000 | x >> 15 | x /     0x8000
x << 16 | x *    0x10000 | x >> 16 | x /    0x10000
x << 17 | x *    0x20000 | x >> 17 | x /    0x20000
x << 18 | x *    0x40000 | x >> 18 | x /    0x40000
x << 19 | x *    0x80000 | x >> 19 | x /    0x80000
x << 20 | x *   0x100000 | x >> 20 | x /   0x100000
x << 21 | x *   0x200000 | x >> 21 | x /   0x200000
x << 22 | x *   0x400000 | x >> 22 | x /   0x400000
x << 23 | x *   0x800000 | x >> 23 | x /   0x800000
x << 24 | x *  0x1000000 | x >> 24 | x /  0x1000000
x << 25 | x *  0x2000000 | x >> 25 | x /  0x2000000
x << 26 | x *  0x4000000 | x >> 26 | x /  0x4000000
x << 27 | x *  0x8000000 | x >> 27 | x /  0x8000000
x << 28 | x * 0x10000000 | x >> 28 | x / 0x10000000
x << 29 | x * 0x20000000 | x >> 29 | x / 0x20000000
x << 30 | x * 0x40000000 | x >> 30 | x / 0x40000000
x << 31 | x * 0x80000000 | x >> 31 | x / 0x80000000

   0b1111_0000    0b1111_0000 |    0b0110_1010    0b0110_1010
or 0b0000_1111  + 0b0000_1111 | or 0b0101_0111  + 0b0101_0111
   0b1111_1111    0b1111_1111 |    0b0111_1111    0b1100_0001

We want the division (/) to act as logical/unsigned integer right shift, but because the integers are signed, it acts as arithmetic right shift, we could work around that but it would make things slower, so we always keep the sign bit unset instead.
Note that (+) emulates (|) only if the bits of its arguments are non-overlapping.

In vJass this might look like:
JASS:
// we declare the pp type that we are going to use in order to implement our value struct
// for our purposes the 'extends array' syntax in vJass means declaring a value type (its kind of goofy, I know)
struct pp extends array
    static method make takes Point a, Point b returns pp
        // local pp result = integer(a) << 14 | integer(b) << 0
        // local pp result = integer(a)*0x4000 + integer(b)*0x1
        // local pp result = a*0x4000 + b*0x1 // integer casting is optional, the << 0 (* 0x1) does nothing
        // return result
        return a*0x4000 + b
    endmethod
endstruct

// shorthand alias for pp.make
function Pp takes Point a, Point b returns pp
    return pp.make(a, b)
endfunction

With this we can create pp(s), store them in variables/arrays, pass them to functions, etc. But we would also like to access the actual Point fields that we've packed. We could do it like so:
JASS:
method operator a takes nothing returns Point
    // 'this' in methods is the instance the method was called on
    // return Point(this >> 14)
    return Point(this / 0x4000)
endmethod

method operator b takes nothing returns Point
    // return Point((this << 18) >> 18)
    return Point((this * 0x40000) / 0x40000)
endmethod

...

local Point p1 = Point.create()
local Point p2 = Point.create()
local pp p = Pp(p1, p2)

set p1.x = 1.0
call BJDebugMsg(R2S( p.a.x )) // 1.0

The custom operator/property feature of vJass makes it look like an actual field access, we didn't have to call a method, like p.get_a(), for example.

The way these work is:
JASS:
remember that our value struct looked like (S = 0, Z = 0):
S 000 ZAAAAAAAAAAAAA ZBBBBBBBBBBBBB

// for operator a
after '>> 14'
0 000 00000000000000 0AAAAAAAAAAAAA

// for operator b
after '<< 18'
0 BBB BBBBBBBBBB 000000000000000000
after '>> 18'
0 000 00000000000000 0BBBBBBBBBBBBB

We also need to be able to modify these fields:
JASS:
method operator a= takes integer x returns nothing
    // set this = (x << 14) | ((this << 18) >> 18)
    set this = (x * 0x4000) + ((this * 0x40000) / 0x40000)
endmethod

method operator b= takes integer x returns nothing
    // set this = ((this >> 14) << 14) | (x << 0)
    set this = ((this / 0x4000) * 0x4000) + x
endmethod

...

local Point p1 = Point.create()
local pp p = Pp(0, 0)

set p1.x = 3.0
set p.a = p1
call BJDebugMsg(R2S( p.a.x )) // 0.0 huh?

Can you see the slight problem? The 'this' variable in the methods is not an alias for to the 'p' variable, these are two separate variables holding the same value, and we can't pass a reference/pointer to the p variable to be modified by a function in Jass. We can however return a new value and reassign p to it:
JASS:
method set_a takes integer x returns pp
    return (x * 0x4000) + ((this * 0x40000) / 0x40000)
endmethod

method set_b takes integer x returns pp
    return ((this / 0x4000) * 0x4000) + x
endmethod

...

local Point p1 = Point.create()
local pp p = Pp(0, 0)

set p1.x = 3.0
set p = p.set_a(p1)
call BJDebugMsg(R2S( p.a.x )) // 3.0

We have to use an actual method for the setter of the fields though, we prefix them with 'set_', not to confuse vJass.


Because of the limited storage of these value types (32 bits) we can't pack in them arbitrary data, we can pack a set of data that's known beforehand though.

Lets implement the value type 'sru' that can store the following sets of values:

JASS:
globals
    constant string S0 = "A"
    constant string S1 = "B"
    constant string S2 = "C"
    constant string S3 = "D"
    string array S_a

    constant real R0 = 0.0
    constant real R1 = 6.28
    constant real R2 = 2.718281
    constant real R3 = 3.0
    real array R_a

    constant integer U0 = 'hfoo'
    constant integer U1 = 'hpea'
    constant integer U2 = 'ogru'
    constant integer U3 = 'ugho'
    integer array U_a
endglobals

function lookup_tables_init takes nothing returns nothing
    set S_a[0] = S0
    set S_a[1] = S1
    set S_a[2] = S2
    set S_a[3] = S3

    set R_a[0] = R0
    set R_a[1] = R1
    set R_a[2] = R2
    set R_a[3] = R3

    set U_a[0] = U0
    set U_a[1] = U1
    set U_a[2] = U2
    set U_a[3] = U3
endfunction

struct sru extends array
    // string s : 4
    // real r : 4
    // integer u : 4
    // S0000000000000000 Zssss Zrrrr Zuuuu

    static method id_for_s takes string s returns integer
        local integer i = 0
        loop
            if s == S_a[i] then
                return i
            endif
            set i = i + 1
        endloop
        return 0
    endmethod
    static method id_for_r takes real r returns integer
        local integer i = 0
        loop
            if r == R_a[i] then
                return i
            endif
            set i = i + 1
        endloop
        return 0
    endmethod
    static method id_for_u takes integer u returns integer
        local integer i = 0
        loop
            if u == U_a[i] then
                return i
            endif
            set i = i + 1
        endloop
        return 0
    endmethod

    static method make takes string s, real r, integer u returns sru
        // return id_for_s(s) << 10 | id_for_r(r) << 5 | id_for_u(u) << 0
        return id_for_s(s) * 0x400 + id_for_r(r) * 0x20 + id_for_u(u)
    endmethod

    method operator s takes nothing returns string
        // return S_a[this >> 10]
        return S_a[this / 0x400]
    endmethod

    method operator r takes nothing returns real
        // return R_a[(this << 22) >> 27]
        return R_a[(this * 0x400000) / 0x8000000]
    endmethod

    method operator u takes nothing returns integer
        // return U_a[(this << 27) >> 27]
        return U_a[(this * 0x8000000) / 0x8000000]
    endmethod

    method set_s takes string s returns sru
        // return (id_for_s(s) << 10) | ((this << 22) >> 22)
        return (id_for_s(s) * 0x400) + ((this * 0x400000) / 0x400000)
    endmethod

    method set_r takes real r returns sru
        // return ((this >> 10) << 10) | (id_for_r(r) << 5) | ((this << 27) >> 27)
        return ((this * 0x400) / 0x400) + (id_for_r(r) * 0x20) + ((this * 0x8000000) / 0x8000000)
    endmethod

    method set_u takes integer u returns sru
        // return ((this >> 5) << 5) | (id_for_u(u) << 0)
        return ((this / 0x20) * 0x20) + id_for_u(u)
    endmethod
endstruct

function Sru takes string s, real r, integer u returns sru
    return sru.make(s, r, u)
endfunction

...

local sru p

call lookup_tables_init()

set p = Sru(S0, R1, U2)

call BJDebugMsg(p.s) // "A"
call BJDebugMsg(R2S(p.r)) // 6.280
call BJDebugMsg(I2S(p.u)) // 1869050485
call BJDebugMsg(I2S('ogru')) // 1869050485

set p = p.set_s(S1).set_r(R2)
call BJDebugMsg(p.s) // "B"
call BJDebugMsg(R2S(p.r)) // 3.000

We simply use lookup tables to map the set values to the integers from 0 to set.size - 1 and back.
Note that because the 'r' field's bits are in the middle, we have to preserve the bits on their left and right, in the 'set_r' method, which is slightly more involved, but not really.


The 'pp' and 'sru' value types are pretty useless but they hopefully got the point across.

Because of the storage limitations we can't really do much we these thingies, I suppose one could have an (error-code, reference) value returned from a function, or (reference-tag, reference) values floating around but not a vector3 unfortunately... maybe a vector2 (real x : 15, real y : 15) although 15 bit reals would probably be way too imprecise, but that's "Poor vJass' value types" for you.
 
Level 29
Joined
Jul 29, 2007
Messages
5,174
The typing system in V8 (Google's JavaScript engine) might interest you.
Last I read a bit about it, it tries to pack stuff like this too, but rather than having the unused bits, they are used for type information.
I am not sure if that's feasible in Jass without proper bitwise operators (and also JS has 64bit numbers, so a lot more storage to begin with), but it's interesting nonetheless.

I think boxing/unboxing is the general term for this?
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
Value types are pretty neat. Wish vjass had them. But at those gymnastics you have to do you could simply start using wurst. They're called tuple-types over there.
 
Status
Not open for further replies.
Top