• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Structs in vanilla WE

Status
Not open for further replies.
Level 12
Joined
Jan 2, 2016
Messages
973
If you want to know how to use "structs" in vanilla WE - there is a way.
Here is an example struct I made (it's in the map's header, since I made it in the WE):
JASS:
// struct location
function create_location takes real x, real y returns integer
    local integer this = udg_struct_location
    if this == 0 then
        set this = udg_struct_location_free[0] + 1
        set udg_struct_location_free[0] = this
    else
        set udg_struct_location = udg_struct_location_free[this]
    endif
    set udg_struct_location_free[this] = - 1
    if udg_struct_location <= 8190 then
        set udg_struct_location_x[this] = x
        set udg_struct_location_y[this] = y
        return this
    endif
    return 8191
endfunction

function destroy_location takes integer this returns nothing
    if udg_struct_location_free[this] == -1 then
        set udg_struct_location_free[this] = udg_struct_location
        set udg_struct_location = this
    endif
endfunction

function move_location takes unit u, integer this returns nothing
    if udg_stuct_location_free[this] == -1 then
        call SetUnitX(u, udg_struct_location_x[this])
        call SetUnitY(u, udg_struct_location_y[this])
    endif
endfunction
// endstruct

and an example usage:

JASS:
function Trig_Test_Function_Actions takes nothing returns boolean
    local real x = GetRandomReal(GetRectMinX(bj_mapInitialPlayableArea), GetRectMaxX(bj_mapInitialPlayableArea))
    local real y = GetRandomReal(GetRectMinY(bj_mapInitialPlayableArea), GetRectMaxY(bj_mapInitialPlayableArea))
    local integer loc = create_location(x,y)
    call move_location(gg_unit_hfoo_0001, loc)
    call destroy_location(loc)
    return false
endfunction

//===========================================================================
function InitTrig_Test_Function takes nothing returns nothing
    set gg_trg_Test_Function = CreateTrigger(  )
    call TriggerRegisterPlayerEventEndCinematic( gg_trg_Test_Function, Player(0) )
    call TriggerAddCondition( gg_trg_Test_Function, Condition(function Trig_Test_Function_Actions) )
endfunction

In vJASS, with a real struct, this would look like:
JASS:
struct Point
    real x
    real y

    static method create takes real x, real y returns Point
        local Point loc = Point.allocate()
        set loc.x = x
        set loc.y = y
        return loc
    endfunction

// no "onDestroy" method, since nothing special is done.

    method move takes unit u returns nothing
        call SetUnitX(u, this.x)
        call SetUnitY(u, this.y)
    endmethod
endstruct

And the example trigger would've been almost the same :p

Do have in mind that this way of doing things isn't exactly the way structs are doing things.

I think real structs also have a boolean array, that is checking if a struct index is used or not, and prevents functions to use them if they aren't created (I haven't really tested it, but it'd be logical if they do). And real structs go from 1 to 8190, and then back to the 1-st free one (I'm not sure if they are going backwards or forward after reaching 8190, and not sure if they aren't using a loop to go to the 1-st unused index), anyways... the method I'm using is kind a good enough for imitating structs :p

This is for people using the vanilla WE, the ones using JNGP can just use normal structs :)

Oh, and 1 more example how to set the structs values after creating it:
JASS:
function Trig_Test_Function_Actions takes nothing returns boolean
    local integer loc = create_location(0,0)
    set udg_struct_location_x[loc] = GetRandomReal(GetRectMinX(bj_mapInitialPlayableArea), GetRectMaxX(bj_mapInitialPlayableArea))
    set udg_struct_location_y[loc] = GetRandomReal(GetRectMinY(bj_mapInitialPlayableArea), GetRectMaxY(bj_mapInitialPlayableArea))
    call move_location(gg_unit_hfoo_0001, loc)
    call destroy_location(loc)
    return false
endfunction
 
Last edited:
Structs internally use "-1" to flag an index as used (instead of a separate boolean). Here is an example of how it could be implemented:
https://github.com/nestharus/JASS/blob/master/jass/Systems/Alloc/Standard/script.j

It is definitely awesome to know how structs work internally, and it is pretty sweet that you got a nice vanilla JASS implementation going! :) However, the beauty of structs is the abstraction layer + the ability to create globals easily. In vanilla JASS, you don't have either of those. So I think vanilla JASS should stick to regular arrays and good ol' hashtables--otherwise you'll spend too much time writing boilerplate code.
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
I always do OOP implementations in vanilla, because it gives me flexibility and ease of access.

