Snippet UnitList

Troll-Brain

You can change this now in User CP.
Reaction score
85
JASS:
library UnitList requires AutoIndex

struct UnitList

    readonly integer Count = 0
    readonly thistype First = 0
    readonly thistype Next = 0
    readonly thistype Prev = 0
    readonly thistype Last = 0
    
    implement AutoDestroy
    
    method clear takes nothing returns nothing
        local thistype s = .First
        
        set thistype.ignoreDestroy = true
        loop
        exitwhen s == 0
            call s.destroy()
            set s = s.Next
        endloop
        set .Count = 0
        call FlushChildHashtable(thistype.hashT,.whichList)
        set thistype.ignoreDestroy = false
    endmethod
    
    method onDestroy takes nothing returns nothing

        if thistype.ignoreDestroy then
            return
        endif
        
        if this == .whichList then // the whole group is destroyed
            call .clear()
            return
        endif
        
        set .whichList.Count = .whichList.Count-1
        call RemoveSavedInteger(thistype.hashT,.whichList,GetUnitId(.me))
        
        if this == .whichList.First then
            set .Prev = 0
            set .whichList.First = .Next
        elseif this == .whichList.Last then
            set .Prev.Next = 0
            set .whichList.Last = .Prev
        else
            set .Prev.Next = .Next
            set .Next.Prev = .Prev
        endif
    endmethod
    
    method addUnit takes unit u returns boolean
    
        if LoadInteger(thistype.hashT,this,GetUnitId(u)) != 0 then // the unit is already inside the group
            return false
        endif
        
        if .Count == 0 then // the group was empty
            set .First = thistype.allocate()
            set .Last = .First
        else
            set .Last.Next = thistype.allocate()
            set .Last.Next.Prev = .Last
            set .Last = .Last.Next
        endif
        set .Last.me = u
        set .Last.whichList = this
        call SaveInteger(thistype.hashT,this,GetUnitId(u),integer(.Last))
        set .Count = .Count+1
        
        return true
        
    endmethod
    
    //implement AutoDestroy
    
    method removeUnit takes unit u returns boolean
    
        if LoadInteger(thistype.hashT,this,GetUnitId(u)) == 0 then // the unit wasn't inside the group
            return false
        endif
        call thistype(LoadInteger(thistype.hashT,this,GetUnitId(u))).destroy()
        return true
    endmethod
    
    method isUnitInList takes unit u returns boolean
        return LoadInteger(thistype.hashT,this,GetUnitId(u)) != 0
    endmethod
    
    method addUnitList takes UnitList whichUnitList returns nothing
        set whichUnitList = whichUnitList.First
        loop
        exitwhen whichUnitList == 0
            call .addUnit(whichUnitList.me)
            set whichUnitList = whichUnitList.Next
        endloop
    endmethod
    
    static method wrap takes group g returns UnitList
        if not SaveGroupHandle(thistype.hashT,0,0,g) then // group invalid
            return 0
        endif
        set thistype.wrapList = thistype.allocate()
        call ForGroup(g,function thistype.WrapList)

        return thistype.wrapList
    endmethod
    
    
    private static method WrapList takes nothing returns nothing
        call thistype.wrapList.addUnit(GetEnumUnit())
    endmethod
    
    private thistype whichList = 0
    private static hashtable hashT
    private static boolean ignoreDestroy = false
    private static thistype wrapList = 0
    
    private static method onInit takes nothing returns nothing
        set thistype.hashT = InitHashtable()
    endmethod
    
endstruct

endlibrary


- What is that ?
Ever heard about ghost units ?
Those units which are removed of the game but not inside the group they were.

- What's the problem with ghost units ?
If a group has many ghost units, we can have performance issues, also we can't safely use a FirstOfGroup, exitwhen <unit> == null loop.

This, automatically removes units when they are about to removed, so you can't have ghost units at all.
And that makes group recycling fairly useless.

Also if include a group global, GroupUtils becomes pointless imho.

I don't make comments, because i suppose if it has enough good feedbacks Jesus4Lyf will just make it from scratch using AIDS (needs an update of it though) *shrugs*
So i don't want to lose time, since making English comments is the most annoying part of a submission for me.

Any comments are welcome.
 

Narks

Vastly intelligent whale-like being from the stars
Reaction score
90
I don't understand the purpose of this. Also, there is no documentation, so I do not understand how to use this.
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
I will post samples this day or tomorrow.

If you use groups only for an instant enumeration of units you just don't need this.
But if you want to keep a bunch of units during time, this is useful.

