coding efficient vjass structs

Jass Tutorials

14 years
edited 8 years
Many people continue to ask me what the difference is between struct Hello and struct Hello extends array is.

In vJASS, regular structs create some extra code in the background to make themselves work.

Code (jass) Select
1
2
struct a
3
endstruct
4


outputs

Code (jass) Select
1
2
constant integer si__a=1
3
integer si__a_F=0
4
integer si__a_I=0
5
integer array si__a_V
6
7
//Generated allocator of a
8
function s__a__allocate takes nothing returns integer
9
 local integer this=si__a_F //first node on recycle stack
10
    if (this!=0) then
11
        set si__a_F=si__a_V[this] //set stack to next node (like stack.next)
12
    else
13
        set si__a_I=si__a_I+1
14
        set this=si__a_I
15
    endif
16
    if (this>8190) then //protection against too many structs
17
        return 0
18
    endif
19
20
    set si__a_V[this]=-1 //set stack to -1 (stack.next = -1)
21
 return this
22
endfunction
23
24
//Generated destructor of a
25
function s__a_deallocate takes integer this returns nothing
26
    if this==null then //don't deallocate null instance
27
        return
28
    elseif (si__a_V[this]!=-1) then //double free protection
29
        return
30
    endif
31
    set si__a_V[this]=si__a_F //set this.next = stack
32
    set si__a_F=this //set stack = this
33
endfunction
34


Code (jass) Select
1
2
struct b extends a
3
endstruct
4


outputs

Code (jass) Select
1
2
constant integer si__a=1
3
integer si__a_F=0
4
integer si__a_I=0
5
integer array si__a_V
6
constant integer si__b=2
7
integer array si__a_type
8
trigger array st__a_onDestroy //Trigger array?!?!
9
integer f__arg_this
10
11
//Generated allocator of a
12
function s__a__allocate takes nothing returns integer
13
 local integer this=si__a_F
14
    if (this!=0) then
15
        set si__a_F=si__a_V[this]
16
    else
17
        set si__a_I=si__a_I+1
18
        set this=si__a_I
19
    endif
20
    if (this>8190) then
21
        return 0
22
    endif
23
24
    set si__a_type[this]=1
25
    set si__a_V[this]=-1
26
 return this
27
endfunction
28
29
//Generated destructor of a
30
function sc__a_deallocate takes integer this returns nothing
31
    if this==null then
32
        return
33
    elseif (si__a_V[this]!=-1) then
34
        return
35
    endif
36
    set f__arg_this=this
37
    call TriggerEvaluate(st__a_onDestroy[si__a_type[this]]) //AHH!!
38
    set si__a_V[this]=si__a_F
39
    set si__a_F=this
40
endfunction
41
42
//Generated allocator of b
43
function s__b__allocate takes nothing returns integer
44
 local integer this=s__a__allocate()
45
 local integer kthis //why??
46
    if(this==0) then
47
        return 0
48
    endif
49
    set si__a_type[this]=2
50
    set kthis=this //... doesn't actually do anything
51
52
 return this
53
endfunction
54


As can be seen, trigger evaluations pop up and useless variables get generated.

Code (jass) Select
1
2
struct a extends array
3
endstruct
4


generates

constant integer si__a=1

Not bad, but this means that this sort of thing will no longer work
Code (jass) Select
1
2
local a myStruct = a.create()
3
call myStruct.destroy()
4


meaning that the allocation and deallocation of structs is left up to the coders.

So let's look on how structs are actually allocated (getting rid of the virtually useless double free protection and getting rid of allocate/deallocate as they might as well be put into create and destroy).

As structs are created, the total number of structs generated continues to increase. A counter is needed in order to track how many structs have been created.

private static integer instanceCount = 0

As structs are destroyed, their instances need to be recycled.

Code (jass) Select
1
2
private static thistype recycle = 0 //next recycled instance
3
private thistype recycleNext //recycled stack
4


Code (jass) Select
1
2
struct MyStruct extends array
3
    private static integer instanceCount = 0
4
    private static thistype recycle = 0
5
    private thistype recycleNext
6
7
    static method create takes nothing returns thistype
8
        local thistype this
9
10
        //first check to see if there are any structs waiting to be recycled
