System GroupTools

Dirac

22710180
Reaction score
147
Enhances your whole Unit Group process by adding speed and possibilities.

Why should i use this?:
-Allows you to exit group enumerations
-Avoids group handles
-Provides useful tools for handling groups
-You can avoid [ljass]ForGroup[/ljass] calls by looping through units in the same function without having to remove the units from the group
-Great for handling groups that aren't destroyed immediately

Uses Alloc and Table
JASS:
library GroupTools uses /*

*/  Table  /*  thehelper.net/forums/showthread.php/162582-Table
*/  Alloc  /*  thehelper.net/forums/showthread.php/163457-Alloc

//  A system that provides optimal speed for group handling instead of 
//  using slow natives such as ForGroup or FirstOfGroup. Also provides
//  multiple tools for grouping that Wc3 doesn't have internalized. This
//  system is entirely dedicated to speed freaks and advanced JASSers.

//  Sadly there is no ForGroup call, and adding one would kill all the
//  performance this system provides. How to enumerate units through the
//  group then? An example is provided:

    function Enumerate takes nothing returns nothing
        local UnitGroup SomeGroup=UnitGroup.create() //Allocates a new
        //group, this group is currently empty
        local integer i=0 //This is needed to go through all units inside
        //the group using a loop
        local unit u //This variable is a must. NextOfGroup always returns
        //a different unit from the group, so be sure to use it only once
        //inside each loop.
        call SomeGroup.enumUnitsInRange(0,0,600) //Adds EVERY unit within
        //the specified range, there's no way to filter these units, but
        //there is a way to prevent actios to be done to those you do not
        //want.
        loop
            exitwhen i==SomeGroup.count //The loop ends once i reachs the
            //number of units inside the group
            set u=SomeGroup.NextOfGroup //Use the variable u to handle the
            //curent unit
            if GetUnitTypeId(u)='hfoo' then //Checks if u is a footman
                call KillUnit(u) //If the unit passes the filter actions
                //are fired
            endif
            set i=i+1 //Iterates the loop
        endloop
        //A great advantage of doing this is that the group isn't empty now,
        //If you were using the common FirstOfGroup loop, the group would
        //be right now, so you couldn't save those units for later use
        call SomeGroup.destroy() //Deallocates the group, deleting units
        //inside of it
        set u=null
    endfunction

//  This system also provides the SimpleGroup struct, it works similar
//  to UnitGroup, it's faster, but doesn't have some of the tools it
//  provides.
***********************************************************************

    API for the UnitGroup struct:
    
        -Allocation Methods:
    
    static method create takes nothing returns thistype
//      Allocates a group, this group is initially empty.

    method destroy takes nothing returns nothing
//      Deallocates a group and all of the units inside of it.

    method addUnit takes unit whichUnit returns nothing
//      Adds an unit to the group.

    method removeUnit takes unit whichUnit returns nothing
//      Removes an unit from the group.

    method hasUnit takes unit whichUnit returns boolean
//      Returns true if the group has the given unit.

    method clear takes nothing returns nothing
//      Removes every unit from the group.

***********************************************************************

    API for the SimpleGroup struct:
    
    static method create takes nothing returns thistype
//      Allocates a group, this group is initially empty.

    method destroy takes nothing returns nothing
//      Deallocates a group and all of the units inside of it.

    method addUnit takes unit whichUnit returns nothing
//      Adds an unit to the group.

    method clear takes nothing returns nothing
//      Removes every unit from the group.

***********************************************************************

    API of the Enumeration methods:

    method enumUnitsInRange takes real x, real y, real radius returns nothing
//      Picks all units within a radius and adds them to the group.

    method enumUnitsInRing takes real x, real y, real innerRadius, real outerRadius returns nothing
//      Only adds the units inside the given parameters.

    method enumUnitsInCone takes real x, real y, real radius, real facing, real coneAngle returns nothing
//      Only adds the units inside the given parameters.

    method enumUnitsInRect takes real x1, real y1, real x2, real y2 returns nothing
//      Only adds the units inside the given parameters.

***********************************************************************

        -Group Handling:

    method isEmpty takes nothing returns boolean
//      Returns true if the group is empty.
//      Using "0==this.count" gives the same result.

***********************************************************************

        -Additional Tools:

    method closestToLoc takes real x, real y returns unit
//      Returns the closest unit from the group to the given location.

    method farthestToLoc takes real x, real y returns unit
//      Returns the farthest unit from the group to the given location.

***********************************************************************

    VARIABLES:
    
        -Globals provided
        
    group ENUM_GROUP
//      Initialized group.

        -Non Static Members (this.Variable)

    readonly integer count
//      The amount of units inside the group (Non modifiable).

    readonly unit FirstOfGroup
//      The first unit of the group, when another is added or removed it changes (Non modifiable).

    readonly unit NextOfGroup
//      Basically cycles between units from the group everytime it's used (Non modifiable).

    integer data
//      This variable exists for those who wants to attach instances to the group.


**********************************************************************/


