• 🏆 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] Any Type Group System

Level 17
Joined
Apr 27, 2008
Messages
2,455
ChangeLog

v0.2
- method clean added
- method getRandom added
- add the set to 0 the Inc integer variable on top of functions in case of reach a limit op
- remove the uneeded local triggercondition in the forGroup method and remplace it by a global
- warning message to avoid desync with mac users with the method forGroup added
- removed an unused variable integer
- improved the methods copy and addGroup to avoid adding a null thing and return an integer instead of a boolean
- made a cleaner example

v0.1
- Initial release

JASS:
//==============================================================================
//   ATGS -> Any Type Group System -- v0.2
//==============================================================================
//
// PURPOUSE:
//  * Use structs and methods for simulate an using of group for any type.
//
//
// HOW TO USE:
//  * Check the test trigger and the last lines in this trigger.
//
//
// PROS: 
//  * Can easily use a "group" for any type (but you need to know vJass).
//
//  * You can add the same thing many times in the group, but only if you really
//    want it of course, with the method altAdd.
//
//  * Not as the natives functions, it return a boolean when you use the methods,
//    to know if it was done or not.
//
//
// CONS:
//  * It's not very fast, if you really care about it, just use arrays ... 
//
//  * You need to remove yourself a thing in a group, not as in an unit group when
//    an unit is removed of the game.
//
//
// DESCRIPTION OF THE METHODS:
//  * create : Just create a new group.
//
//  * add : Add the thing to the group, if the thing is already in the group,
//          then it doesn't add it. Return true if add it and false if not.
//          It will be slower as the group is bigger.
//
//  * altAdd : Add the thing to the group but it doesn't check if it is already
//             in group or not, so it is fast but not safe. It can be use too if
//             you want to add the same thing in the group.
//
//  * remove : Remove the thing to the group. Return true if remove it and false if not.
//             It doesn't check if the thing is many times in group or not.
//             It will be slower as the group is bigger.
//
//  * altRemove : Remove the thing of the group as many times as you want.
//                Use the argument 0 for remove totally the thing.
//                It return the how many times the thing was removed
//                It will be slower as the group is bigger.
//
//  * clear : Just prepare the group for an other usage. it doesn't clear the memory,
//            as the clear natives. It just set an integer variable to 0, so it's fast
//
//  * altClear : Prepare the group for an other usage and clean the memory.
//               It will be slower as the group is bigger.
//
//  * isInGroup : Return true if the thing is in group and false if not.
//                It will be slower as the group is bigger.
//
//  * forGroup : Just like the native ForGroup, but you need to use a textmacro
//               at the top of your function callback. Check the trigger test.
//               Like the native ForGroup it doesn't support TriggerSleepAction,
//               and so of course neither the PolledWait.
//   >>>>  USE A FUNCTION WHICH RETURN A BOOLEAN TO AVOID DESYNC WITH MAC USERS  <<<<
//               It will be slower as the source group is bigger.
//
//  * copy : Copy a group. Return true if it was done and false if not.
//           It will be slower as the source group is bigger.
//
//  * addGroup : Add a group to the source group. Return true if it was done and false if not.
//               It will be slower as the source group is bigger.
//
//  * clean : Remove all null things on the group. It will be slower as the source group is bigger. 
//
//  * getRandom : Get a random thing in the group, return the null type if the group is empty
//
//
//  SPECIAL THANKS TO: 
//       * Vexorian for the vJass.
//
//
//  HOW TO IMPORT:
//       * copy/paste this trigger.
//
//==============================================================================

