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

TreeList (Nested Lists)

Status
Not open for further replies.

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Linked-List that allows dynamic nesting (Tree of Lists).

Script
JASS:
library TreeList /* v1.1


    */uses /*

    */Table         /*  https://www.hiveworkshop.com/threads/snippet-new-table.188084/
    */ErrorMessage  /*  https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j


    *///! novjass

    |-------------|
    | Description |
    |-------------|
    /*
        A list which nodes are another list of nodes - basically, a tree of list.
        The lists are implemented as circular doubly linked-lists.

        This snippet shares the same instance reservior with the Table library.
        But this is not a problem since the Table instances limit is insanely
        big (around 2^31 - 1 instances) so lets say you have many systems that
        might use around 100,000 Table instances (which is very unlikely). Then
        you just need around 20,000 of those sytems in order to reach the Table
        instances limit.

        On the contrary, what seems like a con can actually turn out to be an
        advantage, that is, you can directly use the nodes of this snippet as
        fully functional Table instances without worrying about possible
        collisions and use them to store various data.
    */
    |-----|
    | API |
    |-----|

    struct TreeList/*

      */readonly TreeList prev       /*  The previous member
      */readonly TreeList next       /*  The next member
      */readonly TreeList first      /*  The first member of the node's list
      */readonly TreeList last       /*  The last member of the node's list
      */readonly integer size        /*  The current size of the list (i.e., its children population)
      */readonly boolean isEmpty     /*  Boolean flag that tells if the node's list is empty

      */static method create takes nothing returns TreeList/*
        - Creates a new list
      */method destroy takes nothing returns nothing/*
        - Destroys a list

      */method insert takes TreeList node returns this/*
        - Inserts a node next to this node
      */method remove takes nothing returns this/*
        - Removes this node
      */method push takes TreeList node returns this/*
        - Inserts a node to the front of this node's list
      */method pop takes nothing returns this/*
        - Removes the front of this node's list
      */method enqueue takes TreeList node returns this/*
        - Inserts a node to the back of this node's list
      */method dequeue takes nothing returns this/*
        - Removes the back of this node's list
      */method clear takes nothing returns this/*
        - Clears this node's list
      */method flush takes boolean destroyNodes returns this/*
        - Flushes this node's list

      */method mergeChildren takes nothing returns this/*
        - Merges the nodes of the child lists of this node's list
        - This operation destroys this node's child nodes
      */method mergeDescendants takes nothing returns this/*
        - Performs a chain of mergeChildren operations starting from this node up
          to the leaf nodes
        - Basically just combines all the leaf nodes and puts them into this node's
          list
      */method collapseBranch takes nothing returns this/*
        - Puts the children of this node to its own level and appends them as its
          next and before its former 'next'
      */method collapse takes nothing returns this/*
        - Performs a chain of collapseBranch operations starting from this node up
          to its leaf nodes
      */method rotate takes boolean clockwise returns this/*
        - Rotates this node's list clockwise or counter-clockwise depending on the
          boolean parameter


    */module TreeListTransversal/*
      - Implement this module below your method named onTransverse (may or may not
        be a static method). Then, whenever you call TreeList(instance).transverse(),
        your method named onTransverse will run for each node starting from the root
        up to the leaves.
      - Method onTransverse must take nothing and return a boolean. If it returns
        false, the transversal will halt.


    Textmacros for iteration

    - Use these to iterate over a list named 'this' (which means that you must have
      a TreeList variable named 'this' within your method). Then you can refer to the
      same variable ("this") inside the iteration block to access the currently
      iterated node in the list. The original value of the variable "this" will be
      restored after the iteration.

    */textmacro TREE_LIST_ITERATOR_START takes CLOCKWISE/*
      - run at the beginning of your iteration code block
      - "CLOCKWISE" is a boolean parameter that determines the direction of iteration
      - clockwise iteration iterates from the first node to the last node while
        counter-clockwise iteration does the opposite
    */textmacro TREE_LIST_ITERATOR_END/*
      - run at the end of your iteration code block

    Note that you can nest these macros to iterate the children of the current iterated
    node. This method is slower compared to manually iterating as it involves hashtable
    lookups plus some other additional operations to accommodate for the possibility of
    nested iterations. But this will prove very useful for some deep nested iterations
    as it would eliminate the need for many local variable declarations and would make
    your script very much readable and shorter.

    *///! endnovjass

    private keyword Init

    struct TreeList extends array

        debug private static Table allocated
        private static Table prevT
        private static Table nextT
        private static Table listT

        private method operator list takes nothing returns thistype
            return listT[this]
        endmethod

        method operator prev takes nothing returns thistype
            debug call ThrowError(allocated[this] == 0, "TreeList", "prev", "thistype", this, "Attempted to access an unallocated instance field")
            return prevT[this]
        endmethod
        method operator next takes nothing returns thistype
            debug call ThrowError(allocated[this] == 0, "TreeList", "next", "thistype", this, "Attempted to access an unallocated instance field")
            return nextT[this]
        endmethod
        method operator first takes nothing returns thistype
            debug call ThrowError(allocated[this] == 0, "TreeList", "first", "thistype", this, "Attempted to access an unallocated instance field")
            return this.list.next
        endmethod
        method operator last takes nothing returns thistype
            debug call ThrowError(allocated[this] == 0, "TreeList", "last", "thistype", this, "Attempted to access an unallocated instance field")
            return this.list.prev
        endmethod

        private method operator prev= takes integer node returns nothing
            set prevT[this] = node
        endmethod
        private method operator next= takes integer node returns nothing
            set nextT[this] = node
        endmethod
        private method operator list= takes integer node returns nothing
            set listT[this] = node
        endmethod

        static method create takes nothing returns thistype
            local thistype this = Table.create()
            local thistype list = Table.create()
            set list.prev = list
            set list.next = list
            set this.list = list
            set thistype(-list).list = this
            debug set allocated[this] = 1
            debug set allocated[list] = 1
            return this
        endmethod

        method destroy takes nothing returns nothing
            local thistype list = this.list
            debug call ThrowError(allocated[this] == 0, "TreeList", "destroy()", "thistype", this, "Attempted to doubly destroy instance")
            call Table(this).destroy()
            call Table(list).destroy()
            set this.prev = 0
            set this.next = 0
            set this.list = 0
            set list.prev = 0
            set list.next = 0
            set thistype(-list).list = 0
            debug set allocated[this] = 0
            debug set allocated[list] = 0
        endmethod

        method operator size takes nothing returns integer
            local thistype head = this.list
            local thistype node = head.next
            local integer count = 0
            loop
                exitwhen node == head
                set count = count + 1
                set node = node.next
            endloop
            return count
        endmethod

        method operator isEmpty takes nothing returns boolean
            debug call ThrowError(allocated[this] == 0, "TreeList", "isEmpty", "thistype", this, "Attempted to use an unallocated instance")
            return this.first == this.list
        endmethod

        method insert takes thistype node returns thistype
            local thistype next = this.next
            debug call ThrowError(allocated[this] == 0, "TreeList", "insert()", "thistype", this, "Attempted to insert node to an unallocated list")
            debug call ThrowError(allocated[node] == 0, "TreeList", "insert()", "thistype", node, "Attempted to insert an unallocated node")
            set next.prev = node
            set this.next = node
            set node.prev = this
            set node.next = next
            return this
        endmethod

        method push takes thistype node returns thistype
            debug call ThrowError(allocated[this] == 0, "TreeList", "push()", "thistype", this, "Attempted to push node to an unallocated list")
            debug call ThrowError(allocated[node] == 0, "TreeList", "push()", "thistype", node, "Attempted to push an unallocated node")
            call this.list.insert(node)
            return this
        endmethod

        method enqueue takes thistype node returns thistype
            debug call ThrowError(allocated[this] == 0, "TreeList", "enqueue()", "thistype", this, "Attempted to enqueue node to an unallocated list")
            debug call ThrowError(allocated[node] == 0, "TreeList", "enqueue()", "thistype", node, "Attempted to enqueue an unallocated node")
            call this.last.insert(node)
            return this
        endmethod

        method remove takes nothing returns thistype
            local thistype prev = this.prev
            local thistype next = this.next
            debug call ThrowError(allocated[this] == 0, "TreeList", "remove()", "thistype", this, "Attempted to remove an unallocated node")
            set next.prev = prev
            set prev.next = next
            return this
        endmethod

        method pop takes nothing returns thistype
            debug call ThrowError(allocated[this] == 0, "TreeList", "pop()", "thistype", this, "Attempted to pop an unallocated node")
            call this.first.remove()
            return this
        endmethod

        method dequeue takes nothing returns thistype
            debug call ThrowError(allocated[this] == 0, "TreeList", "dequeue()", "thistype", this, "Attempted to dequeue an unallocated node")
            call this.last.remove()
            return this
        endmethod

        method clear takes nothing returns thistype
            local thistype head = this.list
            debug call ThrowError(allocated[this] == 0, "TreeList", "clear()", "thistype", this, "Attempted to clear an unallocated node")
            set head.prev = 0
            set head.next = 0
            return this
        endmethod

        method flush takes boolean destroyNodes returns thistype
            local thistype head = this.list
            local thistype node = head.next
            debug call ThrowError(allocated[this] == 0, "TreeList", "flush()", "thistype", this, "Attempted to flush an unallocated node")
            if destroyNodes then
                loop
                    exitwhen node == head
                    call node.flush(true)
                    call node.remove()
                    call node.destroy()
                    set node = node.next
                endloop
            else
                loop
                    exitwhen node == head
                    call node.flush(false)
                    call node.remove()
                    set node = node.next
                endloop
            endif
            return this
        endmethod

        method collapseBranch takes nothing returns thistype
            local thistype head = this.list
            local thistype next = this.next
            local thistype first = head.next
            local thistype last = head.prev
            debug call ThrowError(allocated[this] == 0, "TreeList", "collapseBranch()", "thistype", this, "Attempted to use an unallocated node")
            set first.prev = this
            set last.next = next
            set next.prev = last
            set this.next = first
            set head.prev = head
            set head.next = head
            return this
        endmethod

        method collapse takes nothing returns thistype
            local thistype head = this.list
            local thistype node = head.next
            debug call ThrowError(allocated[this] == 0, "TreeList", "collapse()", "thistype", this, "Attempted to use an unallocated node")
            loop
                exitwhen node == head
                if not node.isEmpty then
                    call node.collapseBranch()
                endif
                set node = node.next
            endloop
            call this.collapseBranch()
            return this
        endmethod

        method mergeChildren takes nothing returns thistype
            local thistype head = this.list
            local thistype node = head.next
            local thistype last = head
            local thistype first
            debug call ThrowError(allocated[this] == 0, "TreeList", "mergeChildren()", "thistype", this, "Attempted to use an unallocated node")
            loop
                exitwhen node == head
                if node.isEmpty then
                    set node = node.next
                else
                    set first = node.first
                    set last.next = first
                    set first.prev = last
                    set last = node.last
                    set node = node.next
                    call node.prev.destroy()
                endif
            endloop
            set last.next = head
            set head.prev = last
            return this
        endmethod

        method mergeDescendants takes nothing returns thistype
            local thistype head = this.list
            local thistype node = head.prev
            local thistype collapsed
            debug call ThrowError(allocated[this] == 0, "TreeList", "mergeDescendants()", "thistype", this, "Attempted to use an unallocated node")
            loop
                exitwhen node == head
                if node.isEmpty then
                    set node = node.prev
                else
                    call node.mergeDescendants()
                    call node.collapseBranch()
                    set collapsed = node
                    set node = node.prev
                    call collapsed.remove()
                    call collapsed.destroy()
                endif
            endloop
            return this
        endmethod

        method rotate takes boolean clockwise returns thistype
            local thistype node
            debug call ThrowError(allocated[this] == 0, "TreeList", "rotate()", "thistype", this, "Attempted to use an unallocated node")
            if clockwise then
                set node = this.last
                call node.remove()
                return this.push(node)
            else
                set node = this.first
                call node.remove()
                return this.enqueue(node)
            endif
        endmethod

        private static Table prevHeadNode
        private static Table prevNodeCache
        private static TreeList headNode = 0
        private static TreeList nodeCache = 0

        private static method init takes nothing returns nothing
            set prevT = Table.create()
            set nextT = Table.create()
            set listT = Table.create()
            set prevHeadNode = Table.create()
            set prevNodeCache = Table.create()
            debug set allocated = Table.create()
        endmethod
        implement Init

    endstruct

    private module Init
        private static method onInit takes nothing returns nothing
            call init()
        endmethod
    endmodule

    module TreeListTransversal
        method transverse takes nothing returns TreeList
            local TreeList head
            local TreeList node = this
            debug call ThrowError(s__TreeList_allocated[this] == 0, "TreeList", "transverse()", "thistype", this, "Attempted to transverse an unallocated node")
            static if thistype.onTransverse.exists then
                set head = s__TreeList_listT[this]
                set this = head.next
                loop
                    exitwhen this == head
                    if not onTransverse() then
                        return -1
                    endif
                    if this.transverse() == -1 then
                        return node
                    endif
                    set this = TreeList(this).next
                endloop
            endif
            return node
        endmethod
    endmodule

    //! textmacro TREE_LIST_ITERATOR_START takes CLOCKWISE
        set s__TreeList_prevHeadNode[this] = s__TreeList_headNode
        set s__TreeList_prevNodeCache[this] = s__TreeList_nodeCache
        set s__TreeList_headNode = s__TreeList_listT[this]
        set s__TreeList_nodeCache = s__TreeList_headNode
        set this = s__TreeList_nodeCache
        loop
            static if $CLOCKWISE$ then
                set s__TreeList_nodeCache = s__TreeList_nodeCache.next
            else
                set s__TreeList_nodeCache = s__TreeList_nodeCache.prev
            endif
            set this = s__TreeList_nodeCache
            exitwhen this == s__TreeList_headNode
    //! endtextmacro

    //! textmacro TREE_LIST_ITERATOR_END
        endloop
        set this = s__TreeList_listT[-this]
        set s__TreeList_headNode = s__TreeList_prevHeadNode[this]
        set s__TreeList_nodeCache = s__TreeList_prevNodeCache[this]
        set s__TreeList_prevHeadNode[this] = 0
        set s__TreeList_prevNodeCache[this] = 0
    //! endtextmacro