//  Basically this is how the OOP works:
//      A group is allocated, this group has record of 1 random unit from an UnitList, this unit knows the next and previous
//      instance from it's list. In the example below the group G has 6 units A - B - C - D - E - F

//            B - C
//           /     \
//      G - A      D
//          \     /
//           F - E

//      If the unit "Z" is added to the group then it would look like this

//            B - C - D
//           /         \
//      G - Z          E
//          \         /
//           A   -   F

//      If the unit "D" is removed from the group then it would look like this

//            F - A
//           /     \
//      G - E      Z
//          \     /
//           C - B

//      Notice how the unit the group points to changed from A to Z and now is E. It doesn't really matter which unit the group
//      points to, as long as it belongs to the same unit list the group will remain complete. The reason of why it changed from
//      being "Z" to "E" is explained below.

    globals
        group ENUM_GROUP=CreateGroup()
    endglobals

    private struct UnitList extends array
        implement Alloc
        //The way this system keeps track of units it's similar to a linked list, only that there's no head. UnitList only keeps
        //track of the next and prev units from an unit. In a list where units "A" "B" and "C" are linked it's representation would be
        //like this:
        //  A - B - C - A - B...
        //If this were a normal linked list, "A" would be the head, but instead is just another echelon.
        
        thistype next
        thistype prev
        unit unit
    endstruct
    
    struct UnitGroup extends array
        implement Alloc
        //the "first variable works like a pointer the group has towards any unit allocated inside UnitList, and because this
        //list keeps track units that follow it, there's no need for the group to know all of them.
        private UnitList first
        //keeps track of how many units are inside the group.
        readonly integer count
        //For those who want to attach instances to the group
        integer data

        //Table is used almost exclusively for the Group.hasUnit method.
        private Table lookUp
        
        method addUnit takes unit whichUnit returns nothing
            local UnitList thisUnit
            local UnitList start
            local integer id=GetHandleId(whichUnit)
            
            //prevent adding the same unit to the group twice
            if this.lookUp.has(id) then
                return
            endif
            set thisUnit=UnitList.allocate()
            
            //This is for creating a LinkedList without a head.
            if 0==this.count then
                set start=thisUnit
                //It dosn't really matter which UnitList is stored in the "first" variable
                set this.first=thisUnit
            else
                set start=this.first
            endif
            
            set start.next.prev=thisUnit
            set thisUnit.next=start.next
            set start.next=thisUnit
            set thisUnit.prev=start
            
            set thisUnit.unit=whichUnit
            set this.lookUp[GetHandleId(thisUnit.unit)]=thisUnit
            set this.count=this.count+1
        endmethod
        
        method removeUnit takes unit whichUnit returns nothing
            local integer h=GetHandleId(whichUnit)
            local UnitList thisUnit=this.lookUp[h]
            set thisUnit.next.prev=thisUnit.prev
            set thisUnit.prev.next=thisUnit.next
            
            //This would be only necessary if the deallocated unit IS the "first" of the group, but to avoid useless checks
            //it's performed everytime an unit is deallocated. Again, it dosn't matter which UnitList is the "first" variable
            //unless it's an unit from outside the group.
            set this.first=thisUnit.next
            
            call thisUnit.deallocate()
            call this.lookUp.remove(h)
            set this.count=this.count-1
        endmethod
        
        //Safety method to remove Non-existant units from the unit group
        private method safeIteration takes nothing returns nothing
            loop
                exitwhen not(0==GetUnitTypeId(this.first.unit)) or (0==this.count)
                call this.removeUnit(this.first.unit)
            endloop
        endmethod
        
        method operator FirstOfGroup takes nothing returns unit
            call this.safeIteration()
            return this.first.unit
        endmethod
        
        //This method always returns a different unit from the group, won't return the same unit until it goes through all
        //the units inside the group.
        method operator NextOfGroup takes nothing returns unit
            set this.first=this.first.next
            call this.safeIteration()
            return this.first.unit
        endmethod
        
        //Destroys the whole list looping through it once.
        method destroy takes nothing returns nothing
            local UnitList next
            loop
                exitwhen 0==this.count
                set next=this.first.next
                call this.lookUp.remove(GetHandleId(this.first.unit))
                call this.first.deallocate()
                set this.first=next
                set this.count=this.count-1
            endloop
            call this.deallocate()
        endmethod
        
        method clear takes nothing returns nothing
            local UnitList next
            loop
                exitwhen 0==this.count
                set next=this.first.next
                call this.lookUp.remove(GetHandleId(this.first.unit))
                call this.first.deallocate()
                set this.first=next
                set this.count=this.count-1
            endloop
        endmethod

        method isEmpty takes nothing returns boolean
            return (0==this.count)
        endmethod
        
        method hasUnit takes unit whichUnit returns boolean
            return this.lookUp.has(GetHandleId(whichUnit))
        endmethod
        
        //! runtextmacro GroupTools_GroupEnumerationTools()
        
        static method create takes nothing returns thistype
            local thistype this=thistype.allocate()
            set this.lookUp=Table.create()
            set this.count=0
            return this
        endmethod
    endstruct
    
    struct SimpleGroup extends array
        implement Alloc
        //This one works like a number stack, it's a lot faster for unit groups that doesn't need
        //removing units or checking for units inside of it.
        
        private UnitList first
        readonly integer count
        integer data
        
        private method safeIteration takes nothing returns nothing
            local integer i=this.count
            loop
                exitwhen not(0==GetUnitTypeId(this.first.unit)) or 0==i
                set this.first=this.first.next
                set i=i-1
            endloop
        endmethod

        method operator FirstOfGroup takes nothing returns unit
            call this.safeIteration()
            return this.first.unit
        endmethod
        
        method operator NextOfGroup takes nothing returns unit
            set this.first=this.first.next
            call this.safeIteration()
            return this.first.unit
        endmethod
        
        method isEmpty takes nothing returns boolean
            return (0==this.count)
        endmethod
        
        method addUnit takes unit whichUnit returns nothing
            local UnitList thisUnit=UnitList.allocate()
            local UnitList start
            
            if 0==this.count then
                set start=thisUnit
                set this.first=thisUnit
            else
                set start=this.first
            endif
            
            set thisUnit.next=start.next
            set start.next=thisUnit
            
            set thisUnit.unit=whichUnit
            set this.count=this.count+1
        endmethod
        
        method clear takes nothing returns nothing
            local UnitList next
            loop
                exitwhen 0==this.count
                set next=this.first.next
                call this.first.deallocate()
                set this.first=next
                set this.count=this.count-1
            endloop
        endmethod
        
        method destroy takes nothing returns nothing
            local UnitList next
            loop
                exitwhen 0==this.count
                set next=this.first.next
                call first.deallocate()
                set first=next
                set this.count=this.count-1
            endloop
            call this.deallocate()
        endmethod
        
        //! runtextmacro GroupTools_GroupEnumerationTools()
        
        static method create takes nothing returns thistype
            local thistype this=thistype.allocate()
            set this.count=0
            return this
        endmethod
    endstruct
    
    //! textmacro GroupTools_GroupEnumerationTools
    
        //These two methods use Selection Sort, i can't think of a faster method to compare units
        method farthestToLoc takes real x, real y returns unit
            local integer i=0
            local unit u=this.first.unit
            local real od
            local real ox=GetUnitX(u)-x
            local real oy=GetUnitY(u)-y
            local real min=SquareRoot(ox*ox+oy*oy)
            local unit r=u
            loop
                exitwhen i==this.count
                set this.first=this.first.next
                call this.safeIteration()
                set u=this.first.unit
                set ox=GetUnitX(u)-x
                set oy=GetUnitY(u)-y
                set od=SquareRoot(ox*ox+oy*oy)
                if od>min then
                    set r=u
                    set min=od
                endif
                set i=i+1
            endloop
            set u=null
            return r
        endmethod
        
        method closestToLoc takes real x, real y returns unit
            local integer i=0
            local unit u=this.first.unit
            local real od
            local real ox=GetUnitX(u)-x
            local real oy=GetUnitY(u)-y
            local real min=SquareRoot(ox*ox+oy*oy)
            local unit r=u
            loop
                exitwhen i==this.count
                set this.first=this.first.next
                call this.safeIteration()
                set u=this.first.unit
                set ox=GetUnitX(u)-x
                set oy=GetUnitY(u)-y
                set od=SquareRoot(ox*ox+oy*oy)
                if od<min then
                    set r=u
                    set min=od
                endif
                set i=i+1
            endloop
            set u=null
            return r
        endmethod
        
        private static thistype enumGroup
        
        private static method addTo takes nothing returns boolean
            call enumGroup.addUnit(GetFilterUnit())
            return false
        endmethod
        
        method enumUnitsInRange takes real x, real y, real radius returns nothing
            set enumGroup=this
            call GroupEnumUnitsInRange(ENUM_GROUP,x,y,radius,Filter(function thistype.addTo))
        endmethod

        private static real tempX
        private static real tempY
        private static real tempN
        private static real tempM

        private static method addConeTo takes nothing returns boolean
            local unit u=GetFilterUnit()
            local real a=Atan2(GetUnitY(u)-tempY,GetUnitX(u)-tempX)
            if a<0 then
                set a=a+bj_PI*2
            endif
            if (a>=tempN-tempM) and (a<=tempN+tempM) then
                call enumGroup.addUnit(u)
            endif
            set u=null
            return false
        endmethod

        method enumUnitsInCone takes real x, real y, real radius, real facing, real coneAngle returns nothing
            if 0==coneAngle then
                debug call BJDebugMsg("the given cone angle was 0, group won't pick anything")
                return
            endif
            set enumGroup=this
            set tempX=x
            set tempY=y
            set tempN=facing
            set tempM=coneAngle/2
            call GroupEnumUnitsInRange(ENUM_GROUP,x,y,radius,Filter(function thistype.addConeTo))
        endmethod
        
        private static method addRingTo takes nothing returns boolean
            local unit u=GetFilterUnit()
            local real x=GetUnitX(u)-tempX
            local real y=GetUnitY(u)-tempY
            local real d=SquareRoot(x*x+y*y)
            if d>=tempN then
                call enumGroup.addUnit(u)
            endif
            set u=null
            return false
        endmethod
        
        method enumUnitsInRing takes real x, real y, real innerRadius, real outerRadius returns nothing
            if innerRadius>=outerRadius then
                debug call BJDebugMsg("You can't have a ring with an inner radius bigger than the outer radius")
                return
            endif
            set enumGroup=this
            set tempX=x
            set tempY=y
            set tempN=innerRadius
            call GroupEnumUnitsInRange(ENUM_GROUP,x,y,outerRadius,Filter(function thistype.addRingTo))
        endmethod
        
        method enumUnitsInRect takes rect r returns nothing
            set enumGroup=this
            call GroupEnumUnitsInRect(ENUM_GROUP,r,Filter(function thistype.addTo))
        endmethod
        
    //! endtextmacro