The real con is that destroying/clearing a group is O(N) , N == number of units inside the group.
That shouldn't matter that much though, unless for some reasons you want an huge UnitList.

It's usage is like unit group, but slower.
Sure it's slower but not that much, and fix the ghost unit problem at very least.
And again, if you don't need to keep the track of units during time, this is useless.
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
The linked lists should be circularly linked lists.
Hmm, i know only simple and double linked list, i will take a look about circular linked list and how it could be better here.

ForGroup does not have ghost problems, does it?
It won't enumerate a ghost unit if it's that you want mean.

If we don't use FirstOfGroup, this is completely unnecessary, right?
There is still the performance issue because of ghost units.
Also having a .Count and a double linked list unit is kinda useful.
You don't have to split the code in several functions (i'm talking about the ForGroup alternative)
Note that the units will be kept in order they were added, not like a group.
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
How a circular double linked list would be more useful here ?
For me it's more confusing to loop through the list (sure we could use .Count), but why ?
 

Jesus4Lyf

Good Idea™
Reaction score
397
How a circular double linked list would be more useful here ?
Cleans this up:
JASS:
        if this == .whichList.First then
            set .Prev = 0
            set .whichList.First = .Next
        elseif this == .whichList.Last then
            set .Prev.Next = 0
            set .whichList.Last = .Prev
        else
            set .Prev.Next = .Next
            set .Next.Prev = .Prev
        endif

-->
JASS:
set .Prev.Next=.Next
set .Next.Prev=.Prev

[LJASS]group[/LJASS]s seem to internally use a BST or something, so they're actually much more efficient for checking if a unit is in a group. Also, just because the code is native, the destroy functionality is inherently faster. Since there isn't a problem with ForGroup either, I'm not sure that this is useful...
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
Cleans this up:
JASS:
        if this == .whichList.First then
            set .Prev = 0
            set .whichList.First = .Next
        elseif this == .whichList.Last then
            set .Prev.Next = 0
            set .whichList.Last = .Prev
        else
            set .Prev.Next = .Next
            set .Next.Prev = .Prev
        endif

-->
JASS:
set .Prev.Next=.Next
set .Next.Prev=.Prev

[LJASS]group[/LJASS]s seem to internally use a BST or something, so they're actually much more efficient for checking if a unit is in a group. Also, just because the code is native, the destroy functionality is inherently faster. Since there isn't a problem with ForGroup either, I'm not sure that this is useful...

If you do that you won't be able to start to the last unit or i'm missing something.
I hate ForGroup because it splits the code, also even if you can't enum a ghost unit with it, that doesn't mean it doesn't slow all the thing when the group has many ghost units.
Hmm, i will do benchmarks, and if this is slower than a group with 100 ghost unit then i willl admit this is kinda useless, even if i like the syntax and the little other features.
 

Jesus4Lyf

Good Idea™
Reaction score
397
If you do that you won't be able to start to the last unit or i'm missing something.
Eh?

>if this is slower than a group with 100 ghost unit then i willl admit this is kinda useless
The problem further is, if you think about it, this is only used when you need to store units for x seconds. When you store a list of units for x seconds and then use it, you'd usually then destroy it. That means the efficiency, in many or maybe even most cases, will be not just the efficiency of iteration, but destruction.

I suppose a bigger question is why you're using an indexing system. Why not just leave ghost units in there until iteration occurs, anyway... but then you have this "count" variable. And then I wonder why, because in many situations, who cares about count. And then I consider this to be one of the things that users should write themselves for specific applications, and the real issue here is that Romek needs to finish his linked-list tut. ;)

PS. I actually expect the iteration of this to be faster than a [LJASS]group[/LJASS] if properly done. My problem is I think it should be a singularly linked list.
 

Troll-Brain

You can change this now in User CP.
Reaction score
85

Be able to loop trough the list and begin with the last unit could be handy sometimes.

Jesus4Lyf said:
The problem further is, if you think about it, this is only used when you need to store units for x seconds.

Exactly, and it's only useful for that, i've said it several times on this thread, else just use a static group like GROUP_ENUM of GroupUtils.

When you store a list of units for x seconds and then use it, you'd usually then destroy it. That means the efficiency, in many or maybe even most cases, will be not just the efficiency of iteration, but destruction.

Not always, especially when you need to keep the track of units during the whole game.
I will post a whole system using it if you don't get this point.

I suppose a bigger question is why you're using an indexing system. Why not just leave ghost units in there until iteration occurs, anyway...
Performance issue ?