//! textmacro Groups takes TYPE,NULL_TYPE,STRUCT_ARRAY,GROUP_ARRAY

    struct s_$TYPE$Group[$STRUCT_ARRAY$]
 
     public  integer Index // I don't use private because you may want to read the value, but don't set it ...
     public  $TYPE$ Enum // I don't use private to avoid a function call for the method forForce, but don't use it.
     public  static s_$TYPE$Group TB$TYPE$Group // I don't use private to avoid a function call for the method forForce, but don't use it
     private integer array Copy[$GROUP_ARRAY$]
     private $TYPE$ array Group[$GROUP_ARRAY$] 
     private static trigger T= CreateTrigger()
     private static integer Inc= 0
     private static triggercondition Cond
  
        static method create takes nothing returns s_$TYPE$Group
         local s_$TYPE$Group s= s_$TYPE$Group.allocate()
     
            set s.Index=0
        
         return s
        endmethod
    
        method add takes $TYPE$ t returns boolean
         local integer i= .Index
         
            if .Index== $GROUP_ARRAY$ then
                debug call BJDebugMsg(s_$TYPE$Group.add.name+" the group is full, remove before add again")
                return false
            elseif t== $NULL_TYPE$ then
                debug call BJDebugMsg(s_$TYPE$Group.add.name+" $TYPE$ is $NULL_TYPE$")
                return false
            endif
    
            loop
             exitwhen i==0
             set i=i-1
                 
                if .Group[i]== t then
                    return false
                endif
                                    
            endloop
            
            set .Group[.Index]=t
            set .Index= .Index+1
            
         return true
        endmethod
        
        private method altAdd takes $TYPE$ t returns boolean
         local integer i= .Index
         
            if .Index== $GROUP_ARRAY$ then
                debug call BJDebugMsg(s_$TYPE$Group.altAdd.name+" the group is full, remove before add again")
                return false
            elseif t== $NULL_TYPE$ then
                debug call BJDebugMsg(s_$TYPE$Group.altAdd.name+" $TYPE$ is $NULL_TYPE$")
                return false
            endif
    
            set .Group[.Index]=t
            set .Index= .Index+1
            
         return true
        endmethod
        
        method remove takes $TYPE$ t returns boolean
         local integer i= .Index
         
         if t== $NULL_TYPE$ then
            debug call BJDebugMsg(s_$TYPE$Group.remove.name+" $TYPE$ is $NULL_TYPE$")
            return false
         endif
         
            loop
             exitwhen i==0
             set i=i-1
             
                if .Group[i]== t then
                    set .Index= .Index-1
                    set .Group[i]=.Group[.Index]
                    set .Group[.Index]= $NULL_TYPE$
                    return true
                endif
                
            endloop
         return false
        endmethod
        
        method altRemove takes $TYPE$ t, integer nbr returns integer
         local integer i= .Index
         
            if t== $NULL_TYPE$ then
                debug call BJDebugMsg(s_$TYPE$Group.remove.name+" $TYPE$ is $NULL_TYPE$")
             return 0
            elseif nbr< 0 then

            endif
            
            if nbr== 0 then
                set nbr= i
            endif
        
            set .Inc= 0
            
            loop
             exitwhen i==0 or nbr==0
             set i=i-1
             
                if .Group[i]== t then
                    set .Copy[.Inc]=i
                    set .Inc= .Inc+1
                    set nbr= nbr-1
                endif
              
            endloop
            set i=.Inc
            
            loop
             exitwhen .Inc== 0
             set .Inc= .Inc-1
                     
                set .Index= .Index-1
                set .Group[.Copy[.Inc]]=.Group[.Index]
                set .Group[.Index]= $NULL_TYPE$
                set .Copy[.Inc]= 0
                        
            endloop
            
         return i
        endmethod
     
        method clear takes nothing returns nothing
            set .Index= 0
        endmethod
     
        method altClear takes nothing returns nothing
      
            loop
             exitwhen .Index== 0
             set .Index= .Index-1
             
                set .Group[.Index]=$NULL_TYPE$
            
            endloop
        
        endmethod
     
        method isInGroup takes $TYPE$ t returns boolean
        local integer i= .Index
        
        if t== $NULL_TYPE$ then
            debug call BJDebugMsg(s_$TYPE$Group.isInGroup.name+" $TYPE$ is $NULL_TYPE$")
            return false
        endif
        
            loop
             exitwhen i== 0
             set i= i-1
            
                if t== .Group[i] then
                    return true
                endif
         
            endloop
        
            return false
        endmethod
        
        method forGroup takes code c returns nothing
         local integer i= .Index

            set .Cond= TriggerAddCondition(.T,Condition(c))
            loop
             exitwhen i== 0
             set i =i-1
             
             set .Enum= .Group[i]
             set .TB$TYPE$Group= this
             call TriggerEvaluate(.T)
                        
            endloop
            
            set .Enum= $NULL_TYPE$
            set .TB$TYPE$Group= 0
            call TriggerRemoveCondition(.T,.Cond)
            
        endmethod
        
        method copy takes s_$TYPE$Group s returns integer
         local integer i= .Index
         
            set .Inc=0
            if s== 0 then
                debug call BJDebugMsg(s_$TYPE$Group.copy.name+ " the destGroup is an invalid struct")
                return 0
            endif
            
            loop
             exitwhen i==0
             set i= i-1
             
                if .Group[i]!= $NULL_TYPE$ then
                    set s.Group[i]= .Group[i]
                    set .Inc= .Inc+1
                endif
                
            endloop
            set s.Index= s.Index+.Inc
            
         return .Inc    
        endmethod
        
        method addGroup takes s_$TYPE$Group s returns integer
         local integer i= s.Index
        
            if s== 0 then
                debug call BJDebugMsg(s_$TYPE$Group.addGroup.name+ " the sourceGroup is an invalid struct")
                return 0
            elseif s.Index== 0 then
                debug call BJDebugMsg(s_$TYPE$Group.addGroup.name+ " the sourceGroup is empty")
                return 0
            elseif s.Index+.Index > $GROUP_ARRAY$ then
                debug call BJDebugMsg(s_$TYPE$Group.addGroup.name+ " to many things to add for the destgroup size")
                return 0
            endif
            set .Inc=0
            
            loop
             exitwhen i == 0
             set i= i-1
            
                if s.Group[i]!= $NULL_TYPE$ then
                    set .Group[.Index+i]= s.Group[i]
                    set .Inc= .Inc+1
                endif
            
            endloop
            set .Index= .Index+.Inc
         
         return .Inc
        endmethod
        
        method clean takes nothing returns integer
         local integer i= .Index
        
            set .Inc=0
            loop
             exitwhen i== 0
             set i= i-1
             
                if .Group[i]== $NULL_TYPE$ then
                    set .Index= .Index-1
                    set .Group[i]= .Group[.Index]
                    set .Group[.Index]= $NULL_TYPE$
                    set .Inc= .Inc+1 
                
                endif
                
            endloop
         return .Inc
        endmethod
        
        method getRandom takes nothing returns $TYPE$
            if .Index==0 then
                return $NULL_TYPE$
                debug call BJDebugMsg(s_$TYPE$Group.getRandom.name+ " the group is empty")
            endif
            
            loop
             exitwhen .Index== 0
                
                set .Inc= GetRandomInt(0,.Index-1)
                
                if .Group[.Inc]!= $NULL_TYPE$ then
                    return .Group[.Inc]
                endif
                
                set .Index= .Index-1
                
                if .Index!= .Inc then
                    set .Group[.Inc]= .Group[.Index]
                    set .Group[.Index]= $NULL_TYPE$
                endif
                
            endloop
         return $NULL_TYPE$    
        endmethod
        
    endstruct
    
