• 🏆 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] ArrayList

Level 15
Joined
Nov 30, 2007
Messages
1,202
This is a Array List container using a hashtable for storage to allow for a virtually endless amount of lists. It contains normal list interface, stack interface and a few utility methods such as sort, contains, containsAll, indexOf, setRange, addRange, swap, etc.

The principle behind this library is that the API should be as accomodating as possible, and not minimalistic, so if you have any ideas feel free to post suggested changes and argue your case for a new feature.

JASS:
library ArrayList requires optional HashRecyler
/*
    Made by: Pinzu
 
    Version: 1.2
 
    This is a array list container utilizing a hashtable for storage, making it possible to allocate a virtually unlimited amount of lists.
    It contains regular list interface, stack interface, and a few utility methods such as sort, addMatching, removeMatching, copy, swap, etc.

    Credits:
        Bannar for his ListT library which inspired this.
            https://www.hiveworkshop.com/threads/containers-list-t.249011/
   
        Bribe for his Table library which the HashRecyler is based on.
            https://www.hiveworkshop.com/threads/snippet-new-table.188084/
       
        Overfrost for suggesting improvements.

*/

    // ******************************************************************************************************** \\
    //    API
    // ******************************************************************************************************** \\
 
    //! novjass
 
    struct $NAME$ArrayList extends array
 
        readonly static $TYPE$ pivot     // Pivot element during sort or comparison   
        readonly static $TYPE$ other     // Other element during sort
   
        // Allocates the list.
        static method create takes nothing returns thistype
 
        // Deallocates the list.
        method destroy takes nothing returns nothing
 
        //    Removes all elements from the list.
        method clear takes nothing returns nothing
   
        // Returns the number of elements in the list.
        method operator length takes nothing returns integer
   
        // Returns the element at the specified position.
        method operator[] takes integer index returns $TYPE$
   
        // Replaces the element at the specified position in the list with the specified element.
        method operator[]= takes integer index, $TYPE$ element returns nothing
   
        // Returns the value at the first position of the list
        method first takes nothing returns $TYPE$
   
        // Returns the value at the last position of the list
        method last takes nothing returns $TYPE$
 
        // Inserts the specified element at the specified position in the list.
        method add takes integer index, $TYPE$ element returns nothing

        // Removes the element at the specified position in the the list, returning the value.
        method remove takes integer index returns $TYPE$
   
        // Inserts the element to the last position of the list (stack interface).
        method push takes $TYPE$ value returns thistype
   
        // Removes the element at the last position of the list, returning the value (stack interface).
        method pop takes $TYPE$ value returns $TYPE$
   
        // Returns the index position of the first occurrence of the specified element in the list, or -1 if the list does not contain the element.
        method indexOf takes $TYPE$ value returns integer

        // Returns true if the index is storing any value (table interface).
        method has takes integer index returns boolean
   
        // Returns true if the list contains the specified element.
        method contains takes $TYPE$ value returns boolean
   
        // Returns true if the calling list contains all elements in the specified list.
        method containsAll takes thistype list returns boolean
   
        //Allocates a new list containing shallow copies of all elements held currently.
        method clone takes nothing returns thistype
   
        // Returns true if the list contains no elements.
        method isEmpty takes nothing returns boolean
   
        // Swaps position of 2 elements at the specified positions.
        method swap takes integer indexA, integer indexB returns nothing

        // Returns true if two lists are equal, same length and same structure.
        method equals takes thistype list returns boolean
   
        // Sorts the list after the provided condition (c). Note that the comparison must use the variables 'pivot' and 'other'.
        // Furthermore, only '>' or '<' are valid operators for the sorting to work. Example:
   
            private function SortUnitsByPlayer takes nothing returns boolean
                return GetPlayerId(GetOwningPlayer(UnitArrayList.pivot)) < GetPlayerId(GetOwningPlayer(UnitArrayList.other))
            endfunction
       
        method sort takes code c returns nothing
   
        // Reverses the order of all elements inside the list.
        method reverse takes nothing returns nothing
   
        // Changes the values of all assigned elements from the start position to the end of the range to the specified new value.
        method setRange takes integer start, integer range, $TYPE$ value returns nothing
   
        // Will perform a shallow copy of all elements from the specified source list to the calling list, with the given range. Starting
        // from the specified source position, inserting to the specified destination position.
        method addRange takes integer destPos, thistype src, integer srcPos, integer range returns thistype
   
    //! endnovjass

    // ******************************************************************************************************** \\
    //    Shared Resources
    // ******************************************************************************************************** \\
 
    globals
        private constant integer SIZE_INDEX = -2 // Should be less than -1 to not risk collision with anything else
        private trigger t = CreateTrigger()
        private integer up
        private integer down
    endglobals

static if not LIBRARY_HashRecyler then

    struct HashRecyler extends array
debug    readonly static integer counter    = 0
        readonly static hashtable ht = InitHashtable()

        static method operator[] takes integer k returns integer
            return LoadInteger(ht, -1, k)
        endmethod

        static method operator []= takes integer k, integer tb returns nothing
            call SaveInteger(ht, -1, k, tb)
        endmethod
   
        private static method onInit takes nothing returns nothing
            set thistype[0] = 1
        endmethod
   
        static method alloc takes nothing returns integer
            local integer k =  thistype[0]
            if (thistype[k] == 0) then
                set thistype[0] = k + 1
            else
                set thistype[0] = thistype[k]
            endif
debug         set counter = counter + 1
            return k
        endmethod
   
        static method free takes integer k returns nothing
            set thistype[k] = thistype[0]
            set thistype[0] = k
            call FlushChildHashtable(ht, k)
debug         set counter = counter - 1
        endmethod
    endstruct
endif

    // ******************************************************************************************************** \\
    //    Add any textmacro below of the data types you wish to support.
    //
    // Notice: The last boolean is for a bug fix related to nulling hashtable handles, it's not a necessity.
    // But it allows for nulling list storing handles like this:
    //
    // set units[0] = null or players[0] = null
    //
    // If you set it to false the above snippet will not work but set will be slightly more efficent.
    //
    // ******************************************************************************************************** \\
 
    //! runtextmacro ARRAY_LIST_DEFINE("Int",        "integer",    "0",        "Integer",        "SavedInteger")
    //! runtextmacro ARRAY_LIST_DEFINE("Real",        "real",        "0.",        "Real",            "SavedReal")
    //! runtextmacro ARRAY_LIST_DEFINE("Bool",        "boolean",    "false",    "Boolean",        "SavedBoolean")
    //! runtextmacro ARRAY_LIST_DEFINE("Str",        "string",    "null",        "Str",            "SavedString")
    //! runtextmacro ARRAY_LIST_DEFINE("Unit",        "unit",        "null",        "UnitHandle",    "SavedHandle")
    //! runtextmacro ARRAY_LIST_DEFINE("Player",    "player",    "null",        "PlayerHandle",    "SavedHandle")

    // ******************************************************************************************************** \\
    //    Array List
    // ******************************************************************************************************** \\
 
    //! textmacro_once ARRAY_LIST_DEFINE takes NAME, TYPE, NONE, VAR, HANDLE
 
    struct $NAME$ArrayList extends array
 
        readonly static $TYPE$ pivot         // Pivot element during sort or comparison   
        readonly static $TYPE$ other         // Other element during sort

        static method create takes nothing returns thistype
            return HashRecyler.alloc()
        endmethod
   
        method destroy takes nothing returns nothing
            call HashRecyler.free(this)
        endmethod
   
        private method operator length= takes integer index returns nothing
            call SaveInteger(HashRecyler.ht , this, SIZE_INDEX, index)
        endmethod

        method operator length takes nothing returns integer
            return LoadInteger(HashRecyler.ht , this, SIZE_INDEX)
        endmethod
   
        method clear takes nothing returns nothing
            call FlushChildHashtable(HashRecyler.ht , this)
        endmethod
   
        method first takes nothing returns $TYPE$
            return Load$VAR$(HashRecyler.ht , this, 0)
        endmethod
   
        method last takes nothing returns $TYPE$
            return Load$VAR$(HashRecyler.ht , this, .length - 1)
        endmethod
   
        method operator[] takes integer index returns $TYPE$
            return Load$VAR$(HashRecyler.ht , this, index)
        endmethod
   
        method operator[]= takes integer index, $TYPE$ element returns nothing
            call Save$VAR$(HashRecyler.ht , this, index, element)
        endmethod

        method add takes integer index, $TYPE$ element returns nothing
            local integer i = .length
            if index < 0 or index > i then
debug             call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "ERROR: $NAME$ArrayList(" + I2S(this) + ").add:IndexOutOfBounds (" + I2S(index) + ")")
                return
            endif
            set .length = i + 1
            loop
                exitwhen i == index
                call Save$VAR$(HashRecyler.ht , this, i, Load$VAR$(HashRecyler.ht , this, i - 1))
                set i = i - 1
            endloop
            call Save$VAR$(HashRecyler.ht , this, index, element)
        endmethod
   
        method remove takes integer index returns $TYPE$
            local integer len = .length - 1
            if index < 0 or index > len then
