• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[System] GroupUtils

Now that everyone's using FirstOfGroup loops, I re-evaluated GroupUtils by Rising_Dusk and found that most of the code was useless s***code now.

Here's a faster, cleaner, and 'better' version of it:

JASS:
/***************************************
*
*   GroupTools
*   v1.1.2.2
*   By Magtheridon96
*
*   - Original version by Rising_Dusk
*
*   - Recycles groups, and allows the 
*   enumeration of units while taking 
*   into account collision.
*
*   API:
*   ----
*
*       - group ENUM_GROUP
*
*       - function NewGroup takes nothing returns group
*       - function ReleaseGroup takes group g returns nothing
*           - Get and release group handles
*
*       - function GroupRefresh takes group g returns nothing
*           - Refresh a group so that null units are removed
*
*       - function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
*           - Groups units while taking into account collision
*
***************************************/
library GroupTools
    
    globals
        // The highest collision size you're using in your map.
        private constant real MAX_COLLISION_SIZE = 197.
        // Data Variables
        private group array groups
        private group gT = null
        private integer gN = 0
        private boolean f = false
        // Global Group (Change it to CreateGroup() if you want)
        group ENUM_GROUP = bj_lastCreatedGroup
    endglobals
    
    static if DEBUG_MODE then
        private struct V extends array
            debug static hashtable ht = InitHashtable()
        endstruct
    endif
    
    private function AE takes nothing returns nothing
        if (f) then
            call GroupClear(gT)
            set f = false
        endif
        call GroupAddUnit(gT,GetEnumUnit())
    endfunction
    
    function GroupRefresh takes group g returns nothing
        debug if null==g then
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"[GroupUtils]Error: Attempt to refresh null group!")
            debug return
        debug endif
        set f = true
        set gT = g
        call ForGroup(gT,function AE)
        if (f) then
            call GroupClear(g)
        endif
    endfunction
    
    function NewGroup takes nothing returns group
        if 0==gN then
            return CreateGroup()
        endif
        set gN = gN - 1
        debug call SaveBoolean(V.ht,GetHandleId(groups[gN]),0,false)
        return groups[gN]
    endfunction
    
    function ReleaseGroup takes group g returns nothing
        debug if null==g then
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"[GroupUtils]Error: Attempt to release null group!")
            debug return
        debug endif
        debug if LoadBoolean(V.ht,GetHandleId(g),0) then
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"[GroupUtils]Error: Double free!")
            debug return
        debug endif
        debug call SaveBoolean(V.ht,GetHandleId(g),0,true)
        call GroupClear(g)
        set groups[gN] = g
        set gN = gN + 1
    endfunction
    
    function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
        local unit u
        debug if null==whichGroup then
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"[GroupUtils]Error: Null group passed to GroupUnitsInArea!")
            debug return
        debug endif
        call GroupEnumUnitsInRange(ENUM_GROUP,x,y,radius+MAX_COLLISION_SIZE,null)
        loop
            set u = FirstOfGroup(ENUM_GROUP)
            exitwhen null==u
            if IsUnitInRangeXY(u,x,y,radius) then
                call GroupAddUnit(whichGroup,u)
            endif
            call GroupRemoveUnit(ENUM_GROUP,u)
        endloop
    endfunction
    
endlibrary

By the way, I didn't put an empty GroupEnumUnitsInArea function so that your libraries wouldn't
compile thus notifying you of the libraries you have to fix to run off of GroupUnitsInArea :p

Note: The use of groups is very avoidable in Warcraft III (Thank you Bribe).
The purpose of this is to improve the code of people who use groups anyways.

Feel free to comment..
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
The double free protection and group > 8190 checkings in debug mode are missing. (public stuff is so fun)

OFF-TOPIC :

Mag said:
Now that everyone's using FirstOfGroup loops

Define everyone.
Coz i don't think we live in the same world, most of "jasser" (not that much still alive) probably still use ForForce.