11
        if (recycle == 0) then
12
            //if recycle is 0, there are no structs, so increase instance count
13
            set instanceCount = instanceCount + 1
14
            set this = instanceCount
15
        else
16
            //a struct is waiting to be recycled, so use it
17
            set this = recycle
18
            set recycle = recycle.recycleNext
19
        endif
20
21
        //perform creation code
22
23
        return this
24
    endmethod
25
26
    method destroy takes nothing returns nothing
27
        //add to recycle stack
28
        set recycleNext = recycle
29
        set recycle = this
30
    endmethod
31
endstruct
32


Code output from above (does same thing as first example w/o double free protection)

Code (jass) Select
1
2
constant integer si__MyStruct=1
3
integer s__MyStruct_instanceCount= 0
4
integer s__MyStruct_recycle= 0
5
integer array s__MyStruct_recycleNext
6
7
function s__MyStruct_create takes nothing returns integer
8
    local integer this
9
    if ( s__MyStruct_recycle == 0 ) then
10
        set s__MyStruct_instanceCount=s__MyStruct_instanceCount + 1
11
        set this=s__MyStruct_instanceCount
12
    else
13
        set this=s__MyStruct_recycle
14
        set s__MyStruct_recycle=s__MyStruct_recycleNext[s__MyStruct_recycle]
15
    endif
16
    return this
17
endfunction
18
19
function s__MyStruct_destroy takes integer this returns nothing
20
    set s__MyStruct_recycleNext[this]=s__MyStruct_recycle
21
    set s__MyStruct_recycle=this
22
endfunction
23


A bit more work, but a bit more optimal. What about extending structs?

Extending structs is done with delegates (also allows members to be overriden).

Code (jass) Select
1
2
struct Mystruct2 extends array
3
    //delegate stores pointers to parent struct
4
    //this means that one can extend off of multiple structs
5
    private delegate MyStruct MyStruct
6
    
7
    static method create takes nothing returns thistype
8
        //base instance off of parent struct
9
        local thistype this = MyStruct.create()
10
        //store pointer into delegate
11
        set MyStruct = this
12
        
13
        return this
14
    endmethod
15
    
16
    method destroy takes nothing returns nothing
17
        //simply destroy
18
        call MyStruct.destroy()
19
    endmethod
20
endstruct
21


Outputs
Code (jass) Select
1
2
constant integer si__MyStruct=1
3
integer s__MyStruct_instanceCount= 0
4
integer s__MyStruct_recycle= 0
5
integer array s__MyStruct_recycleNext
6
constant integer si__Mystruct2=2
7
integer array s__Mystruct2_MyStruct
8
9
function s__MyStruct_create takes nothing returns integer
10
	local integer this
11
	if ( s__MyStruct_recycle == 0 ) then
12
		set s__MyStruct_instanceCount=s__MyStruct_instanceCount + 1
13
		set this=s__MyStruct_instanceCount
14
	else
15
		set this=s__MyStruct_recycle
16
		set s__MyStruct_recycle=s__MyStruct_recycleNext[s__MyStruct_recycle]
17
	endif
18
	return this
19
endfunction
20
21
function s__MyStruct_destroy takes integer this returns nothing
22
	set s__MyStruct_recycleNext[this]=s__MyStruct_recycle
23
	set s__MyStruct_recycle=this
24
endfunction
25
26
function s__Mystruct2_create takes nothing returns integer
27
	local integer this= s__MyStruct_create()
28
	set s__Mystruct2_MyStruct[this]=this
29
	return this
30
endfunction
31
32
function s__Mystruct2_destroy takes integer this returns nothing
33
	call s__MyStruct_destroy(s__Mystruct2_MyStruct[this])
34
endfunction
35


No trigger arrays, no trigger evaluations, and no wasted local variables. It even allows you to make one struct extend off of multiple structs.

Multi-Struct Extension
Code (jass) Select
1
2
struct MyStruct extends array
3
    private static integer instanceCount = 0
4
    private static thistype recycle = 0
5
    private thistype recycleNext
6
7
    static method create takes nothing returns thistype
8
        local thistype this
9
        if (recycle == 0) then
10
            set instanceCount = instanceCount + 1