Here is an example of one of my resources:
JASS:
//************************************************************************************
//*
//*
//*                             LINKED LIST TABLE
//*
//*                                BY : ALMIA
//*
//*
//************************************************************************************
//*
//*    Used to create linked list
//*
//************************************************************************************
//*
//*
//*    CODE API
//*
//*    constant function LLT_PrevArrayValueKey takes nothing returns integer
//*    constant function LLT_NextArrayValueKey takes nothing returns integer
//*    constant function LLT_RecycleArrayValueKey takes nothing returns integer
//*    constant function LLT_ICChildKey takes nothing returns integer
//*
//*    - system constants for settings
//*    - Recommended not to be touched
//*
//*    function LLT_GetNextKey takes integer index returns integer
//*    function LLT_GetPrevKey takes integer index returns integer
//*    function LLT_GetRCKey takes integer index returns integer
//*
//*    - Value keys
//*
//*    function GetNextIndexOfList takes integer list , integer index returns integer
//*    function GetPrevIndexOfList takes integer list , integer index returns integer
//*    function GetRecycleIndexOfList takes integer list, integer index returns integer
//*    function GetInstanceCountOfList takes integer list returns integer
//*
//*    - Getters of indexes
//*    - Used for linked list's values
//*
//*    function SetNextIndexOfList takes integer list , integer index , integer value returns nothing
//*    function SetPrevIndexOfList takes integer list , integer index , integer value returns nothing
//*    function SetRecycleIndexOfList takes integer list , integer index , integer value returns nothing
//*    function SetInstanceIndexOfList takes integer list , integer value returns nothing
//*
//*    - Setters for indexes
//*    - Used for setting values of linked lists
//*
//*    function CreateLinkedList takes nothing returns integer
//*
//*    - Creates linked lists
//*
//*    function ClearLinkedList takes integer list returns nothing
//*
//*    - Clears linked lists cached values
//*
//*    function DestroyLinkedList takes integer list returns nothing
//*
//*    - Destroys linked list
//*
//*    function GetNewIndexFromLinkedList takes integer list returns integer
//*
//*    - Allocates a new instance for the linked list
//*
//*    function RecycleIndexFromLinkedList takes integer list,integer index returns nothing
//*
//*    - Recycles the instance
//*    - Commonly used when a code's effect ends
//*
//*    function GetNextIndexFromLinkedList takes integer index , integer list returns integer
//*
//*    - Gets next index for the enumeration of index
//*
//*    function GetFirstIndexFromLinkedList takes integer list returns integer
//*
//*    - Gets first index  for the enumeration of index
//*
//************************************************************************************
//*
//*                 ITERATION MODULE
//*
//*       Module for iterating instance
//*       Must be CnPed
//*
//*       All words in between "$" should be replaced by
//*       your own variable
//*
//*       Legend:
//*
//*       $LIST$ = Your List
//*       $INDEX$ = Your Index
//*       $CODE$ = Your code
//*
//*
//*       set $INDEX$ = GetFirstIndexFromLinkedList($LIST$)
//*
//*       loop
//*           exitwhen $INDEX$ == 0
//*
//*           $CODE$
//*           ( NOTE : If the code's effect ends
//*            please recycle index via calling:
//*            call RecycleIndexFromLinkedList($LIST$, $INDEX$)
//*            in a custom script)
//*
//*           set $INDEX$ = GetNextIndexFromLinkedList($LIST$, $INDEX$)
//*       
//*       endloop
//*
//*
//*       DESCRIPTION : 
//*       
//*       This way,you can iterate indexed variables
//*       unlike Indexed Arrays or Dynamic Indexing
//*       You use custom scripts because GUI For Loop Integer
//*       cannot handle this kind of iteration
//*
//************************************************************************************
//*
//*    Variables :
//*
//*    LLT_Table = hashtable
//*    LLT_RC = integer array
//*    LLT_IC = integer
//*
//************************************************************************************
//*
//*    Credits :
//*
//*    - Magtheridon96
//*    - Maker
//*
//************************************************************************************

//***********************************************************
//*
//*                        SETTINGS
//*
//***********************************************************

//The following constants refer to minimum value of next
//prev and recycle.

constant function LLT_PrevArrayValueKey takes nothing returns integer
    return 0  
endfunction

constant function LLT_NextArrayValueKey takes nothing returns integer
    return JASS_MAX_ARRAY_SIZE// 8192
endfunction

constant function LLT_RecycleArrayValueKey takes nothing returns integer
    return 2* JASS_MAX_ARRAY_SIZE //16384
endfunction

constant function LLT_ICChildKey takes nothing returns integer
    return -1
endfunction