debug             call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "ERROR: $NAME$ArrayList(" + I2S(this) + ").remove:IndexOutOfBounds (" + I2S(index) + ")")
                return $NONE$
            endif
            set .length = len
            set other = Load$VAR$(HashRecyler.ht , this, index)
            loop
                exitwhen index == len
                call Save$VAR$(HashRecyler.ht , this, index, Load$VAR$(HashRecyler.ht , this, index + 1))
                set index = index + 1
            endloop
            call Remove$HANDLE$(HashRecyler.ht , this, len)
            return other
        endmethod
   
        method push takes $TYPE$ value returns thistype
            local integer len = .length
            call Save$VAR$(HashRecyler.ht , this, len, value)
            set .length = len + 1
            return this
        endmethod
   
        method pop takes $TYPE$ value returns $TYPE$
            local integer len = .length - 1
            if len < 0 then
                return $NONE$
            endif
            set .length = len
            set other = Load$VAR$(HashRecyler.ht , this, len)
            call Remove$HANDLE$(HashRecyler.ht , this, len)
            return other
        endmethod
   
        method indexOf takes $TYPE$ value returns integer
            local integer i = 0
            local integer len = .length
            loop
                exitwhen i == len
                if Load$VAR$(HashRecyler.ht , this, i) == value then
                    return i
                endif
                set i = i + 1
            endloop
            return -1
        endmethod
   
        method has takes integer index returns boolean
            return index >= 0 and index < .length
        endmethod
   
        method contains takes $TYPE$ value returns boolean
            return .indexOf(value) != -1
        endmethod
   
        method containsAll takes thistype list returns boolean
            local integer i = 0
            local integer n = list.length
            local integer j
            local integer m = .length
            loop
                exitwhen i == n
                set other = Load$VAR$(HashRecyler.ht , list, i)
                set j = 0
                loop
                    if j == m then
                        return false
                    endif
                    exitwhen other == Load$VAR$(HashRecyler.ht , this, j)
                    set j = j + 1
                endloop
                set i = i + 1
            endloop
            return true
        endmethod
   
        method isEmpty takes nothing returns boolean
            return .length == 0
        endmethod

        method swap takes integer indexA, integer indexB returns nothing
debug       if indexA < 0 or indexA >= .length or indexB < 0 or indexB >= .length then
debug             call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "ERROR: $NAME$ArrayList(" + I2S(this) + ").swap:IndexOutOfBounds")
debug           return
debug         endif
            set other = Load$VAR$(HashRecyler.ht , this, indexA)
            call Save$VAR$(HashRecyler.ht , this, indexA, Load$VAR$(HashRecyler.ht , this, indexB))
            call Save$VAR$(HashRecyler.ht , this, indexB, other)
        endmethod
   
        method clone takes nothing returns thistype
            return create().addRange(0, this, 0, .length)
        endmethod
   
        method equals takes thistype list returns boolean
            local integer i = .length
            if i == list.length then
                set i = i - 1
                loop
                    exitwhen i < 0
                    if Load$VAR$(HashRecyler.ht , this, i) != Load$VAR$(HashRecyler.ht , list, i) then
                        return false
                    endif
                    set i = i - 1
                endloop
                return true
            endif
            return false
        endmethod
   
        private method setupTrigger takes code c returns nothing
            call TriggerClearConditions(t)
            call TriggerAddCondition(t, Condition(c))
        endmethod
   
        private method quicksort takes integer first, integer last returns nothing
            if first < last then
                set up = first
                set down = last
                set pivot = Load$VAR$(HashRecyler.ht , this, first)
                loop
                    loop
                        set other = Load$VAR$(HashRecyler.ht , this, up)
                        exitwhen up >= last or TriggerEvaluate(t)
                        set up = up + 1
                    endloop
                    loop
                        set other = Load$VAR$(HashRecyler.ht , this, down)
                        exitwhen  not TriggerEvaluate(t)
                        set down = down - 1
                    endloop
                    exitwhen up >= down
                    set other = Load$VAR$(HashRecyler.ht , this, up)
                    call Save$VAR$(HashRecyler.ht , this, up, Load$VAR$(HashRecyler.ht , this, down))
                    call Save$VAR$(HashRecyler.ht , this, down, other)
                endloop
                set other = Load$VAR$(HashRecyler.ht, this, first)
                call Save$VAR$(HashRecyler.ht , this, first, Load$VAR$(HashRecyler.ht , this, down))
                call Save$VAR$(HashRecyler.ht , this, down, other)
                call .quicksort(first, down - 1)
                call .quicksort(down + 1, last) 
            endif
        endmethod

        method sort takes code c returns nothing
debug        if .length > 1400 then
debug             call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "WARNING: $NAME$ArrayList.sort: Attempting to sort " + I2S(.length) + " elements.")
debug        endif
            call .setupTrigger(c)
            call .quicksort(0, .length - 1)
        endmethod
   
        method reverseInner takes integer first, integer last returns nothing
            if first < last then
                set other = Load$VAR$(HashRecyler.ht , this, first)
                call Save$VAR$(HashRecyler.ht , this, first, Load$VAR$(HashRecyler.ht , this, last))
                call Save$VAR$(HashRecyler.ht , this, last, other)
                call .reverseInner(first + 1, last - 1)
            endif
        endmethod
   
        method reverse takes nothing returns nothing
            //call reverseInner(0, .length - 1)
            set up = 0
            set down = .length - 1
            loop
                exitwhen down <= up
                set other = Load$VAR$(HashRecyler.ht , this, up)
                call Save$VAR$(HashRecyler.ht , this, up, Load$VAR$(HashRecyler.ht , this, down))
                call Save$VAR$(HashRecyler.ht , this, down, other)
                set up = up + 1
                set down = down - 1
            endloop   
        endmethod
   
        method addRange takes integer destPos, thistype src, integer srcPos, integer range returns thistype
            local integer srcSize = src.length
            local integer len = .length
            local integer i = destPos
            if srcPos < 0 or srcPos == srcSize or destPos < 0 or destPos > len then
debug            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "ERROR: $NAME$ArrayList.copy:IndexOutOfBounds")
                return this
            endif
            if srcPos + range > srcSize then
                set range = srcSize - srcPos
            endif
            loop
                exitwhen i == len
                call Save$VAR$(HashRecyler.ht , this, i + range, Load$VAR$(HashRecyler.ht , this, i))    // make room
                set i = i + 1
            endloop
            set i = 0
            loop
                exitwhen i == range
                call Save$VAR$(HashRecyler.ht , this, destPos + i, Load$VAR$(HashRecyler.ht , src, srcPos + i))    // fill room with copies
                set i = i + 1
            endloop
            set .length =  len + range
            return this
        endmethod
   
        method setRange takes integer index, integer range, $TYPE$ value returns nothing
            local integer len = .length
debug        if index >= len then
debug             call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "ERROR: $NAME$ArrayList(" + I2S(this) + ").setRange:IndexOutOfBounds (" + I2S(index) + ")")
debug        endif
            set range = index + range
            loop
                exitwhen index == range or index >= len
                call Save$VAR$(HashRecyler.ht , this, index, value)
                set index = index + 1
            endloop
        endmethod
    endstruct
    //! endtextmacro
endlibrary



Sorting Units by unit id
upload_2019-2-7_15-44-4.png


Sorting players by score
upload_2019-2-7_15-50-41.png


How to:
JASS:
private function SortUnitsByPlayer takes nothing returns boolean
        return GetPlayerId(GetOwningPlayer(UnitArrayList.pivot)) < GetPlayerId(GetOwningPlayer(UnitArrayList.other))
    endfunction
 
    local UnitArrayList list = UnitArrayList.create()
    // ...
    call list.sort(function SortUnitsByPlayer())  // limit is around 1400 - 2000 depending on complexity, the bigger problem is the lagg that might be inccured when sorting larger sets.