but then you have this "count" variable. And then I wonder why, because in many situations, who cares about count. And then I consider this to be one of the things that users should write themselves for specific applications

Meh, it's not something which will slow down all the thing and could be handy sometimes.
Yourself include things that nobody will ever use in your resources.

and the real issue here is that Romek needs to finish his linked-list tut. ;)

Lol, i'm not that noob, but i'm agree i should make benchmarks before post this thing.

PS. I actually expect the iteration of this to be faster than a [LJASS]group[/LJASS] if properly done. My problem is I think it should be a singularly linked list.
But a singulary linked list is lame when you want to remove an index, right ?
 

Jesus4Lyf

Good Idea™
Reaction score
397
>Be able to loop trough the list and begin with the last unit could be handy sometimes.
Then start from the other end? Circular lists don't change this..

>Exactly, and it's only useful for that, i've said it several times on this thread, else just use a static group like GROUP_ENUM of GroupUtils.
My point was the statement which followed, excuse that.

>Not always, especially when you need to keep the track of units during the whole game.
Fair enough, but there's so many ways to do this. It can be done with GUI and an array of units (which should be faster to iterate through, I think).

>>I suppose a bigger question is why you're using an indexing system. Why not just leave ghost units in there until iteration occurs, anyway...
>Performance issue ?
No, this:
>But a singulary linked list is lame when you want to remove an index, right ?
If you switch to a singularly linked list, and don't try to remove ghosted units, everything is fine. I'm thinking for example a spell that deals damage after x seconds.

>Lol, i'm not that noob, but i'm agree i should make benchmarks before post this thing.
I meant the tut should make this redundant, because then everyone can write it, exactly the way they need it, in 3 minutes.
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
Then start from the other end? Circular lists don't change this..
Hmm, you're probably right i had never use a circular list, i will take a better look for it.

Fair enough, but there's so many ways to do this. It can be done with GUI and an array of units (which should be faster to iterate through, I think).

Again, what about ghost units :D
Other ways you have in mind ?

If you switch to a singularly linked list, and don't try to remove ghosted units, everything is fine. I'm thinking for example a spell that deals damage after x seconds.
No, ghost units are the devil, they revived the return bug, they even invented TriggerSleepAction itself !

Meh, i suppose the next post will be a concrete example of usage with benchmarks included.

Thx for the feedback.
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
Just a quick test to show how ghost units are lame :

Comparison:
[LJASS]ForGroup with 100 ghost units[/LJASS] vs [LJASS]ForGroup without ghost units[/LJASS]​

Approximate Results & Conclusions:
  • Tested on Warcraft III Version 1.24.3.6384
  • [LJASS]ForGroup without ghost units[/LJASS] is 53 % faster than [LJASS]ForGroup with 100 ghost units[/LJASS]
  • [LJASS]ForGroup without ghost units[/LJASS] is 18.4 % faster than [LJASS]ForGroup with 32 ghost units[/LJASS] (with ghost units : 7.612 ; without ghost units : 6.429)
    Ofc it will become faster with less ghost units, and slower with more.

Comments & Personal Criticism:
  • So even if a ForGroup doesn't enum ghost units, ghost units affects the performance of the group.
  • That's the same with IsUnitInGroup, you have the same result with X ghost units and Y real units against X+Y real units.

