Extend Array

Level 22
Joined
Feb 6, 2014
Messages
2,467
When a struct extends array, it will literally acts as an array losing its .allocate and .deallocate method (and inheritance I think?). People usually use this when they want an arrays to have the struct syntax, they don't want an allocation or have their own ways of allocation.

what should i do on Extend Array??
Depends, if you dynamically create the struct, then you should have their own allocation and deallocation method. But instead of doing that, why don't you just use a normal struct instead? (or implement Alloc resources like this or this). People usually use an Alloc resource for less script generated and the debugging features it offers.
 
^+.

Extends array can be usually used for performance optimization, or for an OOP looking syntax.
The user may, or may not declare his own allocation function. He is not forced to.
A struct can not extend a class which extends array for example, so it loses some elemental "class features".

It can be useful sometimes, but mostly only because the basic allocation is not the best.

You can read here a bit and see a example: http://www.wc3c.net/vexorian/jasshelpermanual.html#arrstruct
But use this knowledge advisedly. Now you're dangerous.
 
Level 7
Joined
Oct 19, 2015
Messages
286
It mainly has to do with his descriptions of how allocators and deallocators work, so maybe I exaggerated a bit the scope of his error.

Child.allocate() will call Parent.create(), not Parent.allocate() as his description states. The other descriptions seem similarly confused. It seems a lot simpler to me to simply say that child's allocate and deallocate methods will always call parent's create and destroy, which are then expected to call the parent's allocate and deallocate internally, and so the cycle continues through the inheritance tree. If you don't declare a custom create method, then the default method gets used which is just an allocate call so it gets inlined, so yes in this case the child's allocator will technically call the parent's allocator directly, but that's a special case and doesn't really work as a general explanation.

The onDestroy method is mostly a relic of the past when we weren't able to declare custom destroy methods yet, unfortunately we're stuck with it but there's no real need to use it any more.
 

Dr Super Good

Spell Reviewer
Level 60
Joined
Jan 18, 2005
Messages
26,735
In theory a struct should just be a type which collects various variables together in a way they can be declared and interacted together from a higher level. However vJASS structs implicitly create backing arrays for member variables so as to easily support methods and dynamic allocation. As such vJASS struct variables are in theory struct references, a mechanic closer to java class than C/C++ struct. Additionally each struct type can be viewed as being assigned a separate memory bank for storage in the form of their own set of arrays.

What extends array does it allows you to interact with the struct more closely to how it is implemented. Since the struct references translate into an address in the backing arrays and these arrays are always size 8192 then one can treat the entire type as an array of size 8192. One does not even need the struct to extend array to be able to do this since the arrays are always size 8192 however doing so will break or render redundant allocation/deallocation of structs. What extending array does is explicitly disables allocation/deallocation and gives you access to the underlying backing arrays from the type itself.

Why structs are implemented like this I will never understand. They should rather have been kept a type in the form of grouped variables. Allocation and struct references should then have been done from a "memory bank" which implements a referencing scheme and allocation model for a struct type. When generating method implementations the compiler would then produce functions for each declaration of the type, inlining when appropriate. This would allow you to pass by value structs as parameters, declare multiple banks for struct types and declare multiple struct arrays. In the case of singleton structs this would be massive performance improvement as non-array variables could be used. It certainly would be a lot cleaner than the vJASS implementation as it would not have such obscure syntax and unintuitive mechanics.
 
Level 13
Joined
Nov 7, 2014
Messages
570
In the case of singleton structs this would be massive performance improvement as non-array variables could be used.

JASS:
struct my_global_vars_aka_singleton extends array
    integer foo = 5
    real tau = 6.2831
endstruct

call BJDebugMsg(I2S(my_global_vars_aka_singleton.foo))

Why structs are implemented like this I will never understand. They should rather have been kept a type in the form of grouped variables. Allocation and struct references should then have been done from a "memory bank" which implements a referencing scheme and allocation model for a struct type. When generating method implementations the compiler would then produce functions for each declaration of the type, inlining when appropriate. This would allow you to pass by value structs as parameters, declare multiple banks for struct types and declare multiple struct arrays. In the case of singleton structs this would be massive performance improvement as non-array variables could be used. It certainly would be a lot cleaner than the vJASS implementation as it would not have such obscure syntax and unintuitive mechanics.

Would you mind giving an example/reference implementation of a simple struct that fits your description and what would the generated jass look like?
 

Dr Super Good

Spell Reviewer
Level 60
Joined
Jan 18, 2005
Messages
26,735
JASS:
struct my_global_vars_aka_singleton extends array
    integer foo = 5
    real tau = 6.2831
endstruct

call BJDebugMsg(I2S(my_global_vars_aka_singleton.foo))
As far as I am aware that will not compile and it will still create backing arrays.

What I am referring to would look something like this...
JASS:
struct struct_t
    integer foo = 5
    real bar = 6.2831
endstruct

globals
    struct_t singleton
    struct_t array[16] structarray // size needed for initializer, index 16+ not initialized
endglobals