JASS:
scope Test initializer Init


    globals
        private integer start
        private integer i
        private integer j = 0
        private integer MAX = 500
        private IntArrayList printable
        private IntArrayList largeList
        private UnitArrayList units
    endglobals
 
    private function PrintJob takes nothing returns nothing
        local string s = ""
        local integer size = printable.size()
        set start = i
        set j = 0
        loop
            exitwhen i == size or j == 400
            set s = s + I2S(printable[i]) + " "
            set j = j + 1
            set i = i + 1
        endloop
        call BJDebugMsg("Start: " + I2S(start) + "\n" + s)
        if i < size then
            call ExecuteFunc("Test__PrintJob")
        endif
    endfunction
 
    private function PrintList takes IntArrayList list returns nothing
        set i = 0
        set printable = list
        call ExecuteFunc("Test__PrintJob")
    endfunction
 

    private function ascendingOrder takes nothing returns boolean
        return IntArrayList.pivot < IntArrayList.other
    endfunction
 
    private function descendingOrder takes nothing returns boolean
        return IntArrayList.pivot > IntArrayList.other
    endfunction
 
    private function SortUnitsByPlayer takes nothing returns boolean
        return GetPlayerId(GetOwningPlayer(UnitArrayList.pivot)) < GetPlayerId(GetOwningPlayer(UnitArrayList.other))
    endfunction
 
    globals
        private integer array score
    endglobals
 
    private function SortPlayersByScore takes nothing returns boolean
        return score[GetPlayerId(PlayerArrayList.pivot)] > score[GetPlayerId(PlayerArrayList.other)]
    endfunction
 
    private function PlayerList takes nothing returns nothing
        local PlayerArrayList list = PlayerArrayList.create()
        local integer i = 0
        local integer length
        local player p
        loop
            exitwhen i == 12
            set score[i] = GetRandomInt(0, 100)    // just a random score
            call list.add(i, Player(i))
            set i = i + 1
        endloop
        call list.sort(function SortPlayersByScore)
        set i = 0
        set length = list.size()
        loop
            exitwhen i == length
            set p = list[i]
            call BJDebugMsg(GetPlayerName(p) + " has a score of " + I2S(score[GetPlayerId(p)]))
            set i = i + 1
        endloop
    endfunction
 
    function FormatUnits takes nothing returns nothing
        local integer i = 0
        local integer length = units.size()
        local unit u
        loop
            exitwhen i == length
            set u = units[i]
            call SetUnitX(u, i*10 - 3000)
            call SetUnitY(u, 0)
            set i = i + 1
        endloop
    endfunction
 
    function SortUnits takes nothing returns nothing
        call units.sort(function SortUnitsByPlayer)
        call BJDebugMsg("Units sorted")
        call ExecuteFunc("FormatUnits")
    endfunction
 
    private function UnitList takes nothing returns nothing
        local integer i = 0
        local integer length
        set units = UnitArrayList.create()
        loop
            exitwhen i == 1800
            call units.add(i, CreateUnit(Player(GetRandomInt(0, 24)), 'hpea', 0, 0, 0))
            set i = i + 1
        endloop
        call BJDebugMsg("Created " + I2S(units.size()) + " number of units.")
        call ExecuteFunc("SortUnits")
    endfunction

    private function RemoveMatchingFilter takes nothing returns boolean
        return IntArrayList.pivot >= 30 and IntArrayList.pivot <= 60
    endfunction
 
 
    private function CreateList takes integer size returns IntArrayList
        local integer k = 0
        local IntArrayList list = IntArrayList.create()
        loop
            exitwhen k == size
            call list.add(k, GetRandomInt(0, 200))
            set k = k + 1
        endloop
        return list
    endfunction
 
    function SortLargeList takes nothing returns nothing
        call largeList.sort(function ascendingOrder)
        call PrintList(largeList)
    endfunction
 
    private function Main takes nothing returns nothing
    /*
        local integer i = 0
        local IntArrayList list = IntArrayList.create()
        local IntArrayList list2 = IntArrayList.create()
        local IntArrayList tempList
        loop
            exitwhen i == 100
            //call list.add(i, GetRandomInt(0, 100))
            call list.add(i, i)
            set i = i + 1
        endloop
        //call list.sort(function descendingOrder)
        call PrintList(list)
 
        // This will copy the first 50 elements from list to list2 insertion starting at 0
        // The old list will however retain it's elements.
        call IntArrayList.copy(list, 0, list2, 0, list.size())
        call PrintList(list2)
        call BJDebugMsg("size: " + I2S(list2.size()))
 
        call BJDebugMsg("size: " + I2S(list.size()))
 
        call list2.removeMatching(function RemoveMatchingFilter)
        call PrintList(list2)
 
        set tempList = IntArrayList.tempList
        set i = 0
        loop
            exitwhen i == tempList.size()
            call BJDebugMsg(I2S(i) + ": " + I2S(tempList[i]) + " was removed")
            set i = i + 1
        endloop
 
        //call PlayerList()
 
        */
        call UnitList()
        //set largeList = CreateList(3200)
        //call PrintList(largeList)
        //call ExecuteFunc("SortLargeList")
    endfunction
 
    private function DeleteUnit takes nothing returns nothing
        local integer rdm
        if units.size() > 0 then
            set rdm = GetRandomInt(0, units.size() - 1)
            call RemoveUnit(units.remove(rdm))
            call BJDebugMsg("Remaining: " + I2S(units.size()) + " -- " + I2S(rdm))
        else
            call PauseTimer(GetExpiredTimer())
            call BJDebugMsg("Complete removal")
        endif
    endfunction
 
    private function Init takes nothing returns nothing
        call TimerStart(CreateTimer(), 0.2, false, function Main)
        call TimerStart(CreateTimer(), 0.01, true, function DeleteUnit)
    endfunction
endscope

It's also used here: Spell Menu System 1.2c


I've now included has(index) as a method which makes it technically possible to replace a table with this provided that the data is ordered.[/HIDDEN]


JASS:
method drop takes thistype evicted, code c returns nothing
            local integer i = .length - 1
            local integer j
            local integer len
            call .setupTrigger(c)
            loop
                exitwhen i < 0
                set pivot = Load$VAR$(ht, this, i)
                if TriggerEvaluate(t) then
                    if evicted != 0 then
                        call evicted.push(pivot)
                    endif
                    set j = i
                    set len = .length - 1
                    set .length = len
                    loop
                        exitwhen j == len
                        call Save$VAR$(ht, this, j, Load$VAR$(ht, this, j + 1))
                        set j = j + 1
                    endloop
                    call Remove$HANDLE$(ht, this, len)
                endif
                set i = i - 1
            endloop
        endmethod
 
        method unique takes thistype evicted returns nothing
            local thistype temp
            local integer i
            set temp = $NAME$ArrayList.create()
            set i = .length - 1
            loop
                exitwhen i < 0
                if temp.contains(this[i]) then
                    if evicted != 0 then
                        call evicted.push(this[i])
                    endif
                    call .remove(i)
                else
                    call temp.push(this[i])
                endif
                set i = i - 1
            endloop
            call temp.destroy()
        endmethod

private static method onAddMatching takes nothing returns nothing
            call tempList.push(pivot)
        endmethod
 
        private static method onSetMatching takes nothing returns nothing
            call Save$VAR$(ht, list, i, other)
        endmethod
 
        private static method onCountMatching takes nothing returns nothing
            set up = up + 1
        endmethod
 
        private static method onRemoveMatching takes nothing returns nothing
            local thistype this = list
            if tempList != 0 then
                call tempList.push(this.remove(i))
            else
                call this.remove(i)
            endif
        endmethod
 
        method forEach takes integer start, integer end, integer change, code c, code a returns nothing
debug         if change == 0 or (start > end and change > 0) or (start < end and change < 0) then
debug            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "ERROR: $NAME$ArrayList(" + I2S(this) + ").forEach:InfiniteLoopDetected")
debug            return
debug        endif
            call .setupTrigger(c, a)
            set i = start
            loop
                exitwhen i == end
                set pivot = Load$VAR$(ht, this, i)
                if TriggerEvaluate(t) then
                    call TriggerExecute(t)
                endif
                set i = i + change
            endloop
        endmethod
 
        method removeMatching takes thistype dest, code c returns thistype
            set tempList = dest
            call .forEach(.length - 1, -1, -1, c, function thistype.onRemoveMatching)
            return dest
        endmethod
 
        method addMatching takes thistype srcs, code c returns nothing
            set tempList = this
            call srcs.forEach(0, srcs.length, 1, c, function thistype.onAddMatching)
        endmethod
 
        method setMatching takes $TYPE$ value, code c returns nothing
            set other = value
            call .forEach(0, .length, 1, c, function thistype.onSetMatching)
        endmethod
 
        method countMatching takes code c returns integer
            set up = 0
            call .forEach(0, .length, 1, c, function thistype.onCountMatching)
            return up
        endmethod