//***********************************************************
//*
//*                        TOOLS
//*
//***********************************************************
function LLT_GetNextKey takes integer index returns integer
    return LLT_NextArrayValueKey() + index
endfunction

function LLT_GetPrevKey takes integer index returns integer
    return LLT_PrevArrayValueKey() + index
endfunction

function LLT_GetRCKey takes integer index returns integer
    return LLT_RecycleArrayValueKey() + index
endfunction

//*                SETS and GETS functions

//Get Values

function GetNextIndexOfList takes integer list , integer index returns integer
    return LoadInteger(udg_LLT_Table, list, LLT_GetNextKey(index))
endfunction

function GetPrevIndexOfList takes integer list , integer index returns integer
    return LoadInteger(udg_LLT_Table, list, LLT_GetPrevKey(index))
endfunction

function GetRecycleIndexOfList takes integer list , integer index returns integer
    return LoadInteger(udg_LLT_Table, list, LLT_GetRCKey(index))
endfunction

function GetInstanceCountOfList takes integer list returns integer
    return LoadInteger(udg_LLT_Table, list, LLT_ICChildKey())
endfunction

//Set Values

function SetNextIndexOfList takes integer list , integer index , integer value returns nothing
    call SaveInteger(udg_LLT_Table, list, LLT_GetNextKey(index), value)
endfunction

function SetPrevIndexOfList takes integer list , integer index , integer value returns nothing
    call SaveInteger(udg_LLT_Table, list, LLT_GetPrevKey(index), value)
endfunction

function SetRecycleIndexOfList takes integer list , integer index , integer value returns nothing
    call SaveInteger(udg_LLT_Table, list, LLT_GetRCKey(index), value)
endfunction

function SetInstanceIndexOfList takes integer list , integer value returns nothing
    call SaveInteger(udg_LLT_Table, list, LLT_ICChildKey(), value)
endfunction

//***********************************************************
//*
//*                      MAIN FUNCTIONS
//*
//***********************************************************
function CreateLinkedList takes nothing returns integer
    local integer index  = udg_LLT_RC[0]
    
    //Initializing
    if null == udg_LLT_Table then
        set udg_LLT_Table = InitHashtable()
    endif
    if 0 == index then
        set udg_LLT_IC = udg_LLT_IC + 1
        return udg_LLT_IC
    endif
    set udg_LLT_RC[0] = udg_LLT_RC[index]
    return index
endfunction

function ClearLinkedList takes integer list returns nothing
    call FlushChildHashtable(udg_LLT_Table, list)
endfunction

function DestroyLinkedList takes integer list returns nothing
    call ClearLinkedList(list)
    set udg_LLT_RC[list] = udg_LLT_RC[0]
    set udg_LLT_RC[0] = list
endfunction

function GetNewIndexFromLinkedList takes integer list returns integer
    local integer this = GetRecycleIndexOfList(list, 0) 
    local integer ic
    //Allocating Instance
    if 0 == this then
        set ic = GetInstanceCountOfList(list) + 1
        call SetInstanceIndexOfList(list, ic)
        set this = ic
    else
        call SetRecycleIndexOfList(list, 0, GetRecycleIndexOfList(list, this))
    endif
    // Adding the instance to list
    call SetNextIndexOfList(list, this, 0)
    call SetPrevIndexOfList(list, this, 0)
    call SetNextIndexOfList(list, GetPrevIndexOfList(list, 0), this)
    call SetPrevIndexOfList(list, 0, this)
    return this

endfunction

function RecycleIndexFromLinkedList takes integer list,integer index returns nothing 
    //Recycling instance
    call SetNextIndexOfList(list, GetPrevIndexOfList(list, index), GetNextIndexOfList(list, index))
    call SetPrevIndexOfList(list, GetNextIndexOfList(list, index), GetPrevIndexOfList(list, index))
    call SetRecycleIndexOfList(list, index, GetRecycleIndexOfList(list, 0))
    call SetRecycleIndexOfList(list, 0, index)
    
endfunction

//***********************************************************
//*
//*                    EXTRA FUNCTIONS
//*
//***********************************************************

function GetNextIndexFromLinkedList takes integer list , integer index returns integer
    return GetNextIndexOfList(list, index)
endfunction

function GetFirstIndexFromLinkedList takes integer list returns integer
    return GetNextIndexOfList(list, 0)
endfunction
 
Level 7
Joined
Oct 19, 2015
Messages
286
I always do OOP implementations in vanilla, because it gives me flexibility and ease of access.
It gives me just a headache.

Attempting to reproduce vJass features in regular JASS is pointless. They are far too verbose to be practical.
 
