• 🏆 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!

A Sample of a Unit Indexer

Status
Not open for further replies.
This is my take on a Unit Indexer:

JASS:
library UnitIndex uses optional Hashtable
    native UnitAlive takes unit u returns boolean
  
    ///! external ObjectMerger w3a Adef Dfbg anam "Leave Detection"
  
    //========================================================================================================================//
    //                                                        Unit Index                                                         //
    //                                                    By MyPad, January 16, 2017                                             //
    //========================================================================================================================//
    //                                                                                                                         //
    // Functions used:                                                                                                         //
    //========================================================================================================================//
    //                                                                                                                         //
    //        IndexUnit                                                                                                         //
    //        This indexes the unit in question, adding a certain ability called "defend". This will not index the unit if      //
    //    the unit was already indexed beforehand.                                                                             //
    //                                                                                                                         //
    //        UnindexUnit                                                                                                         //
    //        This does what it basically says, unindexing a unit.                                                             //
    //                                                                                                                         //
    //        GetUnitIndex                                                                                                     //
    //        This retrieves the index of a unit. Useful if you want to use its' index for a spell.                             //
    //                                                                                                                         //
    //========================================================================================================================//
  
    private function GetIndexFilter takes unit u returns boolean
        if GetUnitAbilityLevel(u, 'Aloc') != 0 then
            return false
        elseif IsUnitType(u, UNIT_TYPE_STRUCTURE) then
            return false
        endif
        return true
    endfunction
  
    private module UnitInit
        static HashKey UnitKey
        static HashKey UnitIdKey
        static HashKey recNext
        readonly static hashtable HASH
      
        private static method onInit takes nothing returns nothing
            local integer i = 0
            set HASH = GetUniversalHash()
            set UnitKey = GenerateKey()
            set UnitIdKey = GenerateKey()
            set recNext = GenerateKey()
            loop
                call SetPlayerAbilityAvailable(Player(i), 'Bfbg', false)
                exitwhen i == bj_MAX_PLAYER_SLOTS
                set i = i + 1
            endloop
        endmethod
    endmodule
  
    private struct UnitIndex extends array
        private static thistype count = 0
      
        implement UnitInit
      
        static method unitKeyLoad takes unit u returns integer
            return LoadInteger(HASH, UnitIdKey, GetHandleId(u))
        endmethod
      
        static method registerUnit takes unit u returns nothing
            local thistype this
            if not GetIndexFilter(u) then
                return
            endif
            if unitKeyLoad(u) != 0 then
                return
            endif
            if LoadInteger(HASH, recNext, 0) == 0 then
                set count = count + 1
                set this = count
            else
                set this = LoadInteger(HASH, recNext, 0)
                call SaveInteger(HASH, recNext, 0, LoadInteger(HASH, recNext, LoadInteger(HASH, recNext, 0)))
            endif
            call SaveUnitHandle(HASH, UnitKey, this, u)
            call SaveInteger(HASH, UnitIdKey, GetHandleId(u), this)
            call UnitAddAbility(u, 'Dfbg')
            call UnitMakeAbilityPermanent(u, true, 'Dfbg')
        endmethod
      
        static method unregisterUnit takes unit u returns nothing
            local thistype this = unitKeyLoad(u)
            if this == 0 then
                return
            endif
            call RemoveSavedHandle(HASH, UnitKey, this)
            call RemoveSavedInteger(HASH, UnitIdKey, GetHandleId(u))
            call SaveInteger(HASH, recNext, this, LoadInteger(HASH, recNext, 0))
            call SaveInteger(HASH, recNext, 0, this)
        endmethod
      
        private static method onInit takes nothing returns nothing
            local rect world = GetWorldBounds()
            local group enum = CreateGroup()
            local unit enum_u
            call GroupEnumUnitsInRect(enum, world, null)
            loop
                set enum_u = FirstOfGroup(enum)
                exitwhen enum_u == null
                call registerUnit(enum_u)
                call GroupRemoveUnit(enum, enum_u)
            endloop
            call RemoveRect(world)
            call DestroyGroup(enum)
            set world = null
            set enum = null
            set enum_u = null
        endmethod
    endstruct

    function IndexUnit takes unit u returns nothing
        call UnitIndex.registerUnit(u)
    endfunction
    function UnindexUnit takes unit u returns nothing
        call UnitIndex.unregisterUnit(u)
    endfunction
    function GetUnitIndex takes unit u returns integer
        return UnitIndex.unitKeyLoad(u)
    endfunction
  
    private struct Trigs extends array
        private static unit trained
        private static region mapArea
      
        private static method remove takes nothing returns boolean
            if GetIssuedOrderId() == OrderId("undefend") then
                debug call BJDebugMsg("was based on Defend")
                if GetUnitAbilityLevel(GetTriggerUnit(), 'Dfbg') == 0 then
                    debug call BJDebugMsg("about to remove unit")
                    call UnindexUnit(GetTriggerUnit())
                endif
            endif
            return false
        endmethod
      
        private static method condition takes nothing returns boolean
            set trained = GetTrainedUnit()
            call IndexUnit(trained)
            return false
        endmethod
      
        private static method onInit takes nothing returns nothing
            local trigger trig = CreateTrigger()
            set mapArea = CreateRegion()
            call RegionAddRect(mapArea, bj_mapInitialPlayableArea)
            call TriggerRegisterEnterRegion(trig, mapArea, null)
            call TriggerAddCondition(trig, Condition(function thistype.condition))
            set trig = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_ISSUED_ORDER)
            call TriggerAddCondition(trig, Condition(function thistype.remove))
            set trig = null
        endmethod
    endstruct