11
            set this = instanceCount
12
        else
13
            set this = recycle
14
            set recycle = recycle.recycleNext
15
        endif
16
        return this
17
    endmethod
18
19
    method destroy takes nothing returns nothing
20
        set recycleNext = recycle
21
        set recycle = this
22
    endmethod
23
endstruct
24
25
struct MyStruct2 extends array
26
    private static integer instanceCount = 0
27
    private static thistype recycle = 0
28
    private thistype recycleNext
29
30
    static method create takes nothing returns thistype
31
        local thistype this
32
        if (recycle == 0) then
33
            set instanceCount = instanceCount + 1
34
            set this = instanceCount
35
        else
36
            set this = recycle
37
            set recycle = recycle.recycleNext
38
        endif
39
        return this
40
    endmethod
41
42
    method destroy takes nothing returns nothing
43
        set recycleNext = recycle
44
        set recycle = this
45
    endmethod
46
endstruct
47
48
struct Mystruct3 extends array
49
    private delegate MyStruct MyStruct
50
    private delegate MyStruct2 MyStruct2
51
    
52
    static method create takes nothing returns thistype
53
        local thistype this = MyStruct.create()
54
        set MyStruct = this
55
        
56
        //use first pointer as current struct's instance
57
        set MyStruct2 = MyStruct2.create()
58
        
59
        //creation code
60
        
61
        return this
62
    endmethod
63
    
64
    method destroy takes nothing returns nothing
65
        //destroy both structs
66
        call MyStruct.destroy()
67
        call MyStruct2.destroy()
68
    endmethod
69
endstruct
70


Which happily outputs
Code (jass) Select
1
2
constant integer si__MyStruct=1
3
integer s__MyStruct_instanceCount= 0
4
integer s__MyStruct_recycle= 0
5
integer array s__MyStruct_recycleNext
6
constant integer si__MyStruct2=2
7
integer s__MyStruct2_instanceCount= 0
8
integer s__MyStruct2_recycle= 0
9
integer array s__MyStruct2_recycleNext
10
constant integer si__Mystruct3=3
11
integer array s__Mystruct3_MyStruct
12
integer array s__Mystruct3_MyStruct2
13
14
function s__MyStruct_create takes nothing returns integer
15
	local integer this
16
	if ( s__MyStruct_recycle == 0 ) then
17
		set s__MyStruct_instanceCount=s__MyStruct_instanceCount + 1
18
		set this=s__MyStruct_instanceCount
19
	else
20
		set this=s__MyStruct_recycle
21
		set s__MyStruct_recycle=s__MyStruct_recycleNext[s__MyStruct_recycle]
22
	endif
23
	return this
24
endfunction
25
26
function s__MyStruct_destroy takes integer this returns nothing
27
	set s__MyStruct_recycleNext[this]=s__MyStruct_recycle
28
	set s__MyStruct_recycle=this
29
endfunction
30
31
32
function s__MyStruct2_create takes nothing returns integer
33
	local integer this
34
	if ( s__MyStruct2_recycle == 0 ) then
35
		set s__MyStruct2_instanceCount=s__MyStruct2_instanceCount + 1
36
		set this=s__MyStruct2_instanceCount
37
	else
38
		set this=s__MyStruct2_recycle
39
		set s__MyStruct2_recycle=s__MyStruct2_recycleNext[s__MyStruct2_recycle]
40
	endif
41
	return this
42
endfunction
43
44
function s__MyStruct2_destroy takes integer this returns nothing
45
	set s__MyStruct2_recycleNext[this]=s__MyStruct2_recycle
46
	set s__MyStruct2_recycle=this
47
endfunction
48
49
50
function s__Mystruct3_create takes nothing returns integer
51
	local integer this= s__MyStruct_create()
52
	set s__Mystruct3_MyStruct[this]=this
53
	set s__Mystruct3_MyStruct2[this]=s__MyStruct2_create()
54
	return this
55
endfunction
56
57
function s__Mystruct3_destroy takes integer this returns nothing
58
	call s__MyStruct_destroy(s__Mystruct3_MyStruct[this])
59
	call s__MyStruct2_destroy(s__Mystruct3_MyStruct2[this])
60
endfunction
61