//! endtextmacro

//! textmacro InitEnum takes STRUCT_VARIABLE_NAME,TYPE,ENUM_VARIABLE_NAME
    local s_$TYPE$Group $STRUCT_VARIABLE_NAME$= s_$TYPE$Group.TB$TYPE$Group
    local $TYPE$ $ENUM_VARIABLE_NAME$= $STRUCT_VARIABLE_NAME$.Enum
//! endtextmacro

// THIS IS AN EXAMPLE :

//! runtextmacro Groups("destructable","null","8190","100")

Example :

JASS:
scope Test initializer init

// to avoid a desynch with mac users use a fonction which return a boolean
    private function LoopforGroup takes nothing returns boolean // use a function that takes nothing
     //! runtextmacro InitEnum("s","destructable","d")

        call BJDebugMsg(GetDestructableName(d))
        call s.remove(d)
    
     return true
    endfunction

    private function Actions takes nothing returns nothing
     local s_destructableGroup s = s_destructableGroup.create()
 
        call s.add(CreateDestructable('LTlt',0.0,0.0,0.0,0.0,0))
        call s.add(CreateDestructable('LTba',0.0,0.0,0.0,0.0,0))
        call s.add(CreateDestructable('LOcg',0.0,0.0,0.0,0.0,0))
        call s.forGroup(function LoopforGroup)
        call s.destroy()
    
    endfunction
    
    private function init takes nothing returns nothing
     local trigger t= CreateTrigger()
     
        call TriggerRegisterPlayerEventEndCinematic( t, Player(0) ) // press the escape key during the game
        call TriggerAddAction( t, function Actions )
        
    endfunction

endscope
 
Last edited:
Level 11
Joined
Feb 18, 2004
Messages
394
This would be much better implemented using linked lists...

20 or so minutes of coding:
JASS:
library AnyTypeGroup

//! textmacro GroupDeclare takes GNAME, GTYPE, GNULL
scope $GNAME$GroupScope