endlibrary

I wonder what could go wrong with the sample script.

P.S.:

Sorry for flooding the Lab with my posts, I just like posting.
Oh, this resource is not for public use, it is only for corrections of some sort.
 
Last edited:
Level 29
Joined
Jul 29, 2007
Messages
5,174
And I'll ask this yet again - what is the point of this when you have hashtables?

This code is a messy wrapper above hashtables that adds no benefits.

Not only is it useless, it also changes the availability of a standard ability and changes it for units, which are two too many side effects.
 
And I'll ask this yet again - what is the point of this when you have hashtables?

This code is a messy wrapper above hashtables that adds no benefits.

Not only is it useless, it also changes the availability of a standard ability and changes it for units, which are two too many side effects.

I'm guessing that this line..
call SaveInteger(HASH, recNext, 0, LoadInteger(HASH, recNext, LoadInteger(HASH, recNext, 0)))
must have caused quite some confusion. :grin:

The line above is just a hashtable version of the following:
set thistype(0).recNext = thistype(0).recNext.recNext
which confused me too at first.

As for the struct, (I assume that you're talking about it) I'm just practicing on its' syntax. No cause of harm.

By the way, @GhostWolf, do you know of the defend bug?
 
Last edited:
Level 29
Joined
Jul 29, 2007
Messages
5,174
No, I don't know of the defend bug.

Regardless, there is no confusion - what is the point of a "unit indexer"? the whole point was that hashtables did not exist, and it wasn't easy to reference to data. With hashtables, this whole concept is gone.
The only reason people still make things like this is because they are special snowflakes, and still use irrelevant code from years ago, without the capability to ever move forward.
Please don't be the same.
 
No, I don't know of the defend bug.

Regardless, there is no confusion - what is the point of a "unit indexer"? the whole point was that hashtables did not exist, and it wasn't easy to reference to data. With hashtables, this whole concept is gone.
The only reason people still make things like this is because they are special snowflakes, and still use irrelevant code from years ago, without the capability to ever move forward.
Please don't be the same.

Okay. Got that.

Anyways,

From what I've heard and experienced, the defend bug works like this, you add the custom defend ability to a unit to detect when it leaves the map (when it is either removed or when it dies.)

EDIT:

I forgot to make the necessary changes for the leave detection ability. (mostly in the external functions.) I will make the debug messages truly debugs (noun).
 
Last edited:

Deleted member 219079

D

Deleted member 219079

The events in question are not integral to unit indexing so GW's critique is quite valid.

I personally don't want to intentionally slow down my map so I use UnitDex. :)
 
If I recall, most if not all Damage Detection Systems use Unit Indexers to create their damage triggers. Some of the coders do not like to fiddle with the native SetUnitUserData but that is another story.

And

@jondrean, @IcemanBo, @GhostWolf
At this point, I have no idea what you are discussing about. Mind if I join?

Thread EDIT:

Changing title from My Attempt at a Unit Indexer to A Sample of a Unit Indexer
 
Last edited:
Level 29
Joined
Jul 29, 2007
Messages
5,174
@IcemanBo how is that different in any way to a death event and a global hashtable fetch? (logic-wise, because otherwise it adds a lot of extra redundant nonsense)

A unit indexer gives a way to fetch data based on some key (integer most likely).
A hashtable is a native structure that gives a way to fetch data based on a key (anything).

So again, why do you need an indexer?
 
Because:
Catching death event is often not enough, and otherwise you need periodicly to check the UnitTypeId to know if the unit still exists or not.

When a unit is dead, it may exist some longer, or it can be revived, if it's a hero it can stay dead for .... long amount of time.
There are definitly cases where you wanna catch the event when a unit is removed, not only dead, and then the onDeindex event comes just handy.

You can achieve similar behaviours with hashtables + some code, but imo, it's just simpler and very neat to use events sometimes.

At this point, I have no idea what you are discussing about. Mind if I join?
Sure, just share your thoughts.
 
@IcemanBo

Ah, I get the gist of your talks. You're actually discussing with Ghostwolf about its' applications, isn't it so?

...Oh, I thought of another example; what if the unit (Hero) has some sort of Reincarnation? Wouldn't this at least include the usage of a Unit Indexing System?

Also, can you get the meaning of my signature?
 
Last edited:
You're actually discussing with Ghostwolf about its' applications, isn't it so?
Just about sense of using UnitIndexer systems, yeh.

...Oh, I thought of another example; what if the unit (Hero) has some sort of Reincarnation? Wouldn't this at least include the usage of a Unit Indexing System?
Ghostwolf just says that the feature of giving unique indices to units is pretty much useless, because they already have unique handleids. You can get them with GetHandleId().
So there is not much reason to use unit indexer over hashtable in this regard.

Also, can you get the meaning of my signature?
No.^^
 

~El

Level 17
Joined
Jun 13, 2016
Messages
558
Arguably, speed is the benefit of using a unit indexer (thus, arrays) over hashtables, because hashtables in jass are stupidly slow.

I personally prefer unit indexers and arrays because of the nicer syntax, especially with vJass and it's structs. Feels much more natural than having a crapload of hashtable accessor functions.
I also believe it's cheaper in terms of OPs counter operations, so if you have heavy functions, you can get away with more with arrays, than with hashtables.
Also, giving each unit a numerical index under 8000 allows you to create linked lists (which I do use in my map) with arrays, which are also faster than using hashtables for this purpose.

Of course, none of this is an issue if you don't do anything 'heavy' in your map, but it is if you do.

Code:
public struct UnitData[] {
        private {
            static constant real WORLD_MARGIN = 0;

            integer     m_realOwner;
            integer     m_vertexR;
            integer     m_vertexG;
            integer     m_vertexB;
            integer     m_vertexA;
            integer     m_group;
            playercolor m_color;
            real        m_size;
            real        m_animSpeed;
            real        m_z;
            boolean     m_flown;
            boolean     m_allocated;
            real        m_angle;
            real        m_lastAngle;
        }

        string name;
        ...
}

This just feels very natural to me. Some frameworks like AIDS allow you very nice integration to structs like these with textmacros.
 
Level 29
Joined
Jul 29, 2007
Messages
5,174
I was waiting for someone to come up with the speed argument, just so I could bang my head on the wall, yet again. Thanks, snowflakes, you never let me down.

And even again with the "a crapload of hashtable accessor functions" when it's exactly one call. The same call you'd make for the array. Double good job.

Let me just tell you though, that hashtables can run 100k instances of any spell you can make.
As long as you can pull nonsense without ever giving any real world proof, so can I.
Subjectivity is a double edged sword, who would have known.
 
Level 13
Joined
Nov 7, 2014
Messages
571
JASS:
Let me just tell you though, that hashtables can run 100k instances of any spell you can make.

The numbers I get from this comparison (with collision off, spawning until fps drops to around 50.0 or so) between structs/arrays, hashtables and gamecache are the following:
Code:
struct/arrays: ~200 instances
hashtable: ~125 instances
gamecache: ~70 instances

so if you have heavy functions, you can get away with more with arrays, than with hashtables.
The functions need be called a lot of times as well (think 50 times per second per instance) otherwise even gamecache is sufficiently fast.
 

~El

Level 17
Joined
Jun 13, 2016
Messages
558
Wow, glad to see someone being so nice while making their point!
Are you just posting in this thread to be an ass to everyone who disagrees with you?

I agree with the speed argument being crappy - that's why I said it's arguable, but it's a thing

I don't have any qualms with hashtables, and I would use them if the interface for them in wc3 wasn't so fucking ugly, thank you very much. "unitdata" just looks nicer than "LoadInteger(hash, 0, i)". And yes, I do care about what my code looks like, because JASS is already enough of a nightmare to look at.
I already pointed out that one of my main reasons for using a unit indexer is because it looks nice in the code - nicer than using hashtable, and translates to less OPs, which means I can avoid some cases where I'd otherwise need to introduce an OP resetter into my code flow.

The other neat feature of unit indexers, as has been mentioned, is the onRemove event, which for some reason, doesn't exist as a native event.

JASS:
Let me just tell you though, that hashtables can run 100k instances of any spell you can make.

The numbers I get from this comparison (with collision off, spawning until fps drops to around 50.0 or so) between structs/arrays, hashtables and gamecache are the following:
Code:
struct/arrays: ~200 instances
hashtable: ~125 instances
gamecache: ~70 instances


The functions need be called a lot of times as well (think 50 times per second per instance) otherwise even gamecache is sufficiently fast.

It's not just about speed, it's about the OP limit, too.
 
Last edited by a moderator:
Level 13
Joined
Nov 7, 2014
Messages
571
I personally prefer unit indexers and arrays because of the nicer syntax, especially with vJass and it's structs. Feels much more natural than having a crapload of hashtable accessor functions.

I don't have any qualms with hashtables, and I would use them if the interface for them in wc3 wasn't so fucking ugly, thank you very much. "unitdata" just looks nicer than "LoadInteger(hash, 0, i)". And yes, I do care about what my code looks like, because JASS is already enough of a nightmare to look at.
I already pointed out that one of my main reasons for using a unit indexer is because it looks nice in the code - nicer than using hashtable, and translates to less OPs, which means I can avoid some cases where I'd otherwise need to introduce an OP resetter into my code flow.

You only need 1 call to SaveInteger to map a handle to a struct instance and 1 call to LoadInteger to get the struct instance from the handle:
JASS:
call SaveInteger(ht, 0, GetHandleId(some_handle), some_struct_instance)
...
set some_struct_instance = LoadInteger(ht, 0, GetHandleId(some_handle))

You shouldn't have to do this:
JASS:
set i = GetHandleId(some_handle)
call SaveInteger(ht, i, 1, m_realOwner)
call SaveInteger(ht, i, 2, m_vertexR)
call SaveInteger(ht, i, 3, m_vertexG)
call SaveInteger(ht, i, 4, m_vertexB)
call SaveInteger(ht, i, 5, m_vertexA)

to represent your example struct but with hashtables:
JASS:
public struct UnitData[] {
        private {
            static constant real WORLD_MARGIN = 0;

            integer     m_realOwner;
            integer     m_vertexR;
            integer     m_vertexG;
            integer     m_vertexB;
            integer     m_vertexA;
...

I think GhostWolf's point is to stick to structs for representing and manipulating your data but use hashtables to map from handle types to struct instances.
 
Level 29
Joined
Jul 29, 2007
Messages
5,174
I have no issue whatsoever with people disagreeing with me, nor do I have any problems with you. I do however how an issue with these arguments that keep being made based on nothing and/or on other people making these arguments based on nothing.

You are not the first, nor the last, for example, to somehow completely ignore the fact that the way you reference to your data has nothing to do with your storage. As can be seen above, the fact that a hashtable is used has nothing to do with how many calls you need - it depends on your code. If you use a struct ID, you need one call, it doesn't matter whatsoever where you store the ID.

This is like watching an argument about binary trees and how they are theoretically always faster than a loop, which is of course completely false in reality, and it just happens again and again in so many threads.

Aniki is the only person on this site so far that I've seen produce an actual test, and not repeat the same theoretical nonsense that does not necessarily correlate to reality.
Sadly, though, it is not a valid test, and is biased like the rest of the posts, e.g. for the exact reason above, where you use multiple calls instead of one, therefore not actually testing the same logic.
I also completely disagree with the choice of a physics system, which by itself is heavy, and will distort any results (not in favor of one way over the other, but just the actual difference between them).
 
Last edited:

~El

Level 17
Joined
Jun 13, 2016
Messages
558
You only need 1 call to SaveInteger to map a handle to a struct instance and 1 call to LoadInteger to get the struct instance from the handle:
JASS:
call SaveInteger(ht, 0, GetHandleId(some_handle), some_struct_instance)
...
set some_struct_instance = LoadInteger(ht, 0, GetHandleId(some_handle))

You shouldn't have to do this:
JASS:
set i = GetHandleId(some_handle)
call SaveInteger(ht, i, 1, m_realOwner)
call SaveInteger(ht, i, 2, m_vertexR)
call SaveInteger(ht, i, 3, m_vertexG)
call SaveInteger(ht, i, 4, m_vertexB)
call SaveInteger(ht, i, 5, m_vertexA)

to represent your example struct but with hashtables:
JASS:
public struct UnitData[] {
        private {
            static constant real WORLD_MARGIN = 0;

            integer     m_realOwner;
            integer     m_vertexR;
            integer     m_vertexG;
            integer     m_vertexB;
            integer     m_vertexA;
...

I think GhostWolf's point is to stick to structs for representing and manipulating your data but use hashtables to map from handle types to struct instances.

Using a hashtable to store struct ids still restricts you to a maximum of 8192 ids, and then you suddenly need a counter, and a recycling mechanism for that, too, because you need to stay under 8192 indices. Unit Indexers offer all that in one package, if you use hashtables, you'll need some code to assign indexes to units in a sensible manner.

And at that point, it becomes a question of using LoadInteger(hs, 0, GetUnitId(unit)) or GetUnitData(unit) for getting the index, and there's not much difference between either.

There is only a point to using hashtables if you use it for all unit data and forget about structs, because to benefit from them, you'd have to do exactly that:
Code:
call SaveInteger(ht, i, 1, m_realOwner)
call SaveInteger(ht, i, 2, m_vertexR)
call SaveInteger(ht, i, 3, m_vertexG)
call SaveInteger(ht, i, 4, m_vertexB)
call SaveInteger(ht, i, 5, m_vertexA)

I have no issue whatsoever with people disagreeing with me, nor do I have any problems with you. I do however how an issue with these arguments that keep being made based on nothing and/or on other people making these arguments based on nothing.

You are not the first, nor the last, for example, to somehow completely ignore the fact that the way you reference to your data has nothing to do with your storage. As can be seen above, the fact that a hashtable is used has nothing to do with how many calls you need - it depends on your code. If you use a struct ID, you need one call, it doesn't matter whatsoever where you store the ID.

This is like watching an argument about binary trees and how they are theoretically always faster than a loop, which is of course completely false in reality, and it just happens again and again in so many threads.

Aniki is the only person on this site so far that I've seen produce an actual test, and not repeat the same theoretical nonsense that does not necessarily correlate to reality.
Sadly, though, it is not a valid test, and is biased like the rest of the posts, e.g. for the exact reason above, where you use multiple calls instead of one, therefore not actually testing the same logic.
I also completely disagree with the choice of a physics system, which by itself is heavy, and will distort any results.

When the question is about where to store a struct id, then it's usually the question of whether to store it in a hashtable, or in unit data. You still need a mechanism for recycling struct ids, which what unit indexers exactly are.

Structs:
Code:
UnitData[i].property = ...
vs Non-structs:
Code:
SaveInteger(hs, i, 0, ...)

I honestly prefer the struct syntax for it's clarity.
 
Level 29
Joined
Jul 29, 2007
Messages
5,174
Thank you. Everything you wrote made perfect sense, and I can fully agree with it in this context (except for syntax, but that's obviously subjective).

It still annoys me to see people going through mind loops in other contexts (e.g. referencing things by a timer).
 
Uhh...

JASS:
constant function Get/*
 
*/N/*
 
Tr*/igger/*
 
*/Unit takes nothing returns unit
    return GetTriggerUnit()
endfunction

constant function GetN***er takes nothing returns unit? Wot.

Oh, that function was just for fun. I'll remove that later on.
I'm going to code a library for damage triggers anyway.

EDIT:

This is what it (my damage trigger library) looks like now:

JASS:
library DamageTrigger uses/*

    */ optional UnitIndex
   
    globals
        private constant integer MAX_UNITS = 50
    endglobals
   
    private module DamageTriggerInit
        readonly static hashtable HASH
        readonly static HashKey DamageTrig
        readonly static HashKey DamageGroup
        readonly static HashKey DamageUnit
        readonly static HashKey DamageEvents
        readonly static HashKey RecNext
        readonly static HashKey UnitGroup
        readonly static HashKey UnitRecycled
       
        private static method onInit takes nothing returns nothing
            set HASH = GetUniversalHash()
            //! runtextmacro assign("DamageTrig")
            //! runtextmacro assign("DamageGroup")
            //! runtextmacro assign("DamageUnit")
            //! runtextmacro assign("DamageEvents")
            //! runtextmacro assign("RecNext")
            //! runtextmacro assign("UnitGroup")
            //! runtextmacro assign("UnitRecycled")
        endmethod
       
        //! textmacro operators takes operatorfunc, var, type, typeid, nativefunc, childkey
            method operator $operatorfunc$ takes nothing returns $type$
                return Load$nativefunc$(HASH, $var$, $childkey$)
            endmethod
            method operator $operatorfunc$= takes $type$ $typeid$ returns nothing
                call Save$nativefunc$(HASH, $var$, $childkey$, $typeid$)
            endmethod
        //! endtextmacro
       
        //! runtextmacro operators("recNext", "RecNext", "thistype", "key", "Integer", "this")
        //! runtextmacro operators("loadTrig", "DamageTrig", "trigger", "t", "TriggerHandle", "this")
        //! runtextmacro operators("loadGroup", "DamageGroup", "group", "g", "GroupHandle", "this")
        //! runtextmacro operators("loadUnits", "DamageUnit", "integer", "key", "Integer", "this")
        //! runtextmacro operators("loadEvents", "DamageEvents", "integer", "key", "Integer", "this")
        static method getUnitGroup takes unit u returns integer
            return LoadInteger(HASH, UnitGroup, GetHandleId(u))
        endmethod
        static method setUnitGroup takes unit u, integer i returns nothing
            call SaveInteger(HASH, UnitGroup, GetHandleId(u), i)
        endmethod
        static method flushunitGroup takes unit u returns nothing
            call RemoveSavedInteger(HASH, UnitGroup, GetHandleId(u))
        endmethod
        static method getunitRegist takes unit u returns boolean
            return LoadBoolean(HASH, UnitRecycled, GetHandleId(u))
        endmethod
        static method setunitRegist takes unit u, boolean b returns nothing
            call SaveBoolean(HASH, UnitRecycled, GetHandleId(u), b)
        endmethod
        static method flushunitRegist takes unit u returns nothing
            call RemoveSavedBoolean(HASH, UnitRecycled, GetHandleId(u))
        endmethod
    endmodule
   
    private struct DamageTriggers
        //    temporary variables
        private static trigger damageTrig = null
        private static group damageGroup = null
        private static integer damageUnit = 0
        private static integer damageEvents = 0
        //
        private static integer count = 0
       
        implement DamageTriggerInit
       
        //====================================================================================================================//
        readonly static unit source
        readonly static unit target
        readonly static real damage
       
        private static method onDamage takes nothing returns boolean
            set source = GetEventDamageSource()
            set target = GetTriggerUnit()
            set damage = GetEventDamage()
            call DisableTrigger(GetTriggeringTrigger())
            call UnitDamageTarget(source, target, damage * 4, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
            call EnableTrigger(GetTriggeringTrigger())
            return false
        endmethod
       
        private static method generate_trigger takes trigger t returns nothing
            call TriggerAddCondition(t, Condition(function thistype.onDamage))
        endmethod
       
        static method registerUnit takes unit u returns nothing
            local thistype i
            if thistype(0).recNext == 0 then
                set i = count + 1
            else
                set i = thistype(0).recNext
            endif
            set damageTrig = i.loadTrig
            if damageTrig == null then
                set damageTrig = CreateTrigger()
                call generate_trigger(damageTrig)
                set i.loadTrig = damageTrig
            endif
            set damageGroup = i.loadGroup
            if damageGroup == null then
                set damageGroup = CreateGroup()
                set i.loadGroup = damageGroup
            endif
            set damageUnit = i.loadUnits
            set damageEvents = i.loadEvents
            if not GetIndexFilter(u) then
                debug call BJDebugMsg(I2S(i) + ": registry error")
                return
            debug else
                debug call BJDebugMsg(I2S(i) + ": attempting to register " + GetUnitName(u))
            endif
            if getUnitGroup(u) == 0 then
                if damageEvents < MAX_UNITS then
                    if not getunitRegist(u) then
                        call TriggerRegisterUnitEvent(damageTrig, u, EVENT_UNIT_DAMAGED)
                        call setunitRegist(u, true)
                        set i.loadUnits = damageUnit + 1
                    endif
                    call setUnitGroup(u, i)
                    set i.loadEvents = damageEvents + 1
                    debug call BJDebugMsg("registry success")
                else
                    if thistype(0).recNext == 0 then
                        set count = i
                    else
                        set thistype(0).recNext = thistype(0).recNext.recNext
                    endif
                    debug call BJDebugMsg("registry failed")
                    call registerUnit(u)
                endif
            endif
        endmethod
       
        private method reformat takes nothing returns nothing
            debug call BJDebugMsg("attempting to reformat the trigger")
            set this.loadEvents = 0
            call DestroyTrigger(this.loadTrig)
            call GroupClear(this.loadGroup)
            set this.recNext = thistype(0).recNext
            set thistype(0).recNext = this
            call registerUnit(null)
        endmethod
       
        static method unregisterUnit takes unit u returns nothing
            local thistype i = getUnitGroup(u)
            if i == 0 then
                debug call BJDebugMsg("Error: attempting to remove a unit which has already been removed")
                return
            endif
            debug call BJDebugMsg("Attempting to deduct the number of unit events in index " + I2S(i))
            if getunitRegist(u) then
                set i.loadUnits = i.loadUnits - 1
                call flushunitRegist(u)
                call flushunitGroup(u)
                debug call BJDebugMsg("deduction success")
            debug else
                debug call BJDebugMsg("deduction failed")
            endif
            if i.loadUnits == 0 and i.loadEvents >= MAX_UNITS then
                call i.reformat()
            endif
        endmethod
    endstruct

    private function DamageRegisterUnit takes unit u returns nothing
        call DamageTriggers.registerUnit(u)
    endfunction
    private function DamageUnregisterUnit takes unit u returns nothing
        call DamageTriggers.unregisterUnit(u)
    endfunction
   
    private module MuchInit
        private static unit enum_u
        private static group enum
       
        private static method callback takes nothing returns nothing
            set enum_u = GetEnumUnit()
            call IndexUnit(enum_u)
            call DamageRegisterUnit(enum_u)
            call GroupRemoveUnit(enum, enum_u)
        endmethod
       
        private static method onInit takes nothing returns nothing
            local rect world = GetWorldBounds()
            set enum = CreateGroup()
            call GroupEnumUnitsInRect(enum, world, null)
            call ForGroup(enum, function thistype.callback)
            call RemoveRect(world)
            call DestroyGroup(enum)
            set world = null
            set enum = null
        endmethod
    endmodule
   
    private struct InitTrigs extends array
        implement MuchInit
    endstruct
   
    private struct Trigs extends array
        private static unit trained
        private static region mapArea
       
        private static method remove takes nothing returns boolean
            if GetIssuedOrderId() == OrderId("undefend") then
                debug call BJDebugMsg("was based on Defend")
                if GetUnitAbilityLevel(GetTriggerUnit(), 'Dfbg') == 0 then
                    debug call BJDebugMsg("about to remove unit")
                    call UnindexUnit(GetTriggerUnit())
                    call DamageUnregisterUnit(GetTriggerUnit())
                endif
            endif
            return false
        endmethod
       
        private static method condition takes nothing returns boolean
            set trained = GetTrainedUnit()
            call IndexUnit(trained)
            call DamageRegisterUnit(trained)
            return false
        endmethod
       
        private static method onInit takes nothing returns nothing
            local trigger trig = CreateTrigger()
            set mapArea = CreateRegion()
            call RegionAddRect(mapArea, bj_mapInitialPlayableArea)
            call TriggerRegisterEnterRegion(trig, mapArea, null)
            call TriggerAddCondition(trig, Condition(function thistype.condition))
            set trig = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_ISSUED_ORDER)
            call TriggerAddCondition(trig, Condition(function thistype.remove))
            set trig = null
        endmethod
    endstruct
endlibrary
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
I would like to add my 2 cents.


The on remove event in a unit index system is not a cool death capture system. Death event will not capture native calls like RemoveUnit. The on remove event does exactly what it states. It doesn't capture when a unit dies. It captures when a unit no longer exists. It captures when the handle id pointing to a unit no longer points to anything valid.

This is highly useful for cleanup.


Without this, as others have mentioned, you would have to continue to poll the unit on a periodic timer. This is exactly what AIDS and other old unit indexers do. Polling a unit also has the disadvantage of not actually capturing precisely when the unit went out of scope, which can result in bugs.


Most of the the coding involved with a Unit Indexer actually goes into the onIndex and onDeindex stuff. The actual assignment of a unique id is almost nothing. This is why it is just included in systems. What really matters in these systems are how they capture events and how they run events =).

I don't even think any system except the one I coded will run the initial events on pregame units correctly.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
It's more complex than that.

JASS:
method register takes boolexpr whichExpression returns TriggerCondition
local TriggerCondition triggerCondition = Trigger(this).register(whichExpression)
call PreGameEvent.fireExpression(whichExpression)
return triggerCondition
endmethod

Consider this scenario before the game starts.


Library A creates a unit
Library B depends on Library A


Unit Indexer initializes
A initializes
-A registers to Unit Indexer
-A creates a unit
B initializes
C initializes
-C removes unit A created
-B destroys "attached" data for unit, which never got attached due to B never getting fired when the unit was created
-map crashes


You need to treat pre-game events as constant time. If something registers, all previously fired events fire again for the thing that registered in order. The best we can do in Warcraft 3 is fire all unit creation events for units that have not yet been removed for newly registered code when that code registers before a timer has elapsed.

Index adds unit to PreGame queue
Deindex removes unit from PreGame queue
Registration fires units in PreGame queue for newly registered code if a timer hasn't elapsed yet.
 
Last edited:
It's more complex than that.

JASS:
method register takes boolexpr whichExpression returns TriggerCondition
local TriggerCondition triggerCondition = Trigger(this).register(whichExpression)
call PreGameEvent.fireExpression(whichExpression)
return triggerCondition
endmethod

Consider this scenario before the game starts.


Library A creates a unit
Library B depends on Library A


Unit Indexer initializes
A initializes
-A registers to Unit Indexer
-A creates a unit
B initializes
C initializes
-C removes unit A created
-B destroys "attached" data for unit, which never got attached due to B never getting fired when the unit was created
-map crashes


You need to treat pre-game events as constant time. If something registers, all previously fired events fire again for the thing that registered in order. The best we can do in Warcraft 3 is fire all unit creation events for units that have not yet been removed for newly registered code when that code registers before a timer has elapsed.

Index adds unit to PreGame queue
Deindex removes unit from PreGame queue
Registration fires units in PreGame queue for newly registered code if a timer hasn't elapsed yet.

I think I get some of it now. In your example, the created unit was out of scope for the second library B, which does the attachment and removal of data from the unit (which I presume is its' function). However, the third library C removes the unit, which should run a Deindex event for B but it creates some sort of logical paradox leading to the crash.

All in all, I'm still confused as to the explanation stated above, but thanks for the help.
 
Status
Not open for further replies.
Top