This is a table version of the same library, I've decided to include it should any one prefer that. Note that my tests on the latest patch apperently doesn't inline methods properly so Table severly impeds the performance. And I'm not sure exactly why this is happening. Maybe it's a a problem on my end, and if that is the case this version is perfectly fine.
JASS:
library ArrayList requires Table
/*
    Made by: Pinzu
 
    Version: 1.2
 
    This is a array list container utilizing a hashtable for storage, making it possible to allocate a virtually unlimited amount of lists.
    It contains regular list interface, stack interface, and a few utility methods such as sort, addMatching, removeMatching, copy, swap, etc.

    Credits:
        Bannar for his ListT library which inspired this.
            https://www.hiveworkshop.com/threads/containers-list-t.249011/
    
        Bribe for his Table library.
            https://www.hiveworkshop.com/threads/snippet-new-table.188084/
        
        Overfrost for suggesting improvements.

*/

    // ******************************************************************************************************** \\
    //    API
    // ******************************************************************************************************** \\
 
    //! novjass
 
    struct $NAME$ArrayList extends array
 
        readonly static $TYPE$ pivot     // Pivot element during sort or comparison    
        readonly static $TYPE$ other     // Other element during sort
    
        // Allocates the list.
        static method create takes nothing returns thistype
 
        // Deallocates the list.
        method destroy takes nothing returns nothing
 
        //    Removes all elements from the list.
        method clear takes nothing returns nothing
    
        // Returns the number of elements in the list.
        method operator length takes nothing returns integer
    
        // Returns the element at the specified position.
        method operator[] takes integer index returns $TYPE$
    
        // Replaces the element at the specified position in the list with the specified element.
        method operator[]= takes integer index, $TYPE$ element returns nothing
    
        // Returns the value at the first position of the list
        method first takes nothing returns $TYPE$
    
        // Returns the value at the last position of the list
        method last takes nothing returns $TYPE$
 
        // Inserts the specified element at the specified position in the list.
        method add takes integer index, $TYPE$ element returns nothing

        // Removes the element at the specified position in the the list, returning the value.
        method remove takes integer index returns $TYPE$
    
        // Inserts the element to the last position of the list (stack interface).
        method push takes $TYPE$ value returns thistype
    
        // Removes the element at the last position of the list, returning the value (stack interface).
        method pop takes $TYPE$ value returns $TYPE$
    
        // Returns the index position of the first occurrence of the specified element in the list, or -1 if the list does not contain the element.
        method indexOf takes $TYPE$ value returns integer

        // Returns true if the index is storing any value (table interface).
        method has takes integer index returns boolean
    
        // Returns true if the list contains the specified element.
        method contains takes $TYPE$ value returns boolean
    
        // Returns true if the calling list contains all elements in the specified list.
        method containsAll takes thistype list returns boolean
    
        //Allocates a new list containing shallow copies of all elements held currently.
        method clone takes nothing returns thistype
    
        // Returns true if the list contains no elements.
        method isEmpty takes nothing returns boolean
    
        // Swaps position of 2 elements at the specified positions.
        method swap takes integer indexA, integer indexB returns nothing

        // Returns true if two lists are equal, same length and same structure.
        method equals takes thistype list returns boolean
    
        // Sorts the list after the provided condition (c). Note that the comparison must use the variables 'pivot' and 'other'.
        // Furthermore, only '>' or '<' are valid operators for the sorting to work. Example:
    
            private function SortUnitsByPlayer takes nothing returns boolean
                return GetPlayerId(GetOwningPlayer(UnitArrayList.pivot)) < GetPlayerId(GetOwningPlayer(UnitArrayList.other))
            endfunction
        
        method sort takes code c returns nothing
    
        // Reverses the order of all elements inside the list.
        method reverse takes nothing returns nothing
    
        // Changes the values of all assigned elements from the start position to the end of the range to the specified new value.
        method setRange takes integer start, integer range, $TYPE$ value returns nothing
    
        // Will perform a shallow copy of all elements from the specified source list to the calling list, with the given range. Starting
        // from the specified source position, inserting to the specified destination position.
        method addRange takes integer destPos, thistype src, integer srcPos, integer range returns thistype
    
    //! endnovjass

    // ******************************************************************************************************** \\
    //    Shared Resources
    // ******************************************************************************************************** \\
 
    globals
        private constant integer SIZE_INDEX = -2 // Should be less than -1 to not risk collision with anything else
        private trigger t = CreateTrigger()
        private integer up
        private integer down
    endglobals

    // ******************************************************************************************************** \\
    //    Add any textmacro below of the data types you wish to support
    //
    // ******************************************************************************************************** \\
 
    //! runtextmacro ARRAY_LIST_DEFINE("Int",        "integer",    "0",        "Integer",        "SavedInteger")
    //! runtextmacro ARRAY_LIST_DEFINE("Real",        "real",        "0.",        "Real",            "SavedReal")
    //! runtextmacro ARRAY_LIST_DEFINE("Bool",        "boolean",    "false",    "Boolean",        "SavedBoolean")
    //! runtextmacro ARRAY_LIST_DEFINE("Str",        "string",    "null",        "Str",            "SavedString")
    //! runtextmacro ARRAY_LIST_DEFINE("Unit",        "unit",        "null",        "UnitHandle",    "SavedHandle")
    //! runtextmacro ARRAY_LIST_DEFINE("Player",    "player",    "null",        "PlayerHandle",    "SavedHandle")

    // ******************************************************************************************************** \\
    //    Array List
    // ******************************************************************************************************** \\
 
    //! textmacro_once ARRAY_LIST_DEFINE takes NAME, TYPE, NONE, VAR, HANDLE
 
    struct $NAME$ArrayList extends array
 
        readonly static $TYPE$ pivot         // Pivot element during sort or comparison    
        readonly static $TYPE$ other         // Other element during sort

        static method create takes nothing returns thistype
            return Table.create()
        endmethod
    
        method destroy takes nothing returns nothing
            call Table(this).destroy()
        endmethod
    
        private method operator length= takes integer index returns nothing
            set Table(this).integer[SIZE_INDEX] = index
        endmethod

        method operator length takes nothing returns integer
            return Table(this).integer[SIZE_INDEX]
        endmethod
    
        method clear takes nothing returns nothing
            call Table(this).flush()
        endmethod
    
        method first takes nothing returns $TYPE$
            return Table(this).$TYPE$[0]
        endmethod
    
        method last takes nothing returns $TYPE$
            return Table(this).$TYPE$[.length - 1]
        endmethod
    
        method operator[] takes integer index returns $TYPE$
            return Table(this).$TYPE$[index]
        endmethod
    
        method operator[]= takes integer index, $TYPE$ element returns nothing
            set Table(this).$TYPE$[index] = element
        endmethod

        method add takes integer index, $TYPE$ element returns nothing
            local integer i = .length
            if index < 0 or index > i then
debug             call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "ERROR: $NAME$ArrayList(" + I2S(this) + ").add:IndexOutOfBounds (" + I2S(index) + ")")
                return
            endif
            set .length = i + 1
            loop
                exitwhen i == index
                set Table(this).$TYPE$[i] = Table(this).$TYPE$[i - 1]
                set i = i - 1
            endloop
            set Table(this).$TYPE$[index] = element
        endmethod
    
        method remove takes integer index returns $TYPE$
            local integer len = .length - 1
            if index < 0 or index > len then
debug             call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "ERROR: $NAME$ArrayList(" + I2S(this) + ").remove:IndexOutOfBounds (" + I2S(index) + ")")
                return $NONE$
            endif
            set .length = len
            set other = Table(this).$TYPE$[index]
            loop
                exitwhen index == len
                set Table(this).$TYPE$[index] = Table(this).$TYPE$[index + 1]
                set index = index + 1
            endloop
            call Table(this).$TYPE$.remove(len)
            return other
        endmethod
    
        method push takes $TYPE$ value returns thistype
            local integer len = .length
            set Table(this).$TYPE$[len] = value
            set .length = len + 1
            return this
        endmethod
    
        method pop takes $TYPE$ value returns $TYPE$
            local integer len = .length - 1
            if len < 0 then
                return $NONE$
            endif
            set .length = len
            set other = Table(this).$TYPE$[len]
            call Table(this).$TYPE$.remove(len)
            return other
        endmethod
    
        method indexOf takes $TYPE$ value returns integer
            local integer i = 0
            local integer len = .length
            loop
                exitwhen i == len
                if Table(this).$TYPE$[i] == value then
                    return i
                endif
                set i = i + 1
            endloop
            return -1
        endmethod
    
        method has takes integer index returns boolean
            return index >= 0 and index < .length
        endmethod
    
        method contains takes $TYPE$ value returns boolean
            return .indexOf(value) != -1
        endmethod
    
        method containsAll takes thistype list returns boolean
            local integer i = 0
            local integer n = list.length
            local integer j
            local integer m = .length
            loop
                exitwhen i == n
                set other = Table(this).$TYPE$[i]
                set j = 0
                loop
                    if j == m then
                        return false
                    endif
                    exitwhen other == Table(this).$TYPE$[j]
                    set j = j + 1
                endloop
                set i = i + 1
            endloop
            return true
        endmethod 
    
        method isEmpty takes nothing returns boolean
            return .length == 0
        endmethod

        method swap takes integer indexA, integer indexB returns nothing
debug       if indexA < 0 or indexA >= .length or indexB < 0 or indexB >= .length then
debug             call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "ERROR: $NAME$ArrayList(" + I2S(this) + ").swap:IndexOutOfBounds")
debug           return
debug         endif
            set other = Table(this).$TYPE$[indexA]
            set Table(this).$TYPE$[indexA] = Table(this).$TYPE$[indexB]
            set Table(this).$TYPE$[indexB] = other
        endmethod
    
        method clone takes nothing returns thistype
            return create().addRange(0, this, 0, .length)
        endmethod
    
        method equals takes thistype list returns boolean
            local integer i = .length
            if i == list.length then
                set i = i - 1
                loop
                    exitwhen i < 0
                    if Table(this).$TYPE$[i] != Table(list).$TYPE$[i] then
                        return false
                    endif
                    set i = i - 1
                endloop
                return true
            endif
            return false
        endmethod
    
        private method setupTrigger takes code c returns nothing
            call TriggerClearConditions(t)
            call TriggerAddCondition(t, Condition(c))
        endmethod
    
        private method quicksort takes integer first, integer last returns nothing
            if first < last then
                set up = first
                set down = last
                set pivot = this[first]
                loop
                    loop
                        set other = this[up]
                        exitwhen up >= last or TriggerEvaluate(t)
                        set up = up + 1
                    endloop
                    loop
                        set other = this[down]
                        exitwhen  not TriggerEvaluate(t)
                        set down = down - 1
                    endloop
                    exitwhen up >= down
                    set other = this[up]
                    set this[up] = this[down]
                    set this[down] = other
                endloop
                set other = this[first]
                set this[first] = this[down]
                set this[down] = other
                call .quicksort(first, down - 1)
                call .quicksort(down + 1, last)  
            endif
        endmethod

        method sort takes code c returns nothing
debug        if .length > 1400 then
debug             call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "WARNING: $NAME$ArrayList.sort: Attempting to sort " + I2S(.length) + " elements.")
debug        endif
            call .setupTrigger(c)
            call .quicksort(0, .length - 1)
        endmethod
    
        private method reverseInner takes integer first, integer last returns nothing
            if first < last then
                set other = this[first]
                set this[first] = this[last]
                set this[last] = other
                call .reverseInner(first + 1, last - 1)
            endif
        endmethod
    
        method reverse takes nothing returns nothing
            //call reverseInner(0, .length)
            set up = 0
            set down = .length - 1
            loop
                exitwhen down <= up
                set other = this[up]
                set this[up] = this[down]
                set this[down] = other
                set up = up + 1
                set down = down - 1
            endloop
        endmethod
    
        method addRange takes integer destPos, thistype src, integer srcPos, integer range returns thistype
            local integer srcSize = src.length
            local integer len = .length
            local integer i = destPos
            if srcPos < 0 or srcPos == srcSize or destPos < 0 or destPos > len then
