• 🏆 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!

[vJASS] [Snippet] MultiArray

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Greetings
This thing allows you to have multidimensional variables. Like:
Var[0][0]
Var[1][1][1]
Var[30][30][30][30]​
Can be used for one-dimensional array as well with max size of 4092620.
For 2D the max size is 2023, almost twice larger than my Array2D.

The advantage is this doesn't use neither hashtable nor struct for the data storage. Hopefully it's faster, but the speed difference haven't been tested yet, probably this is slower xD.

The interface is fairly decent imo:
JASS:
set Var[1][1][1] = something

if Var[1][1][1].value == something then
endif

Multi-dimension array can be used for a lot of things in advanced map making: like racing map AI path generation, custom multiplayer interface, etc.

Code
JASS:
scope MultiArray // v1.0.0

    /****************************************************************************************
    
        == MultiArray ==
                by: Dalvengyr
                
                
        I. Description
        ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
            Allows you to declare a multidimensional array variable.
            
            Basically is an over-sized one-dimensional array variable represented in
            a fairly decent multi-dimensional array interface.
            
            The variable could be declared as a global or a static struct member.
        
        
        II. Disadvantages
        ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
            - Allocate tons of memory space for each generated variable.
            - Has max size of 2023.
            
            
        III. API
        ¯¯¯¯¯¯¯¯
            1. Declaring new variable
                | //! textmacro CREATE_MULTI_ARRAY takes TYPE, NAME, DIMENSION
                        TYPE    => integer, real, unit, string, etc.
                        NAME    => the variable's name
                    
            2. Write the declared variable somewhere (global, struct, etc.)
                | //! textmacro PASTE_MULTI_ARRAY_VARIABLE takes PREFIX, NAME
                        NAME    => the variable's name
                        PREFIX  => private, public, static, constant, etc.
                    
            3. Initialize the variable, should be placed at map init
                | //! textmacro INIT_MULTI_ARRAY_VARIABLE takes NAME
                        
            4. Returns max index you may use
                | method operator maxIndex takes nothing returns integer
                
            5. Returns dimension of the variable
                | method operator dimension takes nothing returns integer
                
            6. Returns the contained value based on assigned indexes.
                | method operator value takes nothing returns $TYPE$
                
                | method operator [] takes integer i returns thistype
                | method operator []= takes integer i, $TYPE$ param returns nothing
            
        
        V. Link
        ¯¯¯¯¯¯¯
            -
            
     ***************************************************************************************/
     
    //! textmacro INIT_MULTI_ARRAY_VARIABLE takes NAME
    set $NAME$ = MultiArray__$NAME$__s.create()
    //! endtextmacro
    
    // Write the variable declaration somewhere (globals, struct, etc.)
    //! textmacro PASTE_MULTI_ARRAY_VARIABLE takes PREFIX, NAME
    $PREFIX$ MultiArray__$NAME$__s $NAME$
    //! endtextmacro
     
    //! textmacro CREATE_MULTI_ARRAY takes TYPE, NAME, DIMENSION
    scope MultiArray$NAME$var
        
        globals
            // Allocated array size per container
            private constant integer ALLOCATED_SPACE = 0x63EAE
            // Max index for each dimension for the generated variable
            private constant integer MAX_INDEX = R2I(Pow(ALLOCATED_SPACE*10, 1./$DIMENSION$))
            // Containers
            private $TYPE$ array Container__1[ALLOCATED_SPACE]
            private $TYPE$ array Container__2[ALLOCATED_SPACE]
            private $TYPE$ array Container__3[ALLOCATED_SPACE]
            private $TYPE$ array Container__4[ALLOCATED_SPACE]
            private $TYPE$ array Container__5[ALLOCATED_SPACE]
            private $TYPE$ array Container__6[ALLOCATED_SPACE]
            private $TYPE$ array Container__7[ALLOCATED_SPACE]
            private $TYPE$ array Container__8[ALLOCATED_SPACE]
            private $TYPE$ array Container__9[ALLOCATED_SPACE]
            private $TYPE$ array Container__10[ALLOCATED_SPACE]
        endglobals
        
        struct MultiArray__$NAME$__s
        
            private static integer read = 0
            private static integer array indices
            
            // Check whether some conditions are valid or not
            private static method validateIndices takes string state returns boolean
            
                local integer i = 0
                
                // Check the size boundary
                loop
                    exitwhen i == $DIMENSION$
                    if indices[i] < 0 or indices[i] >= MAX_INDEX then
                        debug call BJDebugMsg("$Error occured :: NAME$ var (on " + state + ") :: index-" + I2S(i+1) + " is out of bound of 0 or " + I2S(MAX_INDEX-1) + " at " + I2S(indices[i]) + ".")
                        return false
                    endif
                    set i = i + 1
                endloop
                
                // If the assigned dimension is valid
                if read < $DIMENSION$ then
                    debug call BJDebugMsg("Error occured :: $NAME$ var (on " + state + ") :: index-" + I2S($DIMENSION$-($DIMENSION$-read-1)) + " couldn't be read.")
                    return false
                elseif read > $DIMENSION$ then
                    debug call BJDebugMsg("Error occured :: $NAME$ var (on " + state + ") :: assigned indexes oversized the dimension of " + I2S($DIMENSION$) + " at " + I2S(read) + ".")
                    return false
                endif
                
                return true
            endmethod
            
            // Returns address based on assigned indices (indexes)
            private static method getMemoryOffset takes nothing returns integer
            
                local integer i = 1
                local integer result = indices[0]
                
                loop
                    exitwhen i == $DIMENSION$
                    set result = result * MAX_INDEX + indices[i]
                    set i = i + 1
                endloop
                
                return result
            endmethod
            
            // Max index which user may use
            method operator maxIndex takes nothing returns integer
                return MAX_INDEX-1
            endmethod
            
            // Dimension of generated variable
            method operator dimension takes nothing returns integer
                return $DIMENSION$
            endmethod
            
            // Returns the current value of generated variable
            method operator value takes nothing returns $TYPE$
            
                local integer loc
                local integer offset
                local integer index
                local boolean crash
                
                if not validateIndices("read") then
                    set read = 0
                    // Generate thread crash if there is any problem with the indexes
                    if crash then
                    endif
                endif
                
                // Calculate the address
                set offset = getMemoryOffset()
                set loc = offset/ALLOCATED_SPACE
                set index = offset-loc*ALLOCATED_SPACE
                
                set read = 0
                // Locate the position of the address
                if loc == 0 then
                    return Container__1[index]
                elseif loc == 1 then
                    return Container__2[index]
                elseif loc == 2 then
                    return Container__3[index]
                elseif loc == 3 then
                    return Container__4[index]
                elseif loc == 4 then
                    return Container__5[index]
                elseif loc == 5 then
                    return Container__6[index]
                elseif loc == 6 then
                    return Container__7[index]
                elseif loc == 7 then
                    return Container__8[index]
                elseif loc == 8 then
                    return Container__9[index]
                elseif loc == 9 then
                    return Container__10[index]
                endif
                
                return null
            endmethod
            
            // Read indices (indexes)
            method operator [] takes integer i returns thistype
            
                set indices[read] = i
                set read = read + 1
                
                return this
            endmethod
            
            // Assign a value to the generated variable
            method operator []= takes integer i, $TYPE$ param returns nothing
            
                local integer loc
                local integer offset
                local integer index
                local boolean crash
                
                // Read the last index
                set indices[read] = i
                set read = read + 1
                
                if not validateIndices("write") then
                    set read = 0
                    // Generate thread crash if there is any problem with the indexes
                    if crash then
                    endif
                endif
                
                // Calculate the address
                set offset = getMemoryOffset()
                set loc = offset/ALLOCATED_SPACE
                set index = offset-loc*ALLOCATED_SPACE
                
                set read = 0
                // Locate the position of the address
                if loc == 0 then
                    set Container__1[index] = param
                elseif loc == 1 then
                    set Container__2[index] = param
                elseif loc == 2 then
                    set Container__3[index] = param
                elseif loc == 3 then
                    set Container__4[index] = param
                elseif loc == 4 then
                    set Container__5[index] = param
                elseif loc == 5 then
                    set Container__6[index] = param
                elseif loc == 6 then
                    set Container__7[index] = param
                elseif loc == 7 then
                    set Container__8[index] = param
                elseif loc == 8 then
                    set Container__9[index] = param
                elseif loc == 9 then
                    set Container__10[index] = param
                endif
                
            endmethod
            
        endstruct
        
    endscope
    //! endtextmacro
    
