Discussion Group System

luorax

Invasion in Duskwood
Reaction score
67
I decided to write my own group system to replace GroupUtils. GroupUtils is a nice system, but:
-contains two useless hashtable;
-does not support data attaching;
-plus I've already made some little functions that I wanted to implement into my snippet.

So, the question is: what do you think about it? What should I improve, and how? Should I create empty groups at init as TimerUtils does?
Basically I want to hear some response.

Here is the code:
JASS:
library GroupTools initializer init uses Table

//:==========================================================================
//:========================== Configurables =================================
//:==========================================================================
globals
    //This value corresponds to the max collision size of a unit in your map.
    private constant real    MAX_COLLISION_SIZE=197.
    private constant boolean HOOK_FORGROUP=false
endglobals
//:==========================================================================
//:============================ The System ==================================
//:==========================================================================
globals
    //---== Enumerations ==---
    private group         TempGroup=CreateGroup()
    private group         EnumGroup
    private boolexpr      EnumFilter
    private boolexpr      ConeFilter
    private real          EnumX=0.
    private real          EnumY=0.
    private real          EnumMinRadius=0.
    private real          EnumMaxRadius=0.
    private real          EnumConeRadius=0.
    private real          EnumConeAngle=0.
    private real          EnumConeViewAngle=0.
    private Table         RingTable
    private Table         ConeTable
    //---== Group Refresh ==---
    private group         RefreshGroup
    //---== GetClosestUnit() ==---
    private unit          ClosestUnit
    private real          ClosestDistance=0.
    private real          ClosestX=0.
    private real          ClosestY=0.
    //---== Recycling ==---
    private integer       OFFSET=0x100000
    private integer       NO_DOUBLEFREE=0x65378463
    private integer       Count=0
    private group   array Groups
    private integer array Datas
    //---== Boolexpr Utils ==---
    boolexpr              BOOLEXPR_TRUE
    boolexpr              BOOLEXPR_FALSE