debug            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "ERROR: $NAME$ArrayList.copy:IndexOutOfBounds")
                return this
            endif
            if srcPos + range > srcSize then
                set range = srcSize - srcPos
            endif
            loop
                exitwhen i == len
                set Table(this).$TYPE$[i + range] = Table(this).$TYPE$[i]    // Make room
                set i = i + 1
            endloop
            set i = 0
            loop
                exitwhen i == range
                set Table(this).$TYPE$[destPos + i] = Table(this).$TYPE$[srcPos + i]    // fill room with copies
                set i = i + 1
            endloop
            set .length =  len + range
            return this
        endmethod
    
        method setRange takes integer index, integer range, $TYPE$ value returns nothing
            local integer len = .length
debug        if index >= len then
debug             call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "ERROR: $NAME$ArrayList(" + I2S(this) + ").setRange:IndexOutOfBounds (" + I2S(index) + ")")
debug        endif
            set range = index + range
            loop
                exitwhen index == range or index >= len
                set Table(this).$TYPE$[index] = value
                set index = index + 1
            endloop
        endmethod
    endstruct
    //! endtextmacro
endlibrary
 
Last edited:
Level 15
Joined
Nov 30, 2007
Messages
1,202
The sort is somewhat slow and limited as it performs 2 trigger evaluations. I'm not sure if I can optimize it further.

One idea would be to allow the user to create a rank value for the elements in the table, but I'm not sure that this would work that well, so perhaps the trade off between being able to sort on any condition is worth the loss in performance.


The only other thing impeding performance is the use of Table instead of having its own hashtable.

Edit: I have recoded it using hashtable instead of Table, I think it makes sense given that its a data structure and performance should be factored in. I created a HashRecyler library so the hashtable could be reused in other places if the method I've implemented is logically sound.

In the end i managed to sort 3000 integers and 1800 units by player id before hitting the OP limit, which I'll consider as a success (it was only a fraction of this before I optimized it).
 
Last edited:
Level 15
Joined
Nov 30, 2007
Messages
1,202
What advantages does this have over Bannar's?

More API and doesn't use Table method wrappers so should have a slight performance edge. I suggested changes to Bannar but they were not approved so this could be seen as the alternative with more features out of the box. I'm gonna stick with it not using Table for now.

The philosophy I'm adopting for this is that it should do as much as could possibly be desired out of the box and to that end I'm open for suggestions on what to include. Bannar's version has a different design approach and is more light weight (though has slightly more execution overhead).

I don't see why these two libraries are in conflict, it's simply a matter or preference.

Updated the code to 1.1.

The most noteworthy features of this is that you can provide conditions to sort anything, which should work well for smaller lists, larger lists should perhaps have their own dedicated sorting algorithm to reduce the overhead derived from executing TriggerEvaluate.

There are also other utility methods in the API which I haven't mentioned here.
 
Last edited:
If you could show a couple use cases where your library does something better that would help.

I also feel it could have a better library name than PArrayList. Maybe ListInterface or ArrayList.

And I noticed you are checking if the trigger member is null before creating it in setupTrigger. Couldn't you just create the trigger in the .create() method? I don't see anywhere in the code where the trigger would become null after initializing it.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
If you could show a couple use cases where your library does something better that would help.

I also feel it could have a better library name than PArrayList. Maybe ListInterface or ArrayList.

And I noticed you are checking if the trigger member is null before creating it in setupTrigger. Couldn't you just create the trigger in the .create() method? I don't see anywhere in the code where the trigger would become null after initializing it.

- ArrayList is fine for me if it's not taken.
- The reason for the check for null is for the cases when it's never used, I figured the added if is not so bad. Remeber that a user might have multiple list types and some may never use any of these features: sort, addMatching or removeMatching (currently).

Here are some examples which I haven't checked if it compiles, tested examples can be found in the opening post.

JASS:
function StackUsage takes nothing returns nothing
    local IntArrayList stack = IntArrayList.create()
    local integer returnValue
    call stack.push(5).push(3).push(2)            // faster than add(i, 5)
    set returnValue = stack.pop()                // == 2
    set returnValue = stack.peek()                 // == 3
    set returnValue = stack.pop()                 // == 3
endfunction

function SortUnit takes nothing returns boolean        // valid operators are "<" and ">"
    return GetPlayerId(GetOwningPlayer(UnitArrayList.pivot)) < GetPlayerId(GetOwningPlayer(UnitArrayList.other))
endfunction

function SortingUnits takes nothing returns nothing
    local UnitArrayList units  = UnitArrayList.create()
    local integer i = 0
    local unit u
    loop
        exitwhen i == 100
        call units.add(i, CreateUnit(Player(GetRandomInt(0, 23)), 'hpea' 0, 0, 0) // Creates a unit and grants it to a random player
        set i = i + 1
    endloop
    call units.sort(function SortUnit)    // Units will now be sorted by player id in ascending order
    set i = 0
    loop
        exitwhen i == units.length
        set u = units[i]
        call SetUnitX(u, 20*i - 1000)
        call SetUnitY(u, 0)
        set i = i + 1
    endloop
endfunction

function RemoveCondition takes nothing returns boolean
    return IntArrayList.pivot > 30 and IntArrayList.pivot < 60
endfunction

function AddCondition takes nothing returns nothing
    return IntArrayList.pivot > 50
endfunction

function RemoveMatching takes nothing returns nothing
    local IntArrayList list = IntArrayList.create()
    local IntArrayList removed                            // Will hold the removed elements
    local integer i = 0
    loop
        exitwhen i == 100
        call list.push(i)
        set i = i + 1
    endloop
    set removed = list.removeMatching(IntArrayList.create(), function RemoveCondition)    // remove all elements between 30 and 60 in this case
    call list.addMatching(removed, function AddCondition) // we append the removed elements that meet the condition to the list.
    call removed.destroy()
endfunction

oh and a more basic case: list.indexOf(value) every list should have this and list.contains(value) in my oppinion.

Is completely hashtable based so should be able to exceed array limitations that regular struct allocation uses, the one I reference uses a table but has it as a member variable which removes this advantage.

I'm sure there are more things to commet on (I looked at this: [vJASS] - [Containers] Vector<T>)

Note that I'm not comparing this to ListT as that is a LinkedList and doesn't have random access which makes it a different beast entirely though I think there is room for API improvement on that library as well as I have indicated.

_______________________________________________________

Version 1.2
- Added: method setMatching takes $TYPE$ value, code c returns nothing Changes value of all elements matching the provided condition.
- Added: method countMatching takes code c returns integer Returns the number matching occurances within the list, can be used to count number of instances of a given value or more complex conditions.
- Added: method clone takes nothing returns thistype Returns a new list containing all elements of the calling list.
- Added: method reverse takes nothing returns nothing Reverses the order of the list.
- Added: method containsAll takes thistype list returns boolean- Changed: copy now also returns the destination list.
- Changed: Removed some of the static variables from the textmacro so that they are shared across all list types, removing the need for multiple triggers.
- Changed: Added a forEach method which was used internally to I think reduce boiler plate code, though slowing it down instead.

I'd appreciate input on the aggregate methods: setMatching, addMatching and removeMatching, Should I make them more flexible by allowing the user to specify which index or leave it as is?
 
Last edited:
Level 6
Joined
Jan 9, 2019
Messages
102
Just go through the comments patiently mate!
JASS:
		//-------------
		// API Design
		//---------

		// - this is fine, but since Table loves to use [] operator, make this use that too
		method indexOf takes $TYPE$ value returns integer
		/* -> */ method operator index[] takes $TYPE$ value returns integer
		
        // - this should not be a method, for a method its name should be getSize
        method size takes nothing returns integer
		/* -> */  method operator size takes nothing returns integer

		// - this needs better explanation
		// - make it takes boolexpr as argument instead of code, so that the user can opt to use 1 stored boolexpr instead of repeated boolexpr conversion
		// - line number 2 is incorrect, the user can pass anything as long as it's a boolean
		// - explains what happens when the filter returns true/false
		//
        // Sorts the list after the provided condition (c). Note that the comparison must use the variables 'pivot' and 'other' in the comparison. 
        // Furthermore, only '>' or '<' are valid operators.
        method sort takes code c returns nothing

		// - this has to be non-static
		// - my suggestion makes (this) as the destination
        static method copy takes thistype src, integer srcPos, thistype dest, integer destPos, integer range returns thistype
		/* -> */ method addCopy takes integer index, thistype source, integer sourceStartIndex, integer length returns thistype(this)

		// - see my review on its code for this
		method clone takes nothing returns thistype

		//-------------------
		// Unnecessary APIs
		//---------------
		
		// - manual is enough, these are unneeded
        method removeMatching takes thistype dest, code c returns thistype
        method addMatching takes thistype srcs, code c returns nothing 
        method setMatching takes $TYPE$ value, code c returns nothing 
        method countMatching takes code c returns integer

        // - will anyone else ever need this?
        method containsAll takes thistype list returns boolean


//---------
// Script
//-----

// - just don't make the required library optional
// - there's no harm in making a lib requires another, it's a feature meant to be used
// - however, if you want to keep this, make the HashRecycler you provide here private
//   because this in public means that you're "advertising" or "force-including" another lib (to me at least)
static if not LIBRARY_HashRecyler then
    struct HashRecyler extends array 
        readonly static hashtable ht = InitHashtable()
debug    readonly static integer counter = 0
       
        private static method operator[] takes integer k returns integer
            return LoadInteger(ht, -1, k)
        endmethod

        private static method operator []= takes integer k, integer tb returns nothing
            call SaveInteger(ht, -1, k, tb)
        endmethod

        private static method onInit takes nothing returns nothing
            set thistype[0] = 1
        endmethod

        static method alloc takes nothing returns integer
            local integer k =  thistype[0]
            if (thistype[k] == 0) then
                set thistype[0] = k + 1
            else
                set thistype[0] = thistype[k]
            endif
debug         set counter = counter + 1
            return k
        endmethod

        static method free takes integer k returns nothing
            set thistype[k] = thistype[0]
            set thistype[0] = k
            call FlushChildHashtable(ht, k)
debug         set counter = counter - 1
        endmethod
    endstruct
endif

	// - use globals instead, that's it, won't make unnecessary extra variables nor make any difference at all
    private struct v extends array
        static integer i
        static hashtable ht = HashRecyler.ht  // - this one line is a lazy line mate, highly unneeded
        static integer up 
        static integer down 
        static integer len 
        static trigger t = CreateTrigger()
    endstruct

	// - move this waaaay up, close to the documentation, and state this there as well
    //! runtextmacro DEFINE_ARRAY_LIST("Int",        "integer",    "0",        "Integer",        "SavedInteger") 
    //! runtextmacro DEFINE_ARRAY_LIST("Real",        "real",        "0.",        "Real",            "SavedReal") 
    //! runtextmacro DEFINE_ARRAY_LIST("Bool",        "boolean",    "false",    "Boolean",        "SavedBoolean") 
    //! runtextmacro DEFINE_ARRAY_LIST("Str",        "string",    "null",        "Str",            "SavedString") 
    //! runtextmacro DEFINE_ARRAY_LIST("Unit",        "unit",        "null",        "UnitHandle",    "SavedHandle") 
    //! runtextmacro DEFINE_ARRAY_LIST("Player",    "player",    "null",        "PlayerHandle",    "SavedHandle")

	// - forgot to make this private eh?
	// - or is this meant to be a ghost API?
    function GetCurArrayListIndex takes nothing returns integer 
        return v.i
    endfunction

	// - this naming is improper, more proper naming system to use is [SCOPE]_[MEMBER]
	// - name -> ARRAY_LIST_DEFINE
	// - this API is much better in conjunction with Table, and like I said somewhere else, 
    //! textmacro_once DEFINE_ARRAY_LIST takes NAME, TYPE, NONE, VAR, REMOVE
   
    struct $NAME$ArrayList extends array

		// - personally, I prefer removing the ifs to make these inline, though it's fine either way
		method operator[] takes integer index returns $TYPE$
            if (index < 0 or index >= .length) then
debug             call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "ERROR: $NAME$ArrayList.get:IndexOutOfBounds")
                return $NONE$
            endif
            return Load$VAR$(v.ht, this, index)
        endmethod
        method operator[]= takes integer index, $TYPE$ element returns nothing
            if (index < 0 or index >= .length) then