endlibrary


Nested iteration sample:
JASS:
struct Test extends array
    private static method onInit takes nothing returns nothing
        /*
                             |    Root Node    |        |   2nd Level   |        |    3rd Level    |       |    4th Level    |
                                                                                                                              */
        local TreeList this = TreeList.create().enqueue(/*
                                                      */TreeList.create().enqueue(/*
                                                                                */TreeList.create().enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                               */)/*
                                                                       */.enqueue(/*
                                                                                */TreeList.create().enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                               */)/*
                                                                       */.enqueue(/*
                                                                                */TreeList.create().enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                               */)/*
                                                     */)/*
                                             */.enqueue(/*
                                                      */TreeList.create().enqueue(/*
                                                                                */TreeList.create().enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                               */)/*
                                                                       */.enqueue(/*
                                                                                */TreeList.create().enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                               */)/*
                                                                       */.enqueue(/*
                                                                                */TreeList.create().enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                               */)/*
                                                     */)/*
                                             */.enqueue(/*
                                                      */TreeList.create().enqueue(/*
                                                                                */TreeList.create().enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                               */)/*
                                                                       */.enqueue(/*
                                                                                */TreeList.create().enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                               */)/*
                                                                       */.enqueue(/*
                                                                                */TreeList.create().enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                                                 */.enqueue(TreeList.create())/*
                                                                               */)/*
                                                     */)

        call TriggerSleepAction(0.10)

        //! runtextmacro TREE_LIST_ITERATOR_START("true")
        //iterates over the nodes in the 2nd level starting from the first to the last node ("true" -> clockwise)

            call BJDebugMsg(I2S(this))

            //! runtextmacro TREE_LIST_ITERATOR_START("false")
            //iterates over the nodes in the 3rd level starting from the last to the first node ("false" -> counter-clockwise)

                call BJDebugMsg(I2S(this))

                //! runtextmacro TREE_LIST_ITERATOR_START("true")
                //iterates over the nodes in the 4rd level starting from the first to the last node ("true" -> clockwise)

                    call BJDebugMsg(I2S(this))

                    //you can iterate up to the nth level by nesting these macros

                //! runtextmacro TREE_LIST_ITERATOR_END()

            //! runtextmacro TREE_LIST_ITERATOR_END()

        //! runtextmacro TREE_LIST_ITERATOR_END()

        call BJDebugMsg(I2S(this)) // the value of "this" is restored to its original value after the iteration

    endmethod