endlibrary
 

Solmyr

Ultra Cool Member
Reaction score
30
This is very, very pointless.
  1. It is terribly slow. Much slower than native groups.
  2. The enumeration does not account for collision size, which is the main point of GroupUtils' [ljass]GroupEnumUnitsInArea()[/ljass] function.
  3. The [ljass].closestToPoint[/ljass] and [ljass].farthestToPoint[/ljass] methods are utterly pointless. Look at this to see how it should be done without the use of a silly library such as yours.
  4. No user-specific filters for the enumeration.
Really, why did you even bother writing this? A single, shared global group would outperform this in every case, and when such group can't be used, a recycled one would still do better. Sure, an object-oriented API might look sweet and all, but it doesn't justify pointless scripts.
 

Dirac

22710180
Reaction score
147
I wrote this because there was an open thread about group libraries and i posted this idea, some people though it was good for submission so i did.
This library would be a must if unit groups didn't exist, but they do. I though this would be faster, i didn't know how it would so I coded 2 very different systems that do the same thing.
 

Dirac

22710180
Reaction score
147
I would like to receive more feedback on this one, also i would like a mod to decide whether this should be placed on graveyard or is it subject to discussion
 

tooltiperror

Super Moderator
Reaction score
231
For the moment, I'm saying this is definitely worth looking at.