debug             call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "ERROR: $NAME$ArrayList.set:IndexOutOfBounds")
                return
            endif
            call Save$VAR$(v.ht, this, index, element)
        endmethod

		// - marked lines are redundant
        method add takes integer index, $TYPE$ element returns nothing
            local integer i = .length
            if (index < 0 or index > i) then
debug             call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "ERROR: $NAME$ArrayList.add:IndexOutOfBounds")
                return
            endif
        //--set v.len = i
            loop
                exitwhen i == index
                call Save$VAR$(v.ht, this, i, Load$VAR$(v.ht, this, i - 1))
                set i = i - 1
            endloop
            call Save$VAR$(v.ht, this, index, element)
        //--set .length = v.len + 1
        endmethod
       
	    // - marked lines are redundant
        method push takes $TYPE$ value returns thistype 
        //--set v.len = .length
        //--call Save$VAR$(v.ht, this, v.len, value)
        //--set v.len = v.len + 1
        //--set .length = v.len
            return this
        endmethod 
       
        method pop takes $TYPE$ value returns $TYPE$
            set v.len = .length
            if .length == 0 then  // - what is this? .length is called again despite the fact that it was called into a variable
                return $NONE$ 
            endif
            set v.len = v.len - 1
            set .length = v.len
            set other = Load$VAR$(v.ht, this, v.len)
            call Remove$REMOVE$(v.ht, this, v.len)
            return other
        endmethod
       
	    // - merge this into .sort directly
/*      private method setupTrigger takes code c returns nothing 
            call TriggerClearConditions(v.t)   
            call TriggerAddCondition(v.t, Condition(c))
        endmethod */

		// - deemed unnecessary, see review on docs
/*      method removeMatching takes thistype dest, code c returns thistype 
            set v.i = .length - 1
            if v.i >= 0 then 
                call .setupTrigger(c)
                loop 
                    exitwhen v. < 0 
                    set pivot = Load$VAR$(v.ht, this, v.i)
                    if TriggerEvaluate(v.t) then 
                        if dest != 0 then 
                            call dest.push(.remove(v.i))
                        else 
                            call .remove(v.i)
                        endif
                    endif
                    set v.i = v.i - 1
                endloop
            endif
            return dest
        endmethod 
        method addMatching takes thistype srcs, code c returns nothing 
            set v.i = 0
            set v.len = srcs.length
            if srcs != 0 then 
                call .setupTrigger(c)
                loop 
                    exitwhen v.i == v.len 
                    set pivot = Load$VAR$(v.ht, srcs, v.i)
                    if TriggerEvaluate(v.t) then 
                        call .push(pivot)
                    endif
                    set v.i = v.i + 1
                endloop
            endif
        endmethod
        method setMatching takes $TYPE$ value, code c returns nothing 
            set v.i = 0
            set v.len = .length
            call .setupTrigger(c)
            loop 
                exitwhen v.i == v.len 
                set pivot = Load$VAR$(v.ht, this, v.i)
                if TriggerEvaluate(v.t) then 
                    call Save$VAR$(v.ht, this, v.i, value)
                endif
                set v.i = v.i + 1
            endloop
        endmethod 
        method countMatching takes code c returns integer 
            local count = 0
            set v.i = 0
            set v.len = .length
            call .setupTrigger(c)
            loop 
                exitwhen v.i == v.len 
                set pivot = Load$VAR$(v.ht, this, v.i)
                if TriggerEvaluate(v.t) then 
                    set count = count + 1
                endif
                set v.i = v.i + 1
            endloop
            return count
        endmethod */

		// - use HaveSavedSomething native instead
		// - Table's API uses the term has instead of contains, might be better with has
        method contains takes $TYPE$ value returns boolean
            return .indexOf(value) != -1
        endmethod
       
		// - bye please
/*      method containsAll takes thistype list returns boolean
            local integer i = 0
            set v.len = list.length
            loop
                exitwhen i == v.len 
                if .indexOf(list[i]) == -1 then
                    return false
                endif
                set i = i + 1
            endloop
            return true 
        endmethod */

		// - just change this to operator
        method size takes nothing returns integer 
            return .length
        endmethod
       
        static method copy takes thistype src, integer srcPos, thistype dest, integer destPos, integer range returns thistype
            local integer i = 0
            local integer srcSize
            if srcPos < 0 or srcPos == src.length or destPos < 0 or destPos > dest.length then  // - so, srcPos > src.length is fine eh?
debug            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "ERROR: $NAME$ArrayList.copy:IndexOutOfBounds")
                return dest
            endif
            set srcSize = src.length  // - move this above on local declaration, also helps inside the if
            loop
                exitwhen i == range or srcPos >= srcSize  // - it should be safe making it == instead after the if above is fixed
                call dest.add(destPos, src[srcPos])  // - do this manually, don't be lazy, the manual way removes the need of so many unnecessary operations
                set destPos = destPos + 1
                set srcPos = srcPos + 1
                set i = i + 1
            endloop
            return dest
        endmethod

		// - unneeded because this can be done easily enough with  ArrayList.create().addCopy(0, AL, 0, AL.length)
		// - there are many optimize-able things if this method is done manually
/*      method clone takes nothing returns thistype 
            return thistype.copy(this, 0, thistype.create(), 0, .length)
        endmethod */
       
	    // - no comment yet on this one, I'm currently occupied with other things, can't be arsed to bother sorting algorithms
        private method quicksort takes integer first, integer last returns nothing 
            if first < last then   
                set v.up = first 
                set v.down = last 
                set pivot = Load$VAR$(v.ht, this, first)
                loop 
                    loop 
                        set other = Load$VAR$(v.ht, this, v.up)
                        exitwhen v.up >= last or TriggerEvaluate(v.t)
                        set v.up = v.up + 1 
                    endloop
                    loop 
                        set other = Load$VAR$(v.ht, this, v.down)
                        exitwhen  not TriggerEvaluate(v.t)
                        set v.down = v.down - 1
                    endloop
                    exitwhen v.up >= v.down 
                    set other = Load$VAR$(v.ht, this, v.up)
                    call Save$VAR$(v.ht, this, v.up, Load$VAR$(v.ht, this, v.down))
                    call Save$VAR$(v.ht, this, v.down, other)
                endloop
                set other = Load$VAR$(v.ht, this, first)
                call Save$VAR$(v.ht, this, first, Load$VAR$(v.ht, this, v.down))
                call Save$VAR$(v.ht, this, v.down, other) 
                call .quicksort(first, v.down - 1)   
                call .quicksort(v.down + 1, last)     
            endif
        endmethod 

		// - just make quicksort public and rename it to sort please, make sure to do the appropriate changes