endstruct
 
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
I can't think of a system or a game mechanic that could possible need/use such a representation.
One usually wants to find/query some entities by going over some lists (e.g: GroupEnumUnitsInRange).

Anyway...

It doesn't seem like calling destroy on the tree from the "nested iteration sample:" would clean up all the created nodes.
JASS:
method destroy takes nothing returns nothing
    local thistype list = this.list
    debug call ThrowError(allocated[this] == 0, "TreeList", "destroy()", "thistype", this, "Attempted to doubly destroy instance")
    call Table(this).destroy()
    call Table(list).destroy()
    set this.prev = 0
    set this.next = 0
    set this.list = 0
    set list.prev = 0
    set list.next = 0
    set thistype(-list).list = 0
    debug set allocated[this] = 0
    debug set allocated[list] = 0
endmethod

Not sure if there are other gotchas, I haven't really tested it. I prefer to use simple lists/arrays because they are easier to use/implement, spot bugs while using them.

The use of macros for iteration is unappealing in my opinion.

I assume that by "transversal" you mean traversal (tree traversal).
It looks like the TreeListTransversal module implements a depth-first pre-order traversal (I think?).
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
I can't think of a system or a game mechanic that could possible need/use such a representation.
I once planned to represent a multidimensional array using this.

It doesn't seem like calling destroy on the tree from the "nested iteration sample:" would clean up all the created nodes.
I forgot this, it should call flush(true) inside it.

The use of macros for iteration is unappealing in my opinion.
Normally, i would suggest manually writing an iterator if it's just for iterating like 2 levels or less, but it would be helpful in case of some really deep nesting so that you don't have to spam additional variable declarations and also make the code easier to read (atleast for some).

I assume that by "transversal" you mean traversal (tree traversal).
It looks like the TreeListTransversal module implements a depth-first pre-order traversal (I think?).
Another gotcha =)
 
Status
Not open for further replies.
Top