On one hand, the type [ljass]group[/ljass] seems to be implemented quite well in Warcraft III, likely using some sort of vector or list internally. I know Kingking wrote a particularly good snippet to replace Groups, but found it was at best "as good" as the native groups. And OOP interface is not a reason to approve something.

Your documentation for this utility, and in general, could use some work. Study some other posts for good documentation and submission.
http://www.wc3c.net/showthread.php?t=105643
http://www.wc3c.net/showthread.php?t=105515
http://www.wc3c.net/showthread.php?t=102721
http://www.thehelper.net/forums/showthread.php/101723-Artificial-s-Recipe-System
http://www.thehelper.net/forums/showthread.php/142460-Heal?p=1169367#post1169367 (sickle's post)

Not saying it has to be a novel, but it's rather lacking right now.

Also give a reason (besides OOP) to use this over natives.
 

Dirac

22710180
Reaction score
147
Very well then, i'll have version A documented.

At this moment version B isn't the way to go because using OFFSETs to count handles is very unsafe.
 

Dirac

22710180
Reaction score
147
Updated:
-Added .forGroup method and some tools to work around it
-The code is now documented
-Uses Alloc whether you like or not
 

luorax

Invasion in Duskwood
Reaction score
67
Well, I think it's getting better. However things to note:
-the system still doesn't support unit collisions
-the code execution should be updated as it has been suggested in your other topic. (Force trick)
-you should add an integer variable, "data". Spell makers often have to use unsafe global variables, because they can't attach data to groups (neither retrieve the enumed group).
-more enum functions would be nice (like "enumUnitsInCone", "enumUnitsRing", things like that - we lack those kind of enum functions)
 