Btw is there a valid benchmark lying there (for instants groups ofc) ?

GroupEnum + null filter + FirstOfGroup/GroupRemoveUnit loop
VS
GroupEnum + all the code in the filter (return false)
 
Well, FoG loops are logically faster than normal group enumerations with filters for 1 reason:

Assume 100 units.

The GroupEnumUnitsInRange function with a filter will open up 100 threads (similar to 100 function calls)
The FoG loop will do nothing but loop through all the units directly.

Does not insult rising dust

?
 
But GroupEnumUnitsInArea can be easily replaced with GroupUnitsInArea and a FoG loop.
If a map is using the old GU, that's fine, it doesn't have to use this one.

Oh and most of that shitcode was used to support recursion (Which I didn't find useful since FoG loops are way faster than
GroupEnumUnitsInRange with a filter, and recursion is impossible with a null filter)

edit

Silly me, I found a way to make GroupUnitsInArea faster ^^ (Will update in 24 hours)
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Glad to know that i would have lost my bet, since FoG loops are way nicer than split code in a filter.

Personnaly, with 100 enumed units and a 0.1 timer and a simple action in the loop (setting an integer to the unit custom value) i get not fps lost with a FoG loop (60 fps), but have less than 30 with a GroupEnum and a filter.

I've cared to use short names and a static group instead of create/destroy a new one each time.
My benchmark was still not a concrete case but precise enough to see that FoG is the absolute winner (since when coding in jass makes sense ? :p)


EDIT : Omg i forgot the GroupEnum before the FoG loop ...
You see, there is a reason for sharing benchmarks test codes :p
Meh, will make a valid test tomorrow ...

However FoG should still be the winner, even if Dirac used dynamic groups and no action at all for filtered units (unless he enumed 0 units).
 
EDIT : Omg i forgot the GroupEnum before the FoG loop ...
You see, there is a reason for sharing benchmarks test codes :p
Meh, will make a valid test tomorrow ...

Anitarf also made some interesting benchmarks for it, in case you want to take a look.

Anyway, the code looks good. However, you should use a global group for the GroupUnitsInArea instead of a local group.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,467
GroupUtils also has the ENUM_GROUP variable.

Dynamic groups are also completely avoidable if you use UnitIndexer and LinkedList, but I don't want to burden all users with such a heavy learning curve and installation process, so this library could still serve as a middle grounds.

However this breaks backwards compatibility so it's going to suffer the same fate as my rendition of Table if you call it like this.
 
GroupUtils also has the ENUM_GROUP variable

No one needs that ;)
You could use bj_lastCreatedGroup ^^

However this breaks backwards compatibility so it's going to suffer the same fate as my rendition of Table if you call it like this

So, should I change the name? :eek:
Table was fine bro :p
No one in their right-mind would even THINK about using Vexorian's Table nowadays ^^ (Except those veterans at wc3c.net)

If I should change the name, I guess I should go with GroupUtilities :p

edit
Updated.
- Now using Global group inside GroupUnitsInArea
- Changed name to GroupUtilities
- GroupUnitsInArea now even faster ^_^
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,467
Or GroupTools, because tool is short for utility.

bj_lastCreatedGroup could mess up if there is some recursion going on, which might catch GUI users off guard and display some odd results. It is very rare such a bug could occur but having a dedicated global group just for temporary enumeration makes sense.

ENUM_GROUP, for example, would be used instead of your overkill NewGroup/ReleaseGroup calls that you do for that enumeration in GroupUnitsInArea.

Also, it makes more sense to inline IsUnitInRangeXY than let GroupUnitsInArea do it for me.

In fact dynamic groups in general are so avoidable in most cases unlike timers. This library was originally made when null filters leaked.
 
Or GroupTools, because tool is short for utility

That will make Dirac quite jelly :eek:

Also, it makes more sense to inline IsUnitInRangeXY than let GroupUnitsInArea do it for me.

But most people wouldn't do that :eek: (Hard to remember to do it everytime :p)