endglobals
//:===================================================
//:                GroupRefresh()
//:===================================================
private function RefreshEnum takes nothing returns nothing
    call GroupAddUnit(RefreshGroup,GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
    set RefreshGroup=g
    call GroupClear(RefreshGroup)
    call ForGroup(RefreshGroup,function RefreshEnum)
endfunction
//:===================================================
//:                GroupRecycling()
//:===================================================
function SetGroupData takes group g,integer value returns nothing
    set Datas[GetHandleId(g)-OFFSET]=value       
endfunction
function GetGroupData takes group g returns integer
    return Datas[GetHandleId(g)-OFFSET]     
endfunction
function NewGroup takes nothing returns group
    if Count==0 then
        set Groups[0]=CreateGroup()
    else
        set Count=Count - 1
    endif
    set Datas[GetHandleId(Groups[Count])-OFFSET]=0
    return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
    local integer id = GetHandleId(g)
    if g==null then
        return false
    elseif Count==8191 then
        call DestroyGroup(g)
        return false
    elseif Datas[id-OFFSET]==NO_DOUBLEFREE then
        return false
    endif
    call GroupClear(g)
    set Datas[id-OFFSET]=NO_DOUBLEFREE
    set Groups[Count]=g
    set Count=Count+1
    return true
endfunction
//:===================================================
//:                    Hooks
//:===================================================
private function HookDestroyBoolExpr takes boolexpr b returns nothing
    local integer id=GetHandleId(b)
    if RingTable.boolexpr.has(id) then
        call DestroyBoolExpr(RingTable.boolexpr[id])
        call RingTable.boolexpr.remove(id)
    elseif ConeTable.boolexpr.has(id) then
        call DestroyBoolExpr(ConeTable.boolexpr[id])
        call ConeTable.boolexpr.remove(id)
    endif
endfunction
private function HookForGroup takes group g,code c returns nothing
    set EnumGroup=g
endfunction
hook DestroyBoolExpr HookDestroyBoolExpr
static if HOOK_FORGROUP then
    hook ForGroup HookForGroup
endif
//:===================================================
//:                Enumerations
//:===================================================
function GetEnumGroup takes nothing returns group
    return EnumGroup
endfunction
function ForGroupEx takes group g,code c returns nothing
    static if not HOOK_FORGROUP then
        set EnumGroup=g
    endif
    call ForGroup(g,c)
endfunction
private function FilterFunc takes nothing returns boolean
    return not IsUnitInRangeXY(GetFilterUnit(),EnumX,EnumY,EnumMinRadius) and IsUnitInRangeXY(GetFilterUnit(),EnumX,EnumY,EnumMaxRadius)
endfunction
function GroupEnumUnitsInRing takes group g,real x,real y,real minRadius,real maxRadius,boolexpr filter returns nothing
    local integer id=0
    local real prevX=EnumX
    local real prevY=EnumY
    local real prevMinRadius=EnumMinRadius
    local real prevMaxRadius=EnumMaxRadius
    
    set EnumX=x
    set EnumY=y
    set EnumMinRadius=minRadius
    set EnumMaxRadius=maxRadius
    if filter==null then
        set filter=EnumFilter
    else
        set id=GetHandleId(filter) 
        if RingTable.boolexpr.has(id) then
            set filter=RingTable.boolexpr[id]
        else
            set filter=And(EnumFilter,filter)
            set RingTable.boolexpr[id]=filter
        endif
    endif
    call GroupEnumUnitsInRange(g,x,y,maxRadius+MAX_COLLISION_SIZE,filter)
    set EnumX=prevX
    set EnumY=prevY
    set EnumMinRadius=prevMinRadius
    set EnumMaxRadius=prevMaxRadius
endfunction
function GroupEnumUnitsInRangeEx takes group g,real x,real y,real radius,boolexpr filter returns nothing
    call GroupEnumUnitsInRing(g,x,y,0.,radius,filter)
endfunction
private function ConeFilterFunc takes nothing returns boolean
    local real angle=bj_RADTODEG*Atan2(GetUnitY(GetFilterUnit())-EnumY,GetUnitX(GetFilterUnit())-EnumX)
    return IsUnitInRangeXY(GetFilterUnit(),EnumX,EnumY,EnumConeRadius) and/*
        */ angle<=EnumConeViewAngle+EnumConeAngle and/*
        */ angle>=EnumConeViewAngle-EnumConeAngle
endfunction
function GroupEnumUnitsCone takes group g,real x,real y,real radius,real currentAngle,real angle,boolexpr filter returns nothing
    local integer id=0
    local real prevX=EnumX
    local real prevY=EnumY
    local real prevConeRadius=EnumConeRadius
    local real prevConeAngle=EnumConeAngle
    local real prevConeViewAngle=EnumConeViewAngle
    
    set EnumX=x
    set EnumY=y
    set EnumConeRadius=radius
    set EnumConeAngle=angle/2
    set EnumConeViewAngle=currentAngle
    if filter==null then
        set filter=ConeFilter
    else
        set id=GetHandleId(filter) 
        if ConeTable.boolexpr.has(id) then
            set filter=ConeTable.boolexpr[id]
        else
            set filter=And(ConeFilter,filter)
            set ConeTable.boolexpr[id]=filter
        endif
    endif
    call GroupEnumUnitsInRange(g,x,y,radius+MAX_COLLISION_SIZE,filter)
    set EnumX=prevX
    set EnumY=prevY
    set EnumConeRadius=prevConeRadius
    set EnumConeAngle=prevConeAngle
    set EnumConeViewAngle=prevConeViewAngle
endfunction
//:===================================================
//:                GetClosestUnit
//:===================================================
private function ClosestEnum takes nothing returns nothing
    local real x=GetUnitX(GetEnumUnit())-ClosestX
    local real y=GetUnitY(GetEnumUnit())-ClosestY
    local real d=SquareRoot(x*x+y*y)
    if d < ClosestDistance then
        set ClosestDistance=d
        set ClosestUnit=GetEnumUnit()
    endif
endfunction
function GetClosestUnitInRange takes real x, real y, real radius, boolexpr filter returns unit
    set ClosestX=x
    set ClosestY=y
    set ClosestUnit=null
    set ClosestDistance=999999.
    call GroupEnumUnitsInRangeEx(TempGroup,x,y,radius,filter)
    call ForGroup(TempGroup,function ClosestEnum)
    return ClosestUnit
endfunction
function GetClosestUnitInGroup takes group g,real x,real y returns unit
    set ClosestX=x
    set ClosestY=y
    set ClosestUnit=null
    set ClosestDistance=999999.
    call ForGroup(g,function ClosestEnum)
    return ClosestUnit
endfunction
private function BoolTrue takes nothing returns boolean
    return true
endfunction
private function BoolFalse takes nothing returns boolean
    return false
endfunction
private function init takes nothing returns nothing
    set BOOLEXPR_TRUE=Condition(function BoolTrue)
    set BOOLEXPR_FALSE=Condition(function BoolFalse)
    set EnumFilter=Filter(function FilterFunc)
    set ConeFilter=Filter(function ConeFilterFunc)
    set RingTable=Table.create()
    set ConeTable=Table.create()
endfunction

endlibrary


Note that this is for personal uses. I don't want to share it, or anything like that.
Thanks in advance!
 

luorax

Invasion in Duskwood
Reaction score
67
That's true, Blizzard has already fixed it.
I completely forgot it :p
Thanks.
 

Dirac

22710180
Reaction score
147
You're using a bubble sort for the GetClosestUnit function, and nestharus already gave me a hard time knowing what it is, you should use a heap sort for that, search for nestharu's Binary Heap.

Please post the API to make it easier to read.

I think using a linked list to store the units inside array variables would make this even better, for the ForGroup call

EDIT: for better understanding, check the following codes
EDIT2: check the codes on the post below
 

luorax

Invasion in Duskwood
Reaction score
67
Well, first of all, thanks for the response!
I'll check that heap sort thing soon. I'll post the API as soon as I change it.
The linked list thing looks good, however:
-using an array struct would be even more efficient, isn't it?
-I'll figure out a better API for the enumeration. No matter how efficient it is, it doesn't worth the time to rewrite the whole map with that API.
 

BlackRose

Forum User
Reaction score
239
Perhaps some sort of warning if you do something you shouldn't. It's for you own use, but I still think it would be good.
 

Dirac

22710180
Reaction score
147
Well the thing is that there's no way to call functions from LinkedList groups without using trigger evalutations which are lame and slow.

I think it's for the best to write a new API, since the previous GroupUtils system dosn't use a linked list which in my opinion is a lot better and avoids function calling. Here's this version with an upgraded API
JASS:
library GroupTools
    globals
        private constant integer OFFSET=0x100000
    endglobals
    
    struct UnitGroup
        thistype next=this
        thistype prev=this
        unit FirstOfGroup
        integer count=0
        
        private static group g=CreateGroup() // the last group you need
        private static thistype tempThis
        private static integer array lookUp
        
        private static method addTo takes nothing returns boolean
            local thistype this=thistype.create()
            
            //LinkedList
            set tempThis.next.prev=this
            set this.next=tempThis.next
            set tempThis.next=this
            set this.prev=tempThis
            
            set this.FirstOfGroup=GetFilterUnit()
            set lookUp[GetHandleId(this.FirstOfGroup)-OFFSET]=this
            set tempThis.count=tempThis.count+1
            set tempThis.FirstOfGroup=this.FirstOfGroup
            
            return false
        endmethod
        
        method AddUnit takes unit whichUnit returns nothing
            local thistype thisUnit=thistype.create()
            
            //LinkedList
            set this.next.prev=thisUnit
            set thisUnit.next=this.next
            set this.next=thisUnit
            set thisUnit.prev=this
            
            set thisUnit.FirstOfGroup=GetFilterUnit()
            set lookUp[GetHandleId(thisUnit.FirstOfGroup)-OFFSET]=thisUnit
            set this.count=this.count+1
            set this.FirstOfGroup=thisUnit.FirstOfGroup
        endmethod
        
        method RemoveUnit takes unit whichUnit returns nothing
            local thistype thisUnit=lookUp[GetHandleId(whichUnit)-OFFSET]
            set thisUnit.next.prev=thisUnit.prev
            set thisUnit.prev.next=thisUnit.next
            set thisUnit.FirstOfGroup=null
            call thisUnit.destroy()
            set this.FirstOfGroup=this.next.FirstOfGroup
            set this.count=this.count-1
        endmethod
        
        method Release takes nothing returns nothing
            local thistype head=this
            set this=this.next
            loop
                exitwhen this==head
                set this=this.next
                call this.deallocate()
            endloop
            call this.deallocate()
        endmethod
        
        method EnumUnitsInRange takes real x, real y, real radius returns nothing
            set tempThis=this
            call GroupEnumUnitsInRange(g,x,y,radius,Filter(function thistype.addTo))
        endmethod
    endstruct
endlibrary
Test
JASS:
scope Tests initializer onInit
    private function onInit takes nothing returns nothing
        local UnitGroup thisGroup=UnitGroup.create()
        call thisGroup.EnumUnitsInRange(0,0,300)
        loop
            exitwhen thisGroup.count==0
            call KillUnit(thisGroup.FirstOfGroup)
            call thisGroup.RemoveUnit(thisGroup.FirstOfGroup)
        endloop
    endfunction
endscope
 

Dirac

22710180
Reaction score
147
I'll bump this thread with this:
JASS:
library/*
*********************/ GroupTools /*********************
*                      */ uses /*                      *
*         ************************************         *
*         */             Table              /*         *
*         ************************************         *
*                                                      *
********************************************************
*                         API:                         *
*                                                      *
*    UnitGroup.create()                                *
*                                                      *
*    UnitGroup.EnumUnitsInRange(locX,locY,radius)      *
*                                                      *
*    UnitGroup.AddUnit(whichUnit)                      *
*                                                      *
*    UnitGroup.RemoveUnit(whichUnit)                   *
*                                                      *
*    UnitGroup.hasUnit(whichUnit)                      *
*                                                      *
********************************************************
*                      Variables:                      *
*                                                      *
*    UnitGroup.count                                   *
*       (integer) amount of units in group             *
*    UnitGroup.FirstOfGroup                            *
*       (unit) first unit of the group                 *
*    UnitGroup.NextOfGroup                             *
*       (unit) following unit from first one           *
*                                                      *
*******************************************************/
    private struct UnitList
        thistype next
        thistype prev
        unit unit
    endstruct
    struct UnitGroup
        UnitList first
        integer count=0
        
        private static group g=CreateGroup() // the last group you need
        private static thistype tempThis
        private Table lookUp
        
        method operator FirstOfGroup takes nothing returns unit
            return this.first.unit
        endmethod
        
        method operator NextOfGroup takes nothing returns unit
            set this.first=this.first.next
            return this.first.unit
        endmethod
        
        //! textmacro GroupToolsLinkedList takes HEAD,UNIT
            local UnitList thisUnit=UnitList.create()
            local UnitList start
            
            if 0==$HEAD$.count then
                set start=thisUnit
                set $HEAD$.first=thisUnit
            else
                set start=$HEAD$.first
            endif
            
            set start.next.prev=thisUnit
            set thisUnit.next=start.next
            set start.next=thisUnit
            set thisUnit.prev=start
            
            set thisUnit.unit=$UNIT$
            set $HEAD$.lookUp[GetHandleId(thisUnit.unit)]=thisUnit
            set $HEAD$.count=$HEAD$.count+1
        //! endtextmacro
        
        private static method addTo takes nothing returns boolean
            //! runtextmacro GroupToolsLinkedList("tempThis","GetFilterUnit()")
            return false
        endmethod
        
        method AddUnit takes unit whichUnit returns nothing
            //! runtextmacro GroupToolsLinkedList("this","whichUnit")
        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
            set this.first=thisUnit.next
            call thisUnit.destroy()
            call this.lookUp.remove(h)
            set this.count=this.count-1
        endmethod
        
        method destroy takes nothing returns nothing
            local UnitList next
            loop
                exitwhen 0==this.count
                set next=this.first.next
                call this.first.destroy()
                set this.first=next
                set this.count=this.count-1
            endloop
            call this.deallocate()
        endmethod
        
        method hasUnit takes unit whichUnit returns boolean
            return this.lookUp.has(GetHandleId(whichUnit))
        endmethod
        
        method EnumUnitsInRange takes real x, real y, real radius returns nothing
            set tempThis=this
            call GroupEnumUnitsInRange(g,x,y,radius,Filter(function thistype.addTo))
        endmethod
        
        static method create takes nothing returns thistype
            local thistype this=thistype.allocate()
            set this.lookUp=Table.create()
            return this
        endmethod
    endstruct
endlibrary
Uses only 1 handle.
 

luorax

Invasion in Duskwood
Reaction score
67
Yeah, it's very cute, I like it too ;)

However, some suggestion:
-As I can see, the actual enum function doesn't support filters. I think it's a serious disadvantage.
-The UnitList struct should be an array struct. I think it's faster than normal structs.
-EnumUnitsInRange should be "enumUnitsInRange". The same goes for the other methods.

Other than that it looks good IMO. (However it's a whole new group system, not a faster version of GroupUtils :p)
 

Dirac

22710180
Reaction score
147
You yourself call the filter, as in here
JASS:
scope Tests initializer onInit
    private function onInit takes nothing returns nothing
        local UnitGroup thisGroup=UnitGroup.create()
        call thisGroup.EnumUnitsInRange(0,0,300)
        loop
            exitwhen thisGroup.count==0
            if GetWidgetLife(thisGroup.FirstOfGroup)>0.405 then
            call KillUnit(thisGroup.FirstOfGroup)
            endif
            call thisGroup.RemoveUnit(thisGroup.FirstOfGroup)
        endloop
    endfunction
endscope
i'll work on that array struct thing.
I think there's no other way to add filters to which units enter your group, but that's not really an issue if you only call actions for the correct units

EDIT: other than using Alloc i don't see a way that the struct could extend array and keep the system working
 

luorax

Invasion in Duskwood
Reaction score
67
Well, I wanted to implement different things as well, like data attaching (it was really important to attach the actual spell struct to the enumed group - it stores everything, eg. strun/slow durations, the caster, the slow factor, things like that), and I wanted to have my different enum functions in the same library. That's why I made this library for personal uses.
I was simply curious and wanted to know your opinion about it.
 

Darthfett

Aerospace/Cybersecurity Software Engineer
Reaction score
615
I see a lot here about Group Enumeration, and I'd like to bring a very well written, useful wiki page on Group Enumeration. :)

@Tooltiperror: This article explains a few useful points about the usefulness of using a FirstOfGroup loop in some scenarios, such as needing to break out of the loop early.
 

tooltiperror

Super Moderator
Reaction score
231
>break out of the loop early
I've never had to do that, so never thought of that. But yeah, I wasn't saying there are no uses for FirstOfGroup, I was just saying when possible ("use normal enumerating whenever possible") don't do that.
 

Dirac

22710180
Reaction score
147
JASS:
            if GetWidgetLife(thisGroup.FirstOfGroup)>0.405 then
            call KillUnit(thisGroup.FirstOfGroup)
            endif
            call thisGroup.RemoveUnit(thisGroup.FirstOfGroup)


please don't do this. This is bad Jass, puts us back to the way things used to be. Just use normal enumerating whenever possible.
Just plain curiosity, i really don't know. What do you mean by this??

I thought this method was way faster than using the [ljass]FirstOfGroup()[/ljass] function since it gets the unit from an array.

My system works like a unit group head attached to a unit linked list, its only attached to 1 unit, but every unit is attached to another one. It's really not important to which unit is it attached to

EDIT: forgot to mention that my system allows you to loop all the units from a group without actually removing them from it using the thisGroup.NextOfGroup operator
 

Laiev

Hey Listen!!
Reaction score
188
>> What do you mean by this??


Instead of [ljass].FirstOfGroup[/ljass] (and the loop), use [ljass].ForGroup[/ljass] or [ljass].GroupEnumUnitIn'Something'[/ljass]
 

Dirac

22710180
Reaction score
147
this is what i got for your request, it's a very crappy solution though
JASS:
// g is a initialized global group and this.NextOfGroup retrieves a different unit everytime it's used
        method forGroup takes code c returns nothing
            local integer i=0
            loop
                exitwhen i==this.count
                call GroupAddUnit(g,this.NextOfGroup)
                set i=i+1
            endloop
            call ForGroup(g,c)
            call GroupClear(g)
        endmethod


EDIT: another possible solution
JASS:
//the code should return a boolean
        method forGroup takes code c returns nothing
            local integer i=0
            loop
                exitwhen i==this.count
                call Filter(c)
                set this.first=this.first.next
                set i=i+1
            endloop
        endmethod

On a side note: is operator making slower than actually using the variable?
JASS:
method operator value takes nothing returns integer
return this.v
endmethod
 

PurgeandFire

zxcvmkgdfg
Reaction score
509
Operators are inlined as far as I know, so it should be the same. Also, Filter(c) will probably not work because then you can't reference GetEnumUnit(). (however, you could instead make a global static named "enumUnit" or something like that for the person to use in the forGroup callback)
 
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