Level 13
Joined
Nov 7, 2014
Messages
571
I think there's at least one guy/gal that can improve his/her map after reading this, I forgot what he/she was called, maybe it was something like "ColdToad" or "FreezingAmphibian", I don't remember.
Anyway, as far as I know his/her map is the most "hashtable happy" (long ago "gamecache happy") map you could find, but I guess not having an easy way to declare global variables could be the reason to stick with those native function calls,
and maybe it's better to help the "Ph. D, MIT, Theoretical Physics" guy to see the Borealis ship, instead of rewriting old code. But I guess he/she could use those for some new code? Maybe.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
GUI Spell System, largely based on Anitarf's Spell Event, uses the same behavior as normal structs to create unique integers.

Werelf, your example script is broken. You return a local variable which may not be set to the correct value. And yes, a singly-linked list is used for recycling instead of a linear stack.
 
Level 12
Joined
Jan 2, 2016
Messages
973
Can ya give me an example when would it return a wrong value, so I can fix it? :p

At the moment its behaviour is like:
"create" - gives 0
"create" - gives 1
"create" - gives 2
"create" - gives 3
"destroy 2"
"create" - gives 4
"create" - gives 2
"destroy 1"
"destroy 3"
"destroy 0"
"create" - gives 5
"create" - gives 1
"destroy 4"
"create" - gives 3
"create" - gives 4
"create" - gives 0
"create" - gives 6
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
What you're doing is off. An index of 0 is supposed to be invalid, and in your case it isn't.

Also, what's with the weird variable names?

The better approach would be to make a standalone "index generator" resource for GUI users which doesn't hardcode a specific struct into it, for modularity.

JASS:
function CreateInt takes nothing returns integer
    local integer this = udg_IntIndexerLast //see if there is a recycled entity to use, first
    if this == 0 then
        set this = udg_IntIndexerMax + 1 //Index generation always above 0
        set udg_IntIndexerMax = this
    else
        set udg_IntIndexerLast = udg_IntIndexerList[this] //the last-last recycled integer will be the next one to be recycler
    endif
    set udg_IntIndexerList[this] = -1 //index is active
    return this
endfunction

function DestroyInt takes integer this returns boolean
    if udg_IntIndexerList[this] == -1 then
        set udg_IntIndexerList[this] =  udg_IntIndexerLast //Have the list remember the last recycled entity
        set udg_IntIndexerLast = this //this is now the last recycled entity
        return true
    endif
    return false //return relevant boolean to ensure systems using this don't double-free.
endfunction
 
Level 12
Joined
Jan 2, 2016
Messages
973
Ha! That's pretty clever :p
Took me a while until I understood how it works (even tho our ways of doing things are quite similar. I have a counter how many recycled values do I have, while you are just checking if the saved value is different than 0) :D
Anyways.. I was thinking to do something like that, but then I figured that different "structs" would need seperate "lists", so I just did it the way I did.
By the way, I changed a bit the original post, now it checks the "next" value when it's asked to assign one, instead of assigning the "next" value just before giving "this" value.
And now it starts its count from 1, instead of 0. Tho I don't really understand why should I do that in case the way "my" struct version works is not how real structs work :p

EDIT: I edited my initial post to do things more "your way" :p

EDIT 2: I figured why "0" is unwanted value. People usually check if a struct is equal to 0 to see if it's a real struct or not. If I'm assigning the 0 - it will fail this check xP
 
Last edited:
Level 7
Joined
Oct 19, 2015
Messages
286
Anyways.. I was thinking to do something like that, but then I figured that different "structs" would need seperate "lists",
That can still be done, the create and destroy functions would just need to take an additional parameter for the "type" of the "struct". Internally, you'd then need to use a hashtable to be able to accommodate storing your data in 2 dimensions.
 
Level 12
Joined
Jan 2, 2016
Messages
973
Hmm, I doubt this, but is there any way to use the "handle of a variable".
Example:
I want to have "real array index_1" and "real array index_2"
and then I want to have 1 functin for both of them, without using an "if"
like this:
JASS:
function ActionFunc takes real r, "real handle" idx returns real
    return idx[0] * r
endfunction

function TestFunc takes nothing returns nothing
    set index_1[udg_someInt] = ActionFunc(0.5, index_1)
endfunction
Hope you get the idea...
Any way to do this without a textmacro, and actually making different function for every "type"?
 
Level 12
Joined
Jan 2, 2016
Messages
973
By "using a hashtable" you mean "just save them into a hashtable", or do you mean "you can still save them into arrays, but you need a hashtable to make it work"
If it's the latter, can ya post an example?
If it's the 1-st, then nevermind.
 
Status
Not open for further replies.
Top