function interface $GNAME$ForGroup takes $GNAME$ sourceGroup, $GTYPE$ value, integer tag returns nothing

private struct $GNAME$Node
    $GNAME$Node next = 0
    $GNAME$Node prev = 0
    
    $GTYPE$ value
endstruct

struct $GNAME$
    $GNAME$Node first = 0
    $GNAME$Node last  = 0
    integer nodeCount = 0
    
    method add takes $GTYPE$ value returns nothing
        local $GNAME$Node n = $GNAME$Node.create()
        
        set n.value = value
        
        set n.next = this.first
        set this.first.prev = n
        set this.first = n
        
        set this.nodeCount = this.nodeCount + 1
    endmethod
    
    method addGroup takes $GNAME$ from returns nothing
        local $GNAME$Node enum = from.first
        
        loop
            exitwhen enum == 0
            
            call this.add(enum.value)
            
            set enum = enum.next
        endloop
    endmethod
    
    method remove takes $GTYPE$ value returns boolean
        local $GNAME$Node enum = this.first
        
        loop
            exitwhen enum == 0
            
            if enum.value == value then
                if this.first == enum then
                    set this.first = enum.next
                endif
                
                if this.last == enum then
                    set this.last = enum.prev
                endif
                
                if enum.prev != 0 then
                    set enum.prev.next = enum.next
                endif
                
                if enum.next != 0 then
                    set enum.next.prev = enum.prev
                endif
                
                call enum.destroy()
                
                set this.nodeCount = this.nodeCount - 1
                
                return true
            endif
            
            set enum = enum.next
        endloop
        
        return false
    endmethod
    
    method clear takes nothing returns nothing
        local $GNAME$Node enum = this.first
        
        loop
            exitwhen enum == 0
            
            if enum.next == 0 then
                call enum.destroy()
                exitwhen true
            else
                set enum = enum.next
                call enum.prev.destroy()
            endif
        endloop
        
        set this.first = 0
        set this.last  = 0
        
        set this.nodeCount = 0
    endmethod
    
    method containsValue takes $GTYPE$ value returns boolean
        local $GNAME$Node enum = this.first
        
        loop
            exitwhen enum == 0
            
            if enum.value == value then
                return true
            endif
            
            set enum = enum.next
        endloop
        
        return false
    endmethod
    
    method getRandom takes nothing returns $GTYPE$
        local $GNAME$Node enum = this.first
        local integer goto = GetRandomInt(0, this.nodeCount - 1)
        local integer i = 0
        
        loop
            if i == goto then
                return enum.value
            endif
            
            set i = i + 1
            set enum = enum.next
        endloop
        
        return $GNULL$
    endmethod
    
    method forGroup takes $GNAME$ForGroup func, integer tag returns nothing
        local $GNAME$Node enum = this.first
        
        loop
            exitwhen enum == 0
            
            call func.evaluate(this, enum.value, tag)
            
            set enum = enum.next
        endloop
    endmethod
endstruct

endscope
//! endtextmacro

endlibrary