/*      method sort takes code c returns nothing 
debug        if .length > 1400 then
debug             call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60, "WARNING: $NAME$ArrayList.sort: Attempting to sort " + I2S(.length) + " elements.")
debug        endif
            call .setupTrigger(c)
            call .quicksort(0, .length - 1)
        endmethod */

		// - a ghost public method, unseen in docs
		// - it's fine except for its ghost status
        method reverse takes nothing returns nothing 
            set v.up = 0
            set v.down = .length - 1
            loop 
                exitwhen v.down <= v.up
                set other = Load$VAR$(v.ht, this, v.up)
                call Save$VAR$(v.ht, this, v.up, Load$VAR$(v.ht, this, v.down))
                call Save$VAR$(v.ht, this, v.down, other)
                set v.up = v.up + 1 
                set v.down = v.down - 1
            endloop 
        endmethod 
       
    endstruct
    //! endtextmacro
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
Thanks for your feedback. That's quite a extensive list so I won't comment on all but some changes will be made from it.
  • On the sort, I'm not sure how you can make it pass a bool expression, care to show an example of how it would work?
  • With regard to the operators '<' and '>' what I mean is that if you try: <= or >=, == or != the sorting won't work.
  • containsAll is for example used when you wanna check for all items in a recepie system is in existence.
  • On the inline vs safty should be enabled or not
    JASS:
    [/LIST]
    method opeartor[] takes integer index returns $TYPE$
        static if SAFTY_ENABLED then 
            if index < 0 or index >= .length then 
                debug msg
                return    
            endif
        endif
        return Load$VAR$(ht, this, index)
    endmethod
  • You don't like the push method?On Hashtable HaveSaved, I'm not sure if that would work as we don't know which key to look for or is that what HaveSavedValue does? Also the length would have to be temporarily removed as to not conflict with saved integers.
    JASS:
    native HaveSavedBoolean (hashtable table, integer parentKey, integer childKey) returns boolean
    native HaveSavedHandle (hashtable table, integer parentKey, integer childKey) returns boolean
    native HaveSavedInteger (hashtable table, integer parentKey, integer childKey) returns boolean
    native HaveSavedReal (hashtable table, integer parentKey, integer childKey) returns boolean
    native HaveSavedString (hashtable table, integer parentKey, integer childKey) returns boolean
    function HaveSavedValue (integer key, integer valueType, integer missionKey, hashtable table) returns boolean
  • I felt that if the copy method was static it was clear how it would work, where as if it's a method it's not as clear what is going where.

  • I don't think making quicksort public makes any sense unless you want to sort extremly large data sets and want something to sort only part of that data set for you, but I can make it public. But as for removing sort(code c) thats' a no. Furthermore, again, I'm not sure you can pass bool expressions as I don't think you can do the comparisons necessary.
  • I suppose changing contains to has makes sense if you'd want to have compatability with normal table usage. But these methods don't exactly do the same thing. Table.has returns true if there is something stored at a particular index. list.contains return true if the list actually have an element of a given value inside it.
A boolexpr is a == b right? The thing is that a and b changes depending on where we are on the list.


I reduced the boiler plate by creating a forEach method instead so that those methods you hated have less boiler plate code generated: method forEach takes integer start, integer end, integer change, code c, code a returns nothing This part of the library wasn't meant to be fast, simply convenient.

For isntance countMatching turned into:
JASS:
method countMatching takes code c returns integer
    set up = 0
    call .forEach(0, .length -1, 1, c, function thistype.onCountMatching)
    return up
endmethod[/icode]

I'm not exactl sure if it saved many lines, but perhaps.. ^^
 
Last edited:
Level 6
Joined
Jan 9, 2019
Messages
102
On the sort, I'm not sure how you can make it pass a bool expression, care to show an example of how it would work?
After accidentally bumping into something, in your case it's actually better to use ForForce. This is the thread.

With regard to the operators '<' and '>' what I mean is that if you try: <= or >=, == or != the sorting won't work.
Well yes, if it's about comparing the values directly. The thing is, it is not about the operator, it's about whether the filter returns true or false.

On Hashtable HaveSaved, I'm not sure if that would work as we don't know which key to look for or is that what HaveSavedValue does? Also the length would have to be temporarily removed as to not conflict with saved integers.
My bad here mate! Don't know why I remembered the function that way.

containsAll is for example used when you wanna check for all items in a recepie system is in existence.
Alright, just do this then:
JASS:
	// - manual rewrite produces faster result, and is more optimized
	method has takes $TYPE$ value returns boolean
		local integer i = .length
		loop
			exitwhen i == 0
			set i = i - 1
			if Load$VAR$(v.ht, this, i) == value then
				return true
			endif
		endloop
		return false
	endmethod

	// - please check on this one, might miss something
	method contains takes thistype list returns boolean
		local integer i = list.length
		local integer j
		local $TYPE$ value
		loop
			exitwhen i == 0
			set i = i - 1
			set value = Load$VAR$(v.ht, list, i)
			set j = .length
			loop
				if j == 0 then
					return false
				endif
				set j = j - 1
				exitwhen Load$VAR$(v.ht, this, j) == value
			endloop
		endloop
		return true
	endmethod
On the inline vs safty should be enabled or not
Don't use static if + constant for this, literally unneeded configuration for the user. Just enable the safety on debug, and disable it otherwise. A good user knows that it's not supposed to hold keys greater than its size.

You don't like the push method?
It's fine, and on point.

I felt that if the copy method was static it was clear how it would work, where as if it's a method it's not as clear what is going where.
I'm sure addCopy explains things better, plus it requires one less argument.

I don't think making quicksort makes public, unless you want to sort extremly large data sets and want something to sort only part of that data set for you, but I can make it public. But as for removing sort(code c) thats' a no. Furthermore, again, I'm not sure you can pass bool expressions as I don't think you can do the comparisons needed.
What I meant was, to merge sort, quicksort, and setup all into one method which is sort alone. Look at recursive calls in this thread. If you really really want to separate them, use textmacros instead.
---

These are new...
JASS:
        private method setupTrigger takes code c, code a returns nothing 
            call TriggerClearConditions(v.t)   
            call TriggerClearActions(v.t)
            call TriggerAddCondition(v.t, Condition(c))
            if a != null then 
                call TriggerAddAction(v.t, a)
            endif
            set v.list = this
        endmethod

        private method forEach takes integer start, integer change, integer end, code c, code a returns nothing
            call .setupTrigger(c, a)
            set v.i = start
            loop 
                exitwhen v.i == end 
                set pivot = Load$VAR$(v.ht, this, v.i)
                if TriggerEvaluate(v.t) then
                    call TriggerExecute(v.t)
                endif
                set v.i = v.i + change 
            endloop
        endmethod
... which literally downgraded the script. Nice!
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
You can't write an iterative quicksort without a elaborate stack manipulation to replace the recursion. I've tried more primitive solutions before and they perform far worse.

As for the ForForce I don't know why bring that up, I'm not manipulating a force here? Though I've never been having anything against things like ForGroup or ForForce.

I don't envision anyone using containsAll in a mission critical way requiring the removal of the function call just to perform the same action. Suppose I could test it. The change in copy makes sense because it was very inefficiently written O(n^2). The changes to contain all does not address the underlying problem of it being O(n^2) and thus will not have any substantial impact.

As for the forEach wrappers, I see what you're saying. They may be slow but I will have to test them in a real scenario before I lay a verdict on if they have any practical use or are just silly, probably the latter. Though I think addMatching and removeMatching are more valuable than setMatching and countMatching.

You can't have nested textmacros I'm afraid.

So you want .sort(function SortComparison) to be changed to list.sort(Filter(function SortComparison))?

I'll try include more debugs, I'm thinking add and remove should have the if-statement because if that goes wrong the structure goes to shit, and not simply that you will have swapped things to unused memory.
 
Last edited:
Level 6
Joined
Jan 9, 2019
Messages
102
You can't have nested textmacros I'm afraid.
Forgot that it was inside a textmacro already, just use manual CnP. There must be a way to make the quicksort non-recursive.

As for the ForForce I don't know why bring that up
To bloody run a code of course. Here I'll CnP Bribe's post just for you:
JASS:
scope Poop initializer Init
    globals 
        private trigger trig = CreateTrigger()
        private force f = CreateForce()
    endglobals
    function TestFunc takes nothing returns nothing
    endfunction
    private function Tester takes nothing returns nothing
        local integer i = 500
        loop
            set i = i - 1
            //call ExecuteFunc("TestFunc")
            //call TriggerExecute(trig)
            //call TriggerEvaluate(trig)
            call ForForce(f, function TestFunc)
            exitwhen i == 0
        endloop
    endfunction
    private function Init takes nothing returns nothing
        local code c = function TestFunc
        //call TriggerAddAction(trig, c)
        //call TriggerAddCondition(trig, Filter(c))
        call ForceAddPlayer(f, Player(0))
        call TimerStart(CreateTimer(), 0.03125, true, function Tester)
    endfunction