Dirac

22710180
Reaction score
147
Well, I think it's getting better. However things to note:
-the system still doesn't support unit collisions
-the code execution should be updated as it has been suggested in your other topic. (Force trick)
-you should add an integer variable, "data". Spell makers often have to use unsafe global variables, because they can't attach data to groups (neither retrieve the enumed group).
-more enum functions would be nice (like "enumUnitsInCone", "enumUnitsRing", things like that - we lack those kind of enum functions)
Update:
-Changed code execution
-Added the data variable
-Added more enum functions
-Added lots of safe mechanisms to avoid retrieving null units from inside the group.
 

luorax

Invasion in Duskwood
Reaction score
67
Cool, I like it :)

Someone should really benchmark it now, as it is a good group system IMO.

EDIT: one more suggestion:
GroupData should be renamed to EnumGroup or something similar. GroupData may be confusing for first (well, I thought it refers to the data of the group.)
 

Dirac

22710180
Reaction score
147
Cool, I like it :)

Someone should really benchmark it now, as it is a good group system IMO.

EDIT: one more suggestion:
GroupData should be renamed to EnumGroup or something similar. GroupData may be confusing for first (well, I thought it refers to the data of the group.)
I'm glad you like this resource

I was thinking maybe naming it UnitGroup.currentGroup or something similar, maybe adding a function instead like "GetGroupData()". Since this resource isn't approved yet i'll make updates without notice periodically.
 

luorax

Invasion in Duskwood
Reaction score
67
I think GetEnumGroup() would be the best. That name is kinda self-speaking.
 

tooltiperror

Super Moderator
Reaction score
231
The documentation looks worse now :confused:

You took something that could have been simple and graceful and made it a lot more complicated. You don't need OOP for this, you don't need to remake the [LJASS]group[/LJASS] type. In fact, it seems just about all of this is added fluff for the already existent Group API. The only useful feature of this is the enumerations - which is syntactic sugar and provides quitting functionality which is really nice.

Maybe this would be inspirational?

JASS:

library Enumerator
/*************************************************************
 *                                                    
 *                      Enumerator                        
 *                                                    
 *     ==About==                                      
 *     + The native type "group" is implemented well in Jass.
 *       The only problem is that enumerating through groups is
 *       a pain.  Enumerator solves this problem with a FirstOfGroup
 *       loop with a minimalistic interface.
 *                                                   
 *     ==Pros==                                       
 *     + Extremely lightweight                                  
 *     + Simple interface          
 *                                                    
 *     ==API==                                        
 *     + function GroupEnumUnits(group which, code action)    
 *       - Runs action for each unit in the group
 *       - action must take exactly one parameter: the group.
 *                                                    
 *     + function GroupExitEnum()                     
 *       - Stops iteration of the enumeration
 *
 *************************************************************/
function interface EnumerationTemplate takes unit selector returns nothing

globals
    private boolean CanExit = false
endglobals

function GroupEnumUnits takes group whichGroup, EnumerationTemplate action returns nothing
    local group g        = whichGroup  // A dummy group to inherit whichGroup's members
    local unit selector  = null        // A

    loop
        set selector = FirstOfGroup(g)
        exitwhen (selector == null or CanExit)

        call action.evaluate(selector)

        call GroupRemoveUnit(g, selector)
    endloop

    // Because it's cute and supporting it isn't that bad, considering vJass does not
    // give us optional parameters.
    if (bj_wantDestroyGroup) then
        call DestroyGroup(whichGroup)
        set bj_wantDestroyGroup = false
    endif

    call DestroyGroup(g)
    set g = null
    set selector = null
endfunction

function GroupExitEnum takes nothing returns nothing
    set CanExit = true
endfunction

endlibrary


JASS:

scope PeasantKiller initializer onInit

// Kill the passed unit, unless it is a footman, in which case
// stop execution.
private function Actions takes unit u returns nothing
    if (GetUnitTypeId(u) == 'hfoo') then
        call GroupExitEnum()
        return
    endif

    call KillUnit(u)
endfunction

private function onInit takes nothing returns nothing
    local trigger trig  = CreateTrigger()
    local group   g     = CreateGroup()

    // Add units within 500 WCU of the origin
    call GroupEnumUnitsInRange(g, 0.00, 0.00, 500.00, null) 

    // Execute 'Actions' for each unit
    call GroupEnumUnits(g, function Actions)
endfunction

endscope
This will kill all the units in 500 WCU of the origin, and then stop if it encounters a footman :cool: :)

It seems most of your code is fluff for the Jass2 API, which isn't necessary. You can expand on Enumeration and improve it, but you've got a big conceptual flaw...

In my opinion, the enumerating functions don't make sense, this is for dealing with the groups, not with regions and rects. So the better approach for that is to get users to make their own rects with another system (Does RectWraps do this?) and then they can use [LJASS]GroupEnumUnitsInRect[/LJASS].

Don't reinvent the wheel, unless you want to learn more about wheels. In which case don't submit it.
 

Dirac

22710180
Reaction score
147
Ok first of all i had no idea you could do that with interfaces, this changes everything

Second, bad news, my system proved to be slower than natives (crashes when handling 200 groups, natives crash when handling 700) i think the addUnit and removeUnit methods are a lot slower than the native ones

Third, yes, i tried to reinvent the wheel, and wasn't successful at it. This system has proven to be not useful.

EDIT: @tooltiperror your snippet returns syntax error because the input function can't take any arguments, and it takes [ljass]unit u[/ljass]

The correct syntax would be
JASS:
call GroupEnumUnits(g,EnumerationTemplate(function what))


EDIT2: after testing your snippet i realized that

Array lookup is slower than the FirstOfGroup native. So if i decide to work on a Group System it would be around this dogma

For those interested in the test realized

tooltiperror's snippet runs at 64 fps on my computer
JASS:
scope Tests

    private struct T
        group Group
        static thistype tempThis
        private static method enum takes unit u returns nothing
            call KillUnit(u)
        endmethod
        private method periodic takes nothing returns nothing
            set tempThis=this
            call GroupEnumUnits(.Group,EnumerationTemplate(thistype.enum))
        endmethod
        implement T32x
        private static method True takes nothing returns boolean
            return true
        endmethod
        private static method onInit takes nothing returns nothing
            local thistype this
            local integer i=500
            loop
                exitwhen 0==i
                set this=thistype.allocate()
                set .Group=CreateGroup()
                call GroupEnumUnitsInRange(.Group,0,0,600,Filter(function thistype.True))
                call .startPeriodic()
                set i=i-1
            endloop
        endmethod
    endstruct
    