Code:
JASS:
library Benchmark initializer OnInit
    ///////////////////////////////////////////////
    // Native declarations for stopwatch natives //
    //  - Requires no modified common.j import   //
    ///////////////////////////////////////////////
    native StopWatchCreate  takes nothing returns integer
    native StopWatchMark    takes integer stopwatch returns real
    native StopWatchDestroy takes integer stopwatch returns nothing
    
    /////////////////////////
    // Benchmarking script //
    /////////////////////////
    
    // Initialisation
    globals
        private group Grp1 // with ghost units
        private group Grp2 //  without ghost units
        private UnitList Unt
        private unit U
        private boolean B = false
    endglobals
    
    private function Callback takes nothing returns nothing
        call DoNothing()
    endfunction
    
    private function Init takes nothing returns nothing
        local integer i = 0
        local unit u

        set Grp1 = CreateGroup()
        set Grp2 = CreateGroup()
        
        loop
        exitwhen i == 100
        
            set u = CreateUnit(Player(0),&#039;hfoo&#039;,0.,0.,0.)
            call GroupAddUnit(Grp1,u)
            call RemoveUnit(u)
            
        set i = i+1
        endloop
        set U = CreateUnit(Player(0),&#039;hfoo&#039;,0.,0.,0.)
        call GroupAddUnit(Grp1,U)
        call GroupAddUnit(Grp2,U)
        call BJDebugMsg(&quot;init done&quot;)
    endfunction
    
    // Tests
    globals
    private constant string TITLE_A=&quot;Grp1 with 100 ghost units&quot;
    //! textmacro Benchmark__TestA
        call ForGroup(Grp1,function Callback)
    //! endtextmacro
    private constant string TITLE_B=&quot;Grp2 without ghost units&quot;
    //! textmacro Benchmark__TestB
        call ForGroup(Grp2,function Callback)
    //! endtextmacro
    endglobals
    
    // execution
    private function TestA1000 takes nothing returns nothing
        local integer i=100 // hence 1,000 execs
        loop
            exitwhen i==0
            set i=i-1
            // Repeat x10
            //! runtextmacro Benchmark__TestA() // 1
            //! runtextmacro Benchmark__TestA() // 2
            //! runtextmacro Benchmark__TestA() // 3
            //! runtextmacro Benchmark__TestA() // 4
            //! runtextmacro Benchmark__TestA() // 5
            //! runtextmacro Benchmark__TestA() // 6
            //! runtextmacro Benchmark__TestA() // 7
            //! runtextmacro Benchmark__TestA() // 8
            //! runtextmacro Benchmark__TestA() // 9
            //! runtextmacro Benchmark__TestA() // 10
        endloop
    endfunction
    private function TestB1000 takes nothing returns nothing
        local integer i=100
        loop
            exitwhen i==0 // hence 1,000 execs
            set i=i-1
            // Repeat x10
            //! runtextmacro Benchmark__TestB() // 1
            //! runtextmacro Benchmark__TestB() // 2
            //! runtextmacro Benchmark__TestB() // 3
            //! runtextmacro Benchmark__TestB() // 4
            //! runtextmacro Benchmark__TestB() // 5
            //! runtextmacro Benchmark__TestB() // 6
            //! runtextmacro Benchmark__TestB() // 7
            //! runtextmacro Benchmark__TestB() // 8
            //! runtextmacro Benchmark__TestB() // 9
            //! runtextmacro Benchmark__TestB() // 10
        endloop
    endfunction
    
    private function OnEsc takes nothing returns nothing
        local integer sw
        local integer i
        
        set i=0
        set sw=StopWatchCreate()
        loop
            set i=i+1
            call TestA1000.evaluate() // x10 - 10,000 executions altogether.
            exitwhen i==10
        endloop
        call BJDebugMsg(TITLE_A+&quot;: &quot;+R2S(StopWatchMark(sw)*100))
        call StopWatchDestroy(sw)
        
        set i=0
        set sw=StopWatchCreate()
        loop
            set i=i+1
            call TestB1000.evaluate() // x10 - 10,000 executions altogether.
            exitwhen i==10
        endloop
        call BJDebugMsg(TITLE_B+&quot;: &quot;+R2S(StopWatchMark(sw)*100))
        call StopWatchDestroy(sw)
    endfunction
    
    ///////////////////////////////
    // Registers the OnEsc event //
    ///////////////////////////////
    private function OnInit takes nothing returns nothing
        local trigger t=CreateTrigger()
        call TriggerRegisterPlayerEvent(t,Player(0),EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddAction(t,function OnEsc)
        call Init()
    endfunction
endlibrary

@ Jesus4Lyf :
I don't see the point of you optimisation suggested (the circular linked list).

For loop through a group instead of doing that :

JASS:
    local UnitList g = Group.create()
    local unit u1 = CreateUnit(Player(0),&#039;hpea&#039;,0.,0.,0.)
    local unit u2 = CreateUnit(Player(0),&#039;hfoo&#039;,0.,0.,0.)
    local unit u3 = CreateUnit(Player(0),&#039;hkni&#039;,0.,0.,0.)
    local unit u4 = CreateUnit(Player(0),&#039;hspt&#039;,0.,0.,0.)
    local UnitList x
    
    call g.addUnit(u1)
    call g.addUnit(u2)
    call g.addUnit(u3)
    call g.addUnit(u4)
    
    set x = g.First // you could just use &#039;g&#039; instead of &#039;x&#039; but then you couldn&#039;t use .Count 
    
    loop
    exitwhen x == 0
        
        call BJDebugMsg(GetUnitName(x.me))
        set x = x.Next
    
    endloop


You should do that :

JASS:
    local UnitList g = Group.create()
    local unit u1 = CreateUnit(Player(0),&#039;hpea&#039;,0.,0.,0.)
    local unit u2 = CreateUnit(Player(0),&#039;hfoo&#039;,0.,0.,0.)
    local unit u3 = CreateUnit(Player(0),&#039;hkni&#039;,0.,0.,0.)
    local unit u4 = CreateUnit(Player(0),&#039;hspt&#039;,0.,0.,0.)
    local unit u
    local UnitList x
    
    call g.addUnit(u1)
    call g.addUnit(u2)
    call g.addUnit(u3)
    call g.addUnit(u4)
    
    set x = g.Next // you could just use &#039;g&#039; instead of &#039;x&#039; but then you couldn&#039;t use .Count 
    set u = x.me
    
    if u != null then // imagine units were added sooner in the game and not on the top of this function.

       loop
        
           call BJDebugMsg(GetUnitName(x.me))
           set x = x.Next
        
       exitwhen x.me == u
        
    
       endloop

   endif


Or i miss something ?
If not, then it's more lame, i prefer to the speed of list iteration against the speed of unit adding (maybe because of my special need), especially because we are talking about removing an if/then/elseif/else ...
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
An other benchmark.

Comparison:
[LJASS]ForGroup[/LJASS] vs [LJASS]UnitList iteration[/LJASS]​

Approximate Results & Conclusions:
  • Tested on Warcraft III Version 1.24.3.6384
  • [LJASS]UnitList iteration[/LJASS] is 320 % faster than [LJASS]ForGroup with 10 units[/LJASS]
  • [lJASS]ForGroup[/lJASS] : 56.664 ; UnitList iteration : 17.732
  • [LJASS]UnitList iteration[/LJASS] is 1350 % faster than [LJASS]ForGroup with 100 units[/LJASS]
  • [lJASS]ForGroup[/lJASS] : 547.865 ; UnitList iteration : 37.749

Comments & Personal Criticism:
  • It would be annoying to test a such loop :

    JASS:
    local unit u = FirstOfGroup(&lt;group&gt;)
    loop
    exitwhen u == null
       //code
       call GroupRemoveUnit(&lt;group&gt;,u)
       set u = FirstOfGroup(&lt;group&gt;)
    endloop

    But for ghost units you would need extra checks (GetUnitTypeId(u) != 0), and anyway if you want to keep the group, you have to store the units again, so i don't see how this method could be faster than a ForGroup callback, even in the best scenario.

Code:
JASS:
library Benchmark initializer OnInit requires UnitList
    ///////////////////////////////////////////////
    // Native declarations for stopwatch natives //
    //  - Requires no modified common.j import   //
    ///////////////////////////////////////////////
    native StopWatchCreate  takes nothing returns integer
    native StopWatchMark    takes integer stopwatch returns real
    native StopWatchDestroy takes integer stopwatch returns nothing
    
    /////////////////////////
    // Benchmarking script //
    /////////////////////////
    
    // Initialisation
    globals
        private unit U
        private group Grp
        private UnitList UnL
        private UnitList X
        private constant integer UNIT_NUMBER = 10
    endglobals
    
    private function Init takes nothing returns nothing
        local unit u
        local integer i = 0
        
        set Grp = CreateGroup()
        set UnL = UnitList.create()
        
        loop
        exitwhen i == UNIT_NUMBER
        
            set u = CreateUnit(Player(0),&#039;hfoo&#039;,0.,0.,0.)
            call GroupAddUnit(Grp,u)
            call UnL.addUnit(u)
        
        set i = i+1
        endloop
        call BJDebugMsg(&quot;init done&quot;)
    endfunction
    
    // Tests
    
    private function Callback takes nothing returns nothing
        set U = GetEnumUnit()
    endfunction

    globals
    
    private constant string TITLE_A=&quot;ForGroup&quot;
    //! textmacro Benchmark__TestA
        call ForGroup(Grp,function Callback)
    //! endtextmacro
    private constant string TITLE_B=&quot;UnitList&quot;
    //! textmacro Benchmark__TestB
        set X = UnL.First
        loop
        exitwhen X == 0
        
            set U = X.me
        
        set X = X.Next
        endloop
    //! endtextmacro
    endglobals
    
    // execution
    private function TestA1000 takes nothing returns nothing
        local integer i=100 // hence 1,000 execs
        loop
            exitwhen i==0
            set i=i-1
            // Repeat x10
            //! runtextmacro Benchmark__TestA() // 1
            //! runtextmacro Benchmark__TestA() // 2
            //! runtextmacro Benchmark__TestA() // 3
            //! runtextmacro Benchmark__TestA() // 4
            //! runtextmacro Benchmark__TestA() // 5
            //! runtextmacro Benchmark__TestA() // 6
            //! runtextmacro Benchmark__TestA() // 7
            //! runtextmacro Benchmark__TestA() // 8
            //! runtextmacro Benchmark__TestA() // 9
            //! runtextmacro Benchmark__TestA() // 10
        endloop
    endfunction
    private function TestB1000 takes nothing returns nothing
        local integer i=100
        loop
            exitwhen i==0 // hence 1,000 execs
            set i=i-1
            // Repeat x10
            //! runtextmacro Benchmark__TestB() // 1
            //! runtextmacro Benchmark__TestB() // 2
            //! runtextmacro Benchmark__TestB() // 3
            //! runtextmacro Benchmark__TestB() // 4
            //! runtextmacro Benchmark__TestB() // 5
            //! runtextmacro Benchmark__TestB() // 6
            //! runtextmacro Benchmark__TestB() // 7
            //! runtextmacro Benchmark__TestB() // 8
            //! runtextmacro Benchmark__TestB() // 9
            //! runtextmacro Benchmark__TestB() // 10
        endloop
    endfunction
    
    private function OnEsc takes nothing returns nothing
        local integer sw
        local integer i
        
        set i=0
        set sw=StopWatchCreate()
        loop
            set i=i+1
            call TestA1000.evaluate() // x10 - 10,000 executions altogether.
            exitwhen i==10
        endloop
        call BJDebugMsg(TITLE_A+&quot;: &quot;+R2S(StopWatchMark(sw)*100))
        call StopWatchDestroy(sw)
        
        set i=0
        set sw=StopWatchCreate()
        loop
            set i=i+1
            call TestB1000.evaluate() // x10 - 10,000 executions altogether.
            exitwhen i==10
        endloop
        call BJDebugMsg(TITLE_B+&quot;: &quot;+R2S(StopWatchMark(sw)*100))
        call StopWatchDestroy(sw)
    endfunction
    
    ///////////////////////////////
    // Registers the OnEsc event //
    ///////////////////////////////
    private function OnInit takes nothing returns nothing
        local trigger t=CreateTrigger()
        call TriggerRegisterPlayerEvent(t,Player(0),EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddAction(t,function OnEsc)
        call Init()
    endfunction
endlibrary
 

Deaod

Member
Reaction score
6
Minimize the overhead in your tests.
Use a new local for every enumeration.
Use a new local inside the Callback function. And null it properly.

For your tests with ghost units, use less ghosts. Ive read somewhere that groups are more efficient if theres 64 or less units in them.
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
Minimize the overhead in your tests.
Maybe i misunderstood you but there is not unnecessary stuff, compared to the tested stuff itself, or correct me if i'm wrong.

Use a new local for every enumeration.
Use a new local inside the Callback function. And null it properly.

Actually it's the same global variable and i don't see anything wrong with this.

For your tests with ghost units, use less ghosts. Ive read somewhere that groups are more efficient if theres 64 or less units in them.
The point of this test was to show that ghost units affects also ForGroup callbacks (not that obvious).
It's perfectly reasonable to think that a group which contain 10 units have 100 ghost units, if you keep it during a long time, but i will do the test with let's say 32 ghost units and edit the post.
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
And now the cons :D

Comparison:
[LJASS]GroupAddUnit[/LJASS] vs [LJASS]UnitList.addUnit[/LJASS]​

Approximate Results & Conclusions:
  • Tested on Warcraft III Version 1.24.3.6384
  • [LJASS]GroupAddUnit[/LJASS] is 166 % faster than [LJASS]UnitList.addUnit with 20 units (added units weren't already inside the group)[/LJASS]
  • [lJASS]GroupAddUnit[/lJASS] : 18.722 ; UnitList.addUnit : 49.493
  • [LJASS]GroupAddUnit[/LJASS] is 78.5 % faster than [LJASS]UnitList.addUnit with 20 units (added units were already inside the group)[/LJASS]
  • [lJASS]GroupAddUnit[/lJASS] : 18.380 ; UnitList.addUnit : 32.802

Comments & Personal Criticism:

I got the same kind of results with 50 units, i think it's kind of a miracle that UnitAddList is only 2.7 times slower than the native GroupAddUnit in the worst scenario, and only 1.8 times slower in the best one.

Code:
JASS:
library Benchmark initializer OnInit requires UnitList
    ///////////////////////////////////////////////
    // Native declarations for stopwatch natives //
    //  - Requires no modified common.j import   //
    ///////////////////////////////////////////////
    native StopWatchCreate  takes nothing returns integer
    native StopWatchMark    takes integer stopwatch returns real
    native StopWatchDestroy takes integer stopwatch returns nothing
    
    /////////////////////////
    // Benchmarking script //
    /////////////////////////
    
    // Initialisation
    globals
        private unit array U
        private group array Grp
        private UnitList array UnL
        private UnitList X
        private integer I = -1
        private integer J = -1
        private integer Index
        private constant integer UNIT_NUMBER = 20
        private constant integer GROUP_NUMBER = 190 // must be a multiple of 100
        // GROUP_NUMBER * (UNIT_NUMBER + 1) must be &lt;= 8190
    endglobals
    
    private function Init takes nothing returns nothing
        local integer i = 0
        
        loop
        exitwhen i == UNIT_NUMBER
        
            set U<i> = CreateUnit(Player(0),&#039;hfoo&#039;,0.,0.,0.)
        
        set i = i+1
        endloop
        
        set i = 0
        
        loop
        exitwhen i == GROUP_NUMBER
        
            set Grp<i> = CreateGroup()
            set UnL<i> = UnitList.create()
        
        set i = i+1
        endloop
        call BJDebugMsg(&quot;init done&quot;)
    endfunction
    
    // Tests
    
    globals
    
    private constant string TITLE_A=&quot;GroupAddUnit&quot;
    //! textmacro Benchmark__TestA
        set Index = 0
        set I = I+1
        loop
        exitwhen Index == UNIT_NUMBER
        
            call GroupAddUnit(Grp<i>,U[Index])
            
        set Index = Index+1
        endloop
    //! endtextmacro
    private constant string TITLE_B=&quot;UnitList.addUnit&quot;
    //! textmacro Benchmark__TestB
        set Index = 0
        set J = J+1
        loop
        exitwhen Index == UNIT_NUMBER
        
            call UnL[J].addUnit(U[Index])
            
        set Index = Index+1
        endloop
    //! endtextmacro
    endglobals
    
    // execution
    private function TestA1000 takes nothing returns nothing
        local integer i=10 
        loop 
            exitwhen i==0
            set i=i-1
            
            //! runtextmacro Benchmark__TestA() // 1
            //! runtextmacro Benchmark__TestA() // 2
            //! runtextmacro Benchmark__TestA() // 3
            //! runtextmacro Benchmark__TestA() // 4
            //! runtextmacro Benchmark__TestA() // 5
            //! runtextmacro Benchmark__TestA() // 6
            //! runtextmacro Benchmark__TestA() // 7
            //! runtextmacro Benchmark__TestA() // 8
            //! runtextmacro Benchmark__TestA() // 9
            //! runtextmacro Benchmark__TestA() // 10
        endloop
        //call BJDebugMsg(&quot;Test A performed&quot;)
    endfunction
    private function TestB1000 takes nothing returns nothing
        local integer i=10
        loop
            exitwhen i==0 
            set i=i-1
            
            //! runtextmacro Benchmark__TestB() // 1
            //! runtextmacro Benchmark__TestB() // 2
            //! runtextmacro Benchmark__TestB() // 3
            //! runtextmacro Benchmark__TestB() // 4
            //! runtextmacro Benchmark__TestB() // 5
            //! runtextmacro Benchmark__TestB() // 6
            //! runtextmacro Benchmark__TestB() // 7
            //! runtextmacro Benchmark__TestB() // 8
            //! runtextmacro Benchmark__TestB() // 9
            //! runtextmacro Benchmark__TestB() // 10
        endloop
        //call BJDebugMsg(&quot;Test B performed&quot;)
    endfunction
    
    private function OnEsc takes nothing returns nothing
        local integer sw
        local integer i
        
        set i=0
        set sw=StopWatchCreate()
        loop
            set i=i+1
            call TestA1000.evaluate()
            exitwhen i == GROUP_NUMBER/10
        endloop
        call BJDebugMsg(TITLE_A+&quot;: &quot;+R2S(StopWatchMark(sw)*100))
        call StopWatchDestroy(sw)
        
        set i=0
        set sw=StopWatchCreate()
        loop
            set i=i+1
            call TestB1000.evaluate()
            exitwhen i == GROUP_NUMBER/10
        endloop
        call BJDebugMsg(TITLE_B+&quot;: &quot;+R2S(StopWatchMark(sw)*100))
        call StopWatchDestroy(sw)
    endfunction
    
    ///////////////////////////////
    // Registers the OnEsc event //
    ///////////////////////////////
    private function OnInit takes nothing returns nothing
        local trigger t=CreateTrigger()
        call TriggerRegisterPlayerEvent(t,Player(0),EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddAction(t,function OnEsc)
        call Init()
    endfunction
endlibrary</i></i></i></i>
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
The last benchmark is wrong, i tried to use more than 8190 instances of UnitList, so it was slower because it returned many times the instance "0" and so the private method "remove", part of module AutoDestroy, was called many times when it shoudn't.
GroupAddUnit should be still much faster though.

I've fixed the benchmark code, but i can't use NewGen Warcraft.exe for the moment.

Could someone test and display the values plz ?

Then, i could edit the previous post with the results.

Thx in advance.
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
The previous benchmark GroupAddUnit vs UnitList.addUnit is now valid.
And i love the result to be honest.

I will add the two last benchmarks later (IsUnitIngroup vs isUnitInList and GroupRemoveUnit vs .removeUnit).
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • Varine Varine:
    How can you tell the difference between real traffic and indexing or AI generation bots?
  • The Helper The Helper:
    The bots will show up as users online in the forum software but they do not show up in my stats tracking. I am sure there are bots in the stats but the way alot of the bots treat the site do not show up on the stats
  • Varine Varine:
    I want to build a filtration system for my 3d printer, and that shit is so much more complicated than I thought it would be
  • Varine Varine:
    Apparently ABS emits styrene particulates which can be like .2 micrometers, which idk if the VOC detectors I have can even catch that
  • Varine Varine:
    Anyway I need to get some of those sensors and two air pressure sensors installed before an after the filters, which I need to figure out how to calculate the necessary pressure for and I have yet to find anything that tells me how to actually do that, just the cfm ratings
  • Varine Varine:
    And then I have to set up an arduino board to read those sensors, which I also don't know very much about but I have a whole bunch of crash course things for that
  • Varine Varine:
    These sensors are also a lot more than I thought they would be. Like 5 to 10 each, idk why but I assumed they would be like 2 dollars
  • Varine Varine:
    Another issue I'm learning is that a lot of the air quality sensors don't work at very high ambient temperatures. I'm planning on heating this enclosure to like 60C or so, and that's the upper limit of their functionality
  • Varine Varine:
    Although I don't know if I need to actually actively heat it or just let the plate and hotend bring the ambient temp to whatever it will, but even then I need to figure out an exfiltration for hot air. I think I kind of know what to do but it's still fucking confusing
  • The Helper The Helper:
    Maybe you could find some of that information from AC tech - like how they detect freon and such
  • Varine Varine:
    That's mostly what I've been looking at
  • Varine Varine:
    I don't think I'm dealing with quite the same pressures though, at the very least its a significantly smaller system. For the time being I'm just going to put together a quick scrubby box though and hope it works good enough to not make my house toxic
  • Varine Varine:
    I mean I don't use this enough to pose any significant danger I don't think, but I would still rather not be throwing styrene all over the air
  • The Helper The Helper:
    New dessert added to recipes Southern Pecan Praline Cake https://www.thehelper.net/threads/recipe-southern-pecan-praline-cake.193555/
  • The Helper The Helper:
    Another bot invasion 493 members online most of them bots that do not show up on stats
  • Varine Varine:
    I'm looking at a solid 378 guests, but 3 members. Of which two are me and VSNES. The third is unlisted, which makes me think its a ghost.
    +1
  • The Helper The Helper:
    Some members choose invisibility mode
    +1
  • The Helper The Helper:
    I bitch about Xenforo sometimes but it really is full featured you just have to really know what you are doing to get the most out of it.
  • The Helper The Helper:
    It is just not easy to fix styles and customize but it definitely can be done
  • The Helper The Helper:
    I do know this - xenforo dropped the ball by not keeping the vbulletin reputation comments as a feature. The loss of the Reputation comments data when we switched to Xenforo really was the death knell for the site when it came to all the users that left. I know I missed it so much and I got way less interested in the site when that feature was gone and I run the site.
  • Blackveiled Blackveiled:
    People love rep, lol
    +1
  • The Helper The Helper:
    The recipe today is Sloppy Joe Casserole - one of my faves LOL https://www.thehelper.net/threads/sloppy-joe-casserole-with-manwich.193585/
  • The Helper The Helper:
    Decided to put up a healthier type recipe to mix it up - Honey Garlic Shrimp Stir-Fry https://www.thehelper.net/threads/recipe-honey-garlic-shrimp-stir-fry.193595/

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top