// example usage
call BJDebugMsg(I2S(singleton.foo))
call BJDebugMsg(R2S(singleton.bar))
call BJDebugMsg(I2S(structarray[0].foo))
call BJDebugMsg(R2S(structarray[15].bar))

This would then produce optimized code some what like...
JASS:
struct struct_t
    integer foo = 5
    real bar = 6.2831
endstruct

globals
    integer struct_t_singleton_foo = 5
    real struct_t_singleton_bar = 6.2831
    integer array struct_t_structarray_foo
    real array struct_t_structarray_bar
endglobals

// following code run on initialization
local integer i = 0
loop
    set struct_t_structarray_foo[i] = 5
    set struct_t_structarray_bar[i] = 6.2831
    set i = i + 1
    exitwhen i >= 16
endloop

// example usage code
call BJDebugMsg(I2S(struct_t_singleton_foo))
call BJDebugMsg(R2S(struct_t_singleton_bar))
call BJDebugMsg(I2S(struct_t_structarray_foo[0]))
call BJDebugMsg(R2S(struct_t_structarray_bar[15]))

Dynamic allocation syntax is open for debate.
JASS:
struct struct_t
    integer foo = 5
    real bar = 6.2831
endstruct

// a new system is needed for memory bank definitions, there would be a few standard implementations built in
bankdef
   ....
endbankdef

bankmap
    dynamic struct_t struct_tr // same allocation model as standard vJASS new/delete
    // one can explicitly name banks, needed for OOP stuff so that child types will share the same bank
    // dynamic<std> struct_t struct_tr // functionally same as above, std name space used by default

    // new allocation model for explicit size support (more than 8192 instances supported)
    // sized<std, 20000> struct_t struct_tr

    // new allocation model with hashtable backing instead of array
    // hashed<std> struct_t struct_tr
endbankmap

// example usage
local struct_tr obj
set obj = new struct_t()
call BJDebugMsg(I2S(obj.foo))
call BJDebugMsg(R2S(obj.bar))
delete obj

// example usage with generic cross bank references, these are slow so should never really be used
local struct_tr obj
local struct_t reference obj2
set obj = new struct_t()
set obj2 = obj // implementation for bank operations is passed in the form of a virtual function table as well as struct address
// set obj2 = new struct_t() // not legal, unknown bank to use to allocate
// set obj = obj2 // only legal if obj instance references same bank type as obj tested using virtual function table reference, otherwise error is thrown and thread crashed
delete obj2 // virtual functions used for this as well

A lot of the code this could produce is quite inefficient. However it would be a lot more powerful than the way current structs are implemented.
 
Level 13
Joined
Nov 7, 2014
Messages
570
This would then produce optimized code some what like...

JASS:
struct struct_t
    integer foo = 5
    real bar = 6.2831
endstruct

globals
    integer struct_t_singleton_foo = 5
    real struct_t_singleton_bar = 6.2831
    integer array struct_t_structarray_foo
    real array struct_t_structarray_bar
endglobals

// following code run on initialization
local integer i = 0
loop
    set struct_t_structarray_foo[i] = 5
    set struct_t_structarray_bar[i] = 6.2831
    set i = i + 1
    exitwhen i >= 16
endloop

// example usage code
call BJDebugMsg(I2S(struct_t_singleton_foo))
call BJDebugMsg(R2S(struct_t_singleton_bar))
call BJDebugMsg(I2S(struct_t_structarray_foo[0]))
call BJDebugMsg(R2S(struct_t_structarray_bar[15]))

Well if each struct declaration created it's own backing global variables how would you pass/return structs to/from functions?

JASS:
struct struct_t
    integer foo = 5
    real bar = 6.2831
endstruct

globals
    struct_t A
    // integer struct_t_A_foo = 5
    // real struct_t_A_bar = 6.2831

    struct_t B
    // integer struct_t_B_foo = 5
    // real struct_t_B_bar = 6.2831

    struct_t array[16] my_struct_t_array
    // integer array my_struct_t_array_foo
    // real array my_struct_t_array_bar
endglobals

function my_func takes struct_t s returns nothing
// function my_func takes integer s_foo, real s_bar returns nothing
    local real baz = s.foo + s.bar
    // local real baz = s_foo + s_bar
endfunction

function returing_struct_t takes nothing returns struct_t
    local struct_t result

    set result.foo = GetRandomInt(1, 100)
    set result.bar = GetRandomReal(0, 1)

    return result

    // globals
    //     integer return_struct_t_foo
    //     real return_struct_t_bar
    // endglobals
    //
    // set return_struct_t_foo = struct_t_result_foo
    // set return_struct_t_bar = struct_t_result_bar
    //
    // on the call site
    // set my_struct_t = reuturing_struct_t()
    // set struct_t_my_struct_t_foo = return_struct_t_foo
    // set struct_t_my_struct_t_bar = return_struct_t_bar
endfunction