endscope
But it can't return any value, so a global variable is needed to pass the returned value.

I'll try include more debugs, I'm thinking add and remove should have the if-statement because if that goes wrong the structure goes to shit, and not simply that you will have swapped things to unused memory.
Good users know what an array is. They can use their own if statement when they know they might pass an invalid key.

As for the forEach wrappers, I see what you're saying. They may be slow but I will have to test them in a real scenario before I lay a verdict on if they have any practical use or are just silly, probably the latter. Though I think addMatching and removeMatching are more valuable than setMatching and countMatching.
Alright... Did you read this?
Recursive Calls
Recursive function calls (like many BJ's) have a relatively significant impact on performance. For example GetHandleId was ~24-28% faster than GetHandleIdBJ and it increases within each recursion.
For your convenience, here's what GetHandleIdBJ is:
JASS:
function GetHandleIdBJ takes handle h returns integer
    return GetHandleId(h)
endfunction
And you're saying that something like this is just fine:
JASS:
private function A takes integer exp returns integer
    local integer i = 1
    loop
        exitwhen exp <= 0
        set i = i*2
        set int = int - 1
    endloop
    return i
endfunction
function B takes integer len returns integer
    local integer i = 0
    loop
        exitwhen len <= 0
        set len = len - 1
        set i = i + A(len)
    endloop
    return i
endfunction
// notice that A is private, and if this is the entire lib, it's stupid to have A as a standalone function
While this one is way better:
JASS:
function B takes integer len returns integer
    local integer i = 0
    local integer j = 1
    loop
        exitwhen len <= 0
        set i = i + j
        set j = j*2
        set len = len - 1
    endloop
    return i
endfunction
// while this example has an extreme difference, the fact that it no longer does unnecessary function calls INSIDE A LOOP is a lot already
---

Now I should really stop arsing around with all this.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
Alright I've implemented your suggested changes, in particular I removed the forEach stuff. The only thing I didn't implement was the ForForce as I would have to test the validity of that before changing it). Because even if ForForce is faster than EvalTrigger for callbacks it does not necessarily mean that this is also true:

JASS:
function Compare takes nothing returns nothing 
    set result = evaluate something 
endfunction 

loop 
    set other = load()
    call ForForce(F. function Compare)
    if result then 
         call swap()
    endif
endloop

// vs 
function Compare takes nothing returns boolean
    return evaluate something
endfunction 

loop 
    set other = load()
    if EvaluateTrigger(t) then 
         call swap()
    endif
endloop

I also read in one of the treads you linked that EvaluateTrigger(null) is a rather small cost? Which makes me consider if the new methods
addRange and setRange still could implement a condition argument as optional in case one wants to apply a filter?
 
Last edited:
Level 15
Joined
Nov 30, 2007
Messages
1,202
It's not because of the speed. It's because of the place, the situation. method sort takes code c returns nothing. There's nothing being saved, so directly passing c works.

Man, I should stop doing this...
You are free to try writing a better sorting algorithm and post results ^^

Edit: Added all the removed code to the opening post in case anyone wants to argue for their place in the library.

JASS:
        method drop takes thistype evicted, code c returns nothing                 // drop all elements matching condition
        method unique takes thistype evicted returns nothing                     // drops all duplications
        method forEach takes integer start, integer end, integer change, code c, code a returns nothing
        method removeMatching takes thistype dest, code c returns thistype 
        method addMatching takes thistype srcs, code c returns nothing 
        method setMatching takes $TYPE$ value, code c returns nothing 
        method countMatching takes code c returns integer
 
Last edited:
Level 15
Joined
Nov 30, 2007
Messages
1,202
Since Overfrost didn't believe me when I said that Table doesn't inline I now have proof that method operators don't inline, or am I reading this result wrong? (I didn't actually look at Table but the principle is the same).

JASS:
        method operator[] takes integer index returns $TYPE$
            return Load$VAR$(HashRecyler.ht , this, index)
        endmethod
  
        method operator[]= takes integer index, $TYPE$ element returns nothing
            call Save$VAR$(HashRecyler.ht , this, index, element)
        endmethod
private method reverseInner takes integer first, integer last returns nothing
            if first < last then
                set other = this[first]
                set this[first] = this[last]
                set this[last] = other
                call .reverseInner(first + 1, last - 1)
            endif
        endmethod
  
        method reverse takes nothing returns nothing
            //call reverseInner(0, .length)
            set up = 0
            set down = .length - 1
            loop
                exitwhen down <= up
                set other = this[up]
                set this[up] = this[down]
                set this[down] = other
                set up = up + 1
                set down = down - 1
            endloop
        endmethod

Translates into:

JASS:
 // Setter
 function s__IntArrayList__getindex takes integer this,integer index returns integer
            return LoadInteger(s__HashRecyler_ht, this, index)
  endfunction
   
    // Getter
    function s__IntArrayList__setindex takes integer this,integer index,integer element returns nothing
        call SaveInteger(s__HashRecyler_ht, this, index, element)
    endfunction


 function s__IntArrayList_reverseInner takes integer this,integer first,integer last returns nothing
            if first < last then
                set s__IntArrayList_other=s__IntArrayList__getindex(this,first)
                call s__IntArrayList__setindex(this,first, s__IntArrayList__getindex(this,last))
                call s__IntArrayList__setindex(this,last, s__IntArrayList_other)
                call s__IntArrayList_reverseInner(this,first + 1 , last - 1)
            endif
  endfunction
  
  function s__IntArrayList_reverse takes integer this returns nothing
            //call reverseInner(0, .length)
            set ArrayList__up=0
            set ArrayList__down=s__IntArrayList__get_length(this) - 1
            loop
                exitwhen ArrayList__down <= ArrayList__up
                set s__IntArrayList_other=s__IntArrayList__getindex(this,ArrayList__up)
                call s__IntArrayList__setindex(this,ArrayList__up, s__IntArrayList__getindex(this,ArrayList__down))
                call s__IntArrayList__setindex(this,ArrayList__down, s__IntArrayList_other)
                set ArrayList__up=ArrayList__up + 1
                set ArrayList__down=ArrayList__down - 1
            endloop
  endfunction
This doesn't look like inlining to me.

Which also explains why using method operators over directly calling the hashtable reaches the OP limit much sooner.

@Overfrost, I will now continue saying what you told me to stop saying directly into your face. :D
 
Last edited:
Level 6
Joined
Jan 9, 2019
Messages
102
It's your compiler mate, probably your settings. Here's what happens for me.

JASS:
scope Test initializer Init

private function Init takes nothing returns nothing
    local IntArrayList list = IntArrayList.create()
    call list.push(0xF)
    call list.push(0xFF)
    set list[0] = list.last()
    set list[list.length - 1] = list.first()
endfunction

endscope
Compiled to:
JASS:
//===========================================================================
// scope Test begins

function Test___Init takes nothing returns nothing
    local integer list= (s__HashRecyler_alloc()) // INLINED!!
    call s__IntArrayList_push(list,0xF)
    call s__IntArrayList_push(list,0xFF)
    call SaveInteger(s__HashRecyler_ht, (list), (0), ( s__IntArrayList_last(list))) // INLINED!!
    call SaveInteger(s__HashRecyler_ht, (list), ((LoadInteger(s__HashRecyler_ht, (list), ArrayList___SIZE_INDEX)) - 1), ( (LoadInteger(s__HashRecyler_ht, (list), 0)))) // INLINED!!
endfunction

// scope Test ends
Just to clarify, I used your lib without any changes.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
Could you try this from a scope.
JASS:
library THIS_IS_A_BIG_LIB_NAME uses Table

    struct S
 
        static method create takes nothing returns thistype
            return  Table.create()
        endmethod
 
        method destroy takes nothing returns nothing
            call Table(this).destroy()
        endmethod
 
        method operator[] takes integer index return integer
            return Table(this).integer[index]
        endmethod
 
        method operator[]= takes integer index, integer value return integer
            return Table(this).integer[index] = value
        endmethod

    endstruct
endlibrary

  local S s = S.create()
  set s[0] = 1
  call s.destroy()

So we can kill this debate once and for all.

Also, I'm using the latest patch of wc3... Does this mean the new patches have killed inlining? :>

Can someone confirm that this is the case?

Also, should I stick with hashtable or use Table regardless of this possible problem?
 
Last edited:
Level 6
Joined
Jan 9, 2019
Messages
102
JASS:
library LIBRARY uses Table

    struct S extends array
 
        static method create takes nothing returns thistype
            return  Table.create()
        endmethod
     
        method destroy takes nothing returns nothing
            call Table(this).destroy()
        endmethod
     
        method operator[] takes integer index returns integer
            return Table(this).integer[index]
        endmethod
     
        method operator[]= takes integer index, integer value returns nothing
            set Table(this).integer[index] = value
        endmethod

    endstruct
endlibrary
->
JASS:
//library LIBRARY:

 
        function s__S_create takes nothing returns integer
            return s__Table_create()
        endfunction
     
        function s__S_destroy takes integer this returns nothing
            call s__Table_destroy((this))
        endfunction
     
        function s__S__getindex takes integer this,integer index returns integer
            return (LoadInteger(Table__ht, ((((this)))), (index))) // INLINED!!
        endfunction
     
        function s__S__setindex takes integer this,integer index,integer value returns nothing
            call SaveInteger(Table__ht, ((((this)))), (index), ( value)) // INLINED!!
        endfunction


//library LIBRARY ends
 
Top