endscope

GroupTools runs at 10 fps and then crashes
JASS:
scope Tests
    private struct T
        static UnitGroup Group
        private static method EnumSimple takes unit whichUnit returns nothing
            call KillUnit(whichUnit)
        endmethod
        private method periodic takes nothing returns nothing
            local UnitGroup g=this
            call g.forGroup(Enum(thistype.EnumSimple))
        endmethod
        implement T32x
        private static method onInit takes nothing returns nothing
            local thistype this
            local integer i=500
            loop
                exitwhen 0==i
                set Group=UnitGroup.create()
                call Group.enumUnitsInRange(0,0,600)
                set this=Group
                call .startPeriodic()
                set i=i-1
            endloop
        endmethod
    endstruct
endscope
 

Dirac

22710180
Reaction score
147
Double post, but i have a reason

I just coded this out of the Interface idea tooltiperror provided.
This works as an expansion to the well known GroupUtils, adding a few new options and API

-CountGroup is no longer a bj, it provides the number of units inside a group directly from a var
-Allows you to exit ForUnitGroup calls using ExitEnum()
-An example on how to call ForUnitGroup or EnumUnitsInRange
[ljass]call ForUnitGroup(group,EnumFunc(functionName))[/ljass]
[ljass]call EnumUnitsInRange(group,0,0,600,EnumFunc(functionName))[/ljass]
the input function should take unit whichUnit and in case of EnumUnitsInRange it should return a boolean
-You MUST add and remove units from groups using this library's API if you want this to work properly.

It dosn't have any safety mechanism, IMO there shouldn't exist any because even if there are, your map would still crash or malfunction when you code something you're supposed not to. But they can be added if needed.
Works with OFFSETs right now, if this library is viable in any way i'll add static ifs for hashtable use, or even using Table as an optional library.


JASS:
library GroupTools
    globals
        private constant integer    OFFSET=0x100000
        
        private boolean             EXIT            =false
        private integer             RECYCLE         =0
        private key                 NONSET
        private group               CURRENT_GROUP

        private group   array       GROUP
    endglobals

    globals
        private integer array       UNIT_COUNT
        private integer array       DATA
    endglobals
    
    function interface EnumFunc takes unit whichUnit returns boolean

    private function addUnit takes nothing returns boolean
        set UNIT_COUNT[0]=UNIT_COUNT[0]+1
        return true
    endfunction
    
    function EnumUnitsInRange takes group whichGroup, real x, real y, real radius, EnumFunc check returns nothing
        local group g=CreateGroup()
        local unit u=null
        set UNIT_COUNT[0]=0
        call GroupEnumUnitsInRange(g,x,y,radius,Filter(function addUnit))
        loop
            set u=FirstOfGroup(g)
            exitwhen null==u or EXIT
            if not(check.evaluate(u)) then
                call GroupRemoveUnit(whichGroup,u)
                set UNIT_COUNT[0]=UNIT_COUNT[0]-1
            else
                call GroupAddUnit(whichGroup,u)
            endif
            call GroupRemoveUnit(g,u)
        endloop
        call DestroyGroup(g)
        set UNIT_COUNT[GetHandleId(whichGroup)-OFFSET]=UNIT_COUNT[0]
        set u=null
        set g=null
    endfunction
        
    function ForUnitGroup takes group whichGroup, EnumFunc action returns nothing
        local group g=whichGroup
        local unit u=null
        set EXIT=false
        set CURRENT_GROUP=whichGroup
        loop
            set u=FirstOfGroup(g)
            exitwhen null==u or EXIT
            call action.execute(u)
            call GroupRemoveUnit(g,u)
        endloop
        call DestroyGroup(g)
        set u=null
        set g=null
    endfunction

    function IsGroupEmpty takes group whichGroup returns boolean
        return 0==UNIT_COUNT[GetHandleId(whichGroup)-OFFSET]
    endfunction
    
    function CountGroup takes group whichGroup returns integer
        return UNIT_COUNT[GetHandleId(whichGroup)-OFFSET]
    endfunction
    
    function RemoveUnitFromGroup takes unit whichUnit, group whichGroup returns boolean
        local integer id=GetHandleId(whichGroup)-OFFSET
        if (IsUnitInGroup(whichUnit,whichGroup)) then
            call GroupRemoveUnit(whichGroup,whichUnit)
            set UNIT_COUNT[id]=UNIT_COUNT[id]-1
            return true
        endif
        return false
    endfunction
    
    function AddUnitToGroup takes unit whichUnit, group whichGroup returns boolean
        local integer id=GetHandleId(whichGroup)-OFFSET
        if not(IsUnitInGroup(whichUnit,whichGroup)) then
            call GroupAddUnit(whichGroup,whichUnit)
            set UNIT_COUNT[id]=UNIT_COUNT[id]+1
            return true
        endif
        return false
    endfunction
    
    function ExitEnum takes nothing returns nothing
        set EXIT=true
    endfunction
    
    function GetEnumGroup takes nothing returns group
        return CURRENT_GROUP
    endfunction

    function GetGroupData takes group whichGroup returns integer
        local integer data=DATA[GetHandleId(whichGroup)-OFFSET]
        if data==NONSET then
            debug call BJDebugMsg("No data was set to this group")
            return 0
        endif
        return data
    endfunction

    function SetGroupData takes group whichGroup, integer data returns nothing
        set DATA[GetHandleId(whichGroup)-OFFSET]=data
    endfunction

    function ReleaseGroup takes group whichGroup returns nothing
        local integer id=GetHandleId(whichGroup)-OFFSET
        set GROUP[0]=GROUP[RECYCLE]
        set RECYCLE=RECYCLE+1
        call GroupClear(whichGroup)
    endfunction

    function NewGroup takes nothing returns group
        local integer i
        local integer id
        if 0==RECYCLE then
            set GROUP[0]=CreateGroup()
        else
            set RECYCLE=RECYCLE-1
        endif
        set id=GetHandleId(GROUP[RECYCLE])-OFFSET
        set DATA[id]=NONSET
        set UNIT_COUNT[id]=0
        return GROUP[RECYCLE]
    endfunction