(Stupid need for $GNULL$ >_>) Possible problem I have yet to remedy: calling .remove(value) from a forgroup callback can possibly fuck up iteration. (I'm just too lazy to solve that problem right now...)
 
Last edited:
Level 40
Joined
Dec 14, 2005
Messages
10,532
EF, a small optimization of .remove:

JASS:
    method remove takes $GTYPE$ value returns boolean
        local $GNAME$Node enum = this.first

        loop
            exitwhen enum == 0

            if enum.value == value then
                if this.first == enum then
                    set this.first = enum.next
                else
                    set enum.prev.next = enum.next
                endif

                if this.last == enum then
                    set this.last = enum.prev
                else
                    set enum.next.prev = enum.prev
                endif

                call enum.destroy()

                set this.nodeCount = this.nodeCount - 1

                return true
            endif

            set enum = enum.next
        endloop

        return false
    endmethod
 
Level 11
Joined
Feb 18, 2004
Messages
394
New code:
JASS:
library AnyTypeGroup

//! textmacro GroupDeclare takes GNAME, GTYPE
scope $GNAME$GroupScope

function interface $GNAME$ForGroup takes $GNAME$ sourceGroup, $GTYPE$ value, integer tag returns nothing

private struct $GNAME$Node
    $GNAME$Node next = 0
    $GNAME$Node prev = 0

    $GTYPE$ value
endstruct

struct $GNAME$
    $GNAME$Node first = 0
    $GNAME$Node last = 0
    integer nodeCount = 0

    method add takes $GTYPE$ value returns nothing
        local $GNAME$Node n = $GNAME$Node.create()

        set n.value = value

        // Linked List operations:
        set n.prev = this.last
        set this.last.next = n
        set this.last = n
        
        if this.first == 0 then
            set this.first = n
        endif

        // Node Counting:
        set this.nodeCount = this.nodeCount + 1
    endmethod

    method addGroup takes $GNAME$ from returns nothing
        local $GNAME$Node enum = from.first

        loop
            exitwhen enum == 0

            call this.add(enum.value)

            set enum = enum.next
        endloop
    endmethod

    method remove takes $GTYPE$ value returns boolean
        local $GNAME$Node enum = this.first

        loop
            exitwhen enum == 0

            if enum.value == value then
                // Iteration:
                if enum == this.now then
                    if this.now.next == 0 then
                        set this.now = 0
                    elseif this.now.prev == 0 then
                        set this.now = -1
                    else
                        set this.now = this.now.prev
                    endif
                endif
                // End Iteration
            
                if this.first == enum then
                    set this.first = enum.next
                else
                    set enum.prev.next = enum.next
                endif

                if this.last == enum then
                    set this.last = enum.prev
                else
                    set enum.next.prev = enum.prev
                endif

                call enum.destroy()

                set this.nodeCount = this.nodeCount - 1

                return true
            endif

            set enum = enum.next
        endloop

        return false
    endmethod

    method clear takes nothing returns nothing
        local $GNAME$Node enum = this.first

        loop
            exitwhen enum == 0

            if enum.next == 0 then
                call enum.destroy()
                exitwhen true
            else
                set enum = enum.next
                call enum.prev.destroy()
            endif
        endloop

        set this.first = 0
        set this.last = 0

        set this.nodeCount = 0
        
        // Iteration:
        set this.now = 0
    endmethod

    method containsValue takes $GTYPE$ value returns boolean
        local $GNAME$Node enum = this.first

        loop
            exitwhen enum == 0

            if enum.value == value then
                return true
            endif

            set enum = enum.next
        endloop

        return false
    endmethod

    method getRandom takes nothing returns $GTYPE$
        local $GNAME$Node enum = this.first
        local integer goto = GetRandomInt(0, this.nodeCount - 1)
        local integer i = 0

        loop
            if i == goto then
                return enum.value
            endif

            set i = i + 1
            set enum = enum.next
        endloop

        return enum.value // Will never be reached. Prevents syntax error.
    endmethod

    method forGroup takes $GNAME$ForGroup func, integer tag returns nothing
        loop
            exitwhen not this.pointerStep()

            call func.evaluate(this, this.getEnum(), tag)
        endloop
    endmethod
    
    // Iteration API:
    
    $GNAME$Node now = -1
    
    method pointerStep takes nothing returns boolean
        if this.now == -1 then
            set this.now = this.first
            return true
        elseif this.now == 0 or this.now.next == 0 then
            set this.now = this.first
            return false
        endif
        
        set this.now = this.now.next
        
        return true
    endmethod
    
    method pointerReset takes nothing returns nothing
        set this.now = -1
    endmethod
    
    method getEnum takes nothing returns $GTYPE$
        return this.now.value
    endmethod
endstruct

endscope
//! endtextmacro

endlibrary

an example of iterating through a group without a function callback:
JASS:
//! runtextmacro GroupDeclare("IntGroup", "integer")

function InitTrig_testing takes nothing returns nothing
    local IntGroup g = IntGroup.create()
    
    call BJDebugMsg("Adding")
    call g.add(1)
    call g.add(2)
    call g.add(3)
    call g.add(4)
    call g.add(5)
    call g.add(6)
    call g.add(99)
    call g.add(42)
    
    loop
        exitwhen not g.pointerStep()
        
        call BJDebugMsg("Echo: " + I2S(g.getEnum()))
        
        if g.getEnum() == 99 then
            call g.remove(g.getEnum())
            call g.pointerReset()
        endif
        
    endloop
endfunction
Output is:
1
2
3
...
6
99
1
2
3
...
6
42

My groups are safe for all values, including null. (Meaning it is perfectly acceptable to add null to say, a destructable group.)

Planned future additions: a full deque implementation with Push and Pop to front and back, "random access" using an O(n) search... anything else?
 
Top