bj_lastCreatedGroup could mess up if there is some recursion going on, which might catch GUI users off guard and display some odd results. It is very rare such a bug could occur but having a dedicated global group just for temporary enumeration makes sense.

ENUM_GROUP, for example, would be used instead of your overkill NewGroup/ReleaseGroup calls that you do for that enumeration in GroupUnitsInArea.

Ok, I'll add it in :3

In fact dynamic groups in general are so avoidable in most cases unlike timers. This library was originally made when null filters leaked.

Sometimes, groups are unavoidable without a lot of overhead and complications. Example:

I'm updating my SubZero spell and I found that the only way to keep track of the missiles without using some other struct is either a group or an ugly Table :/ (The Table didn't really workout though)
I also found that I needed a group for storing damaged units (So I don't damage them a second time)
 
Well, if you're ok with it, I'll change the name to GroupTools and I'll use my super cool non-existent moderator/haxxor skillz to change the thread name :p

I'll change the name to ENUM_GROUP too so I could make this system more compatible when replacing GroupUtils :p

You can use a hashtable to set a boolean to the handle id to say don't attack again (or a Table for this). If you need to iterate over it as well then you could use the new LinkedList to fix both problems.

Cool :p
I wonder why the hashtable idea didn't occur to me :eek:

edit
Done.

edit
I made a special version for people who don't use groups (except for global ones)
and inline GroupUnitsInArea (which I just started doing):

JASS:
library GroupConstants
    globals
        constant real MAX_COLLISION_SIZE = 197.0
        constant group ENUM_GROUP = CreateGroup()
    endglobals
endlibrary

rofl
 
Last edited:

BBQ

BBQ

Level 4
Joined
Jun 7, 2011
Messages
97
Personally, I see no point in group recycling. In a projectile system (or anything similar), you'd generally want to avoid an extra requirement and just handle it by yourself (which would also end up being more efficient than NewGroup() and ReleaseGroup()):
JASS:
struct projectile
{
	group affectedUnits;
	
	static method create() -> thistype
	{
		thistype this = thistype.allocate();
		
		if (this.affectedUnits == null)
			this.affectedUnits = CreateGroup();
		
		return this;
	}
	
	method destroy()
	{
		GroupClear(this.affectedUnits);
		this.deallocate();
	}
}
 
Yeah, group recycling isn't really necessary for specific systems and spells, since you will usually just need one group and only need it for an instantaneous enumeration. However, in actual maps, group recycling can become pretty handy. When using this system, there is hardly any loss of performance (very light), so it does not really do any notable harm yet gives a notable benefit. =) (and in terms of systems intended for optimization, saving handles is considered notable benefit)
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,467
The only thing I don't like about recycling handles is that there could be spikes in which an index is created but never recycled, in such a case where maybe at one part of the game you need 200 groups but for the rest of the game you only need 0-20. The surplus groups will stay recyclable but never recycled.

bj_lastCreatedGroup is good albeit you can do group ENUM_GROUP = bj_lastCreatedGroup and let the user change that to "CreateGroup()" if he so desires.
 
The only thing I don't like about recycling handles is that there could be spikes in which an index is created but never recycled, in such a case where maybe at one part of the game you need 200 groups but for the rest of the game you only need 0-20. The surplus groups will stay recyclable but never recycled.

However, recycling can still be the better of the 3 methods. If you were to use a separate group variable per use you would theoretically still end up with 200 groups. If you create and destroy, yes you will save handles, but apparently groups leak memory when they are destroyed, so there may be a net gain of memory.

So group recycling will usually be equally efficient (in terms of handles) if not better. :)
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,467
Graveyarded.

GroupUtils does group recycling already and this also breaks compatibility becuase function names are shared.

Group recycling is easy enough to inline into a library and IsUnitInRangeXY is easy enough to inline as well. For people that need to refresh a group over a duration of time it is easy enough to inline GroupRefresh or use the actual GroupUtils resource.
 
Top