endscope

Demo

JASS:
library Demo initializer init

// 1. Declaring MultiArray as a global variable

    // Create a 3-dimensional array unit variable named "SomeUnit"
    //! runtextmacro CREATE_MULTI_ARRAY("unit", "SomeUnit", "3")
    
    globals
        // Paste the variable declaration
        //! runtextmacro PASTE_MULTI_ARRAY_VARIABLE("private", "SomeUnit")
        // The result:
        //      private unit SomeUnit
    endglobals
        
    private function periodic takes nothing returns nothing
        
        // To read the value contained inside the variable
        // you should attach ".value" as suffix
        if SomeUnit[1][2][3].value == null then
        endif
        
        // This is how you assign a value into the variable
        set SomeUnit[1][2][3] = CreateUnit(Player(0), 'hpea', 1, 1, 1)
        
        // Note that number of assigned indexes should be exactly
        // the same as the dimension. E.g. if you do
        
        // set SomeUnit[0][0] = CreateUnit(Player(0), 'hpea', 1, 1, 1)
        // (Only 2 assigned indexes, whereas SomeUnit has 3D array)
        
        // it will crash the thread. Turn debug mode on to help
        // you find and fix the problem
    
    endfunction
    
    private function init takes nothing returns nothing
    
        // Initialize the variable at map initialization (mandatory)
        //! runtextmacro INIT_MULTI_ARRAY_VARIABLE("SomeUnit")
        call BJDebugMsg("Max index for variable SomeUnit is " + I2S(SomeUnit.maxIndex))
        
        call TimerStart(CreateTimer(), 1, true, function periodic)
        
    endfunction
    
    
    