endlibrary
 

tooltiperror

Super Moderator
Reaction score
231
You again took something that could be simple and graceful and help Jassers, and turned it into a horrible mess.

> private boolean EXIT =false
It should be [Ljass]Exit[/LJASS] not [LJASS]EXIT[/LJASS]. It's not constant.

>return 0==
Stop this foolishness. My code is better than all of your code because it's more readable. And that's just the way it is. One day you might collaborate with another mapper, and he can't read your code. "0==" is clearly unintuitive for humans, who compare things to zero.

>function EnumUnitsInRange
Let the Group API take care of this enumerating. The filter stuff in here is totally 2003esque Jass.

>function AddUnitToGroup
>function RemoveUnitFromGroup

This really grinds my gears. Reinventing Jass is sometimes necessary: timers, multiboards, and damage are good examples of this. Groups don't need to be started from scratch, the [ljass]group[/ljass] type is already excellent. You can't just plug this into your map and use it right away, and in that respect, Enumerator is superior to this script :)

Fix up the script a bit, make it work on plain old groups, fix the design flaws, and this could be something really useful.
 

Dirac

22710180
Reaction score
147
1. Done
2. It's not that hard to read, gives the same result. But ok, i'll work on that
3. Unless you want it to retrieve the amount of units a group has through an awful loop this should stay as a function
4 and 5. Again, this is only because the system should know how many units are inside the group to avoid looping through it to retrieve it.

The previous system i wrote reinvents the whole group system, and it's less efficient, yes. But this one rather works around it, by taking in consideration how many units are added to the group, removed, and preventing unit group creation / destruction when not necessary (GroupUtils already does that, but this should be a replacement)

EDIT: Enumerator removes all the units from the group every time it's enumerated, i already solved this issue.
 

Dirac

22710180
Reaction score
147
I'll go ahead and ask a moderator to graveyard this. Reasons why:

>It's slower than natives
>If you want to exit group enumeration i advise you to loop through the units from a group instead of Enumerator (which is actually very slow)
>GroupUtils provides all other tools this system does besides exiting group enumeration

Conclusion: If you want to pick all the units inside a group, use common ForGroup calls, if you want to exit group enumeration, instead use loops.
 

tooltiperror

Super Moderator
Reaction score
231
>use common ForGroup calls
Leaks.

>if you want to exit group enumeration, instead use loops.
Sure.

Graveyarded.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top