function main takes nothing returns nothing
    local struct_t C
    // globals
    //     integer local_struct_t_C_foo = 5
    //     real local_struct_t_C_bar = 6.2831
    // endglobals

    call my_func(A)
    // call my_func(struct_t_A_foo, struct_t_A_bar)

    call my_func(B)
    // call my_func(struct_t_B_foo, struct_t_B_bar)

    if GetRandomReal(0, 1) < 0.5 then
        set C = A
        // set local_struct_t_C_foo = struct_t_A_foo
        // set local_struct_t_C_bar = struct_t_A_bar
    else
        set C = B
        // set local_struct_t_C_foo = struct_t_B_foo
        // set local_struct_t_C_bar = struct_t_B_bar
    endif

    call my_func(C)
    // call my_func(local_struct_t_C_foo, local_struct_t_C_bar)

    // if struct_t has a lot of members that could be problematic if there's a limit on the number arguments a function could take,
    // and it might be the case that calling a function with more arguments is slower, idk...

    call my_func(my_struct_t_array[GetRandomInt(0, 15)])
    // set i_expr_1 = GetRandomInt(0, 15)
    // call my_func(my_struct_t_array_foo[i_expr_1], my_struct_t_array_bar[i_expr_1])
endfunction

struct A
    integer foo = 1
    real bar = 2.0
endstruct

struct B
    A a
    string baz = "=)"
endstruct

globals
    A some_a
    // integer A_some_a_foo = 1
    // real A_some_a_bar = 2.0

    B some_b
    // integer B_some_b_A_foo = 1
    // real B_some_b_A_bar = 2.0
    // integer B_some_b_baz = "=)"
endglobals

Could work, I guess... Although there's a limit on the number of global variables (~25_000) so that might also be a problem.

You lost me on the "dynamic part", though.
 

Dr Super Good

Spell Reviewer
Level 60
Joined
Jan 18, 2005
Messages
26,735
Well if each struct declaration created it's own backing global variables how would you pass/return structs to/from functions?
By value. Basically each struct member then becomes a function parameter. This is very good for inlining as methods could be unwound and work directly on the function parameters. Returning structs would need "register globals" since you are limited to returning only one argument, these register globals would then be copied to locals after function return based on how the value is used.

Currently structs are passed by references. This is desirable and a lot more efficient for complex structs. That is covered by the next bit since you would need memory banks to hold the structs and provide an address space to reference them with.

Could work, I guess... Although there's a limit on the number of global variables (~25_000) so that might also be a problem.
You would mostly want to work with structs in their current form, which is provided by the bank mechanics to reference them.

Using structs directly as variables would be for structs intended as data types. For example a x y pair might be declared as a struct with various Euclidian operations provided as methods. Declaring a variable of that type would then compile into separate variables for the x and y components which is what is commonly done. The methods on this could then inline to produce the mathematics which people commonly use. Another example is the obvious singleton type where only ever 1 instance will exist.

You lost me on the "dynamic part", though.
Current structs are aimed at dynamic memory allocation and do so by backing every member variable with an array of the same type to form a memory bank. Struct types are then references within this memory bank to the struct instance. A memory model with the memory bank allows for dynamic allocation and deallocation of structs.

This locks you into one implementation of memory banks and also forces this functionality. This is where extends array currently works to allow you access to the memory bank directly in a safe way.

In the suggested model this would be done with bank declarations which are used to produce, manage and evaluate struct references. The special reference keyword type would allow for cross-bank referencing of the same struct but is not recommended as it would be extremely slow and generate extremely bloated code but still may be useful for debugging or possibly other strange purposes.
 
Level 7
Joined
Oct 19, 2015
Messages
286
JASS:
struct my_global_vars_aka_singleton extends array
    integer foo = 5
    real tau = 6.2831
endstruct

call BJDebugMsg(I2S(my_global_vars_aka_singleton.foo))
As far as I am aware that will not compile and it will still create backing arrays.
JASS:
struct my_global_vars_aka_singleton extends array
    static integer foo = 5
    static real tau = 6.2831
endstruct

call BJDebugMsg(I2S(my_global_vars_aka_singleton.foo))
There, fixed.

I think vJass is fine the way it is. So what if a vJass struct is closer to a Java class than a C struct? So what if the implementation isn't perfectly generalised or whatever other abstract concepts you're complaining about. It's useful, and if anything your suggestions so far seemed less useful by comparison. So for each struct I'd first need to declare a struct, then a bank, then who knows what else? This might be how the SC2 data editor came to be.
 

Dr Super Good

Spell Reviewer
Level 60
Joined
Jan 18, 2005
Messages
26,735
I think vJass is fine the way it is. So what if a vJass struct is closer to a Java class than a C struct? So what if the implementation isn't perfectly generalised or whatever other abstract concepts you're complaining about. It's useful, and if anything your suggestions so far seemed less useful by comparison. So for each struct I'd first need to declare a struct, then a bank, then who knows what else?
The bank feature is needed due to a lack of generic storage space being part of the language. Instead storage space has to be simulated using language features, and hence multiple implementations can exist.

The Java language abstracts storage away completely, something that JASS cannot do as it is not a feature of the language.

In C++ you need to declare references to objects to achieve what vJASS does automatically.

This might be how the SC2 data editor came to be.
Yes, making life make more sense is why it is a great editor. No "local declared local handle variable reference counter leak on return" there.
 
Top