// 2. Declaring MultiArray as a struct member

    // Create all components of the variable
    // This is a 2-dimensional string array variable named "SomeString"
    //! runtextmacro CREATE_MULTI_ARRAY("string", "SomeString", "2")
    
    struct SomeStruct
    
        // Paste the declaration here
        //! runtextmacro PASTE_MULTI_ARRAY_VARIABLE("private static", "SomeString")
        // The result:
        //      private static string SomeString
        
        private static method onInit takes nothing returns nothing
            
            local string msg
            local integer i
            local integer j
            
            // Initialize the variable at map initialization (mandatory)
            // For struct member variable, the initialization should be
            // done at struct initialization (onInit method)
            //! runtextmacro INIT_MULTI_ARRAY_VARIABLE("SomeString")
            call BJDebugMsg("Max index for variable SomeString is " + I2S(SomeString.maxIndex))
            
            
            // Note that you can only use the variable after you initialize it
            set SomeString[0][0] = "Hello, "
            set SomeString[0][1] = "there! "
            set SomeString[0][2] = "Thanks "
            
            set SomeString[1][0] = "for "
            set SomeString[1][1] = "testing "
            set SomeString[1][2] = "this "
            
            set SomeString[2][0] = "snippet! "
            set SomeString[2][1] = "I hope "
            set SomeString[2][2] = "you enjoy :)"
            
            set msg = ""
            set i = 0
            loop
                exitwhen i > 2
                set j = 0
                loop
                    exitwhen j > 2
                    // Remember to attach ".value" to read the value
                    set msg = msg + SomeString[i][j].value
                    set j = j + 1
                endloop
                set i = i + 1
            endloop
            call BJDebugMsg(msg)
            
        endmethod
        
    endstruct
    
    struct Demo
        
    endstruct
endlibrary

Big thanks to Nestharus for helping me with max index formula ;)
 

Attachments

  • MultiArray.w3m
    65 KB · Views: 83
Last edited:
Level 23
Joined
Apr 16, 2012
Messages
4,041
JASS:
                if loc == 0 then
                    return Container__1[index]
                elseif loc == 1 then
                    return Container__2[index]
                elseif loc == 2 then
                    return Container__3[index]
                elseif loc == 3 then
                    return Container__4[index]
                elseif loc == 4 then
                    return Container__5[index]
                elseif loc == 5 then
                    return Container__6[index]
                elseif loc == 6 then
                    return Container__7[index]
                elseif loc == 7 then
                    return Container__8[index]
                elseif loc == 8 then
                    return Container__9[index]
                elseif loc == 9 then
                    return Container__10[index]
                endif
This could benefit from binary search

validateIndices takes string state, but it is not used anywhere in the whole function other than the BJDebugMsg.

All debug messages, as well as the crash should be debug-only.

Also BJDebugMsg is very inefficient
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
For War3 environment, you almost never will use 3 or more dimensions. Whatmore, the hashtables especially in form of Table should do the job just fine. In most cases, you can change your approach in such a way, that simple Table instance will be enough.

For 2D, Vector of mine, provides quite convenient api and is a wrapper of Table to eaze the process of implementation - everyone uses Table. Each vector type is implemented with a single code line :)

You generate quite a amount of variables there.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Just to inform, second thought, I disagree if this get gy-ed. This seemingly will become an extension to NewTable by Bribe eventually: a second form of TableArray. And if this issue stands true, this library will have another advantage compared to TableArray, other than fancy interface and multi-dimension feature.

However, in the next update I will try to add a feature that allows user to configure the size of each dimension, for better memory efficiency.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Dalvengyr,

That issue with TableArray which you pointed out is nonexistent. You never posted the code with the problem but when you "replaced it" with MultiArray you fixed the original problem in the first place. If TableArray had a flaw like that, it would be unusable. The only times you run into issues with TableArray is when you try to access a node which is out of bounds.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Guys, is there a way to shorten this part?
JASS:
                // Locate the position of the address
                if loc == 0 then
                    return Container__1[index]
                elseif loc == 1 then
                    return Container__2[index]
                elseif loc == 2 then
                    return Container__3[index]
                elseif loc == 3 then
                    return Container__4[index]
                elseif loc == 4 then
                    return Container__5[index]
                elseif loc == 5 then
                    return Container__6[index]
                elseif loc == 6 then
                    return Container__7[index]
                elseif loc == 7 then
                    return Container__8[index]
                elseif loc == 8 then
                    return Container__9[index]
                elseif loc == 9 then
                    return Container__10[index]
                endif
And I've never been sure about the speed of this thing. I requested to un-gy this because I used this on several maps and it works so fluently, thus I think it still has potential.
 
Top