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

Are indexing system really needed?

Status
Not open for further replies.
Level 13
Joined
Nov 7, 2014
Messages
571
What's the point of indexing systems and specifically "unit indexing systems" like (in no particular order): AutoIndex, UnitIndexer, AIDS, PUI

When the game does that for you (it reuses handle-ids), i.e:

set unit_index = H2I | GetHandleId(<unit-var>) - 0x00_10_00_00 // = 1_048_576 = MIN_HANDLE_ID .

0x00_10_00_00 should probably be the handle-id of the last "statically" allocated handle, i.e if you preload 2k+ timers, then

set MIN_HANDLE_ID = GetHandleId(timers[LAST_TIMER_INDEX]) or create trackables then

set MIN_HANDLE_ID = GetHandleId(trackables[LAST_TRACKABLE_INDEX]), etc.

Of course the map must NOT leak handle-ids (but that's somewhat easy, i.e null local variables that are handles and null struct members that are handles),
and the map doesn't have more than 8190 (dynamic handles (units, items, destructables, etc) at the same time), which is probably the case for most maps.
 
Level 9
Joined
Jul 30, 2012
Messages
156
and the map doesn't have more than 8190 (dynamic handles (units, items, destructables, etc) at the same time

I really really doubt it.

Ok, you're probably not going to have 8000 units, or 8000 items, or 8000 destructables at a time. But you're forgetting that almost everything you create is an agent. Every timer and trigger is an agent, every trigger event, condition and action is an agent, every unit, item and destructable is an agent, and every ability and buff that a unit has is also an agent. Not to mention the internal game stuff that you can't see.

So, even though I didn't test it, but I'd say it's quite impossible that even a medium size map would have less than 8000 handles at once.

EDIT: Ok, I realized that you are suggesting preloading the stuff that's not going to be destroyed, and then the limit would only apply to stuff that is dynamically created. Well in this case, I'm still skeptical about the handle amount, but I'd say it's worth a try.
 
Funnily enough, that exact system was used for the first edition of TimerUtils back before the various "flavors" existed. Similar methods were used back when "Attachment" systems were all the rage--people tried to get things to be as fast as possible.

In fact, the current TimerUtils red already uses that, along with preloaded timers (as you've mentioned).

However, there are a decent amount of reasons why it fell out of popularity:
  • First, you must assume that the map (1) doesn't leak agent ID's (2) doesn't have more than 8191 active agents at the same time (as you've said). These assumptions are easy to follow in your own map, but you can't make those assumptions with other people's maps. One periodic leak and your map can eventually crash. Also, most of the GUI group functions ("GetUnits...") leak the handle ID, so it could cause a lot of problems in GUI maps if they plan on using your system.
  • Assigning them that way is fast, but it leads to sparse arrays. Indexers are guaranteed to be packed due to their array recycling method. Also, arrays allocate memory in powers of 2 (e.g. if the highest index is 120, then the array only has 128 slots of memory allocated for it), so sparseness is bad for memory. This is minor, but interesting to know regardless. :p
  • Indexers have the option of ignoring certain units. For example, a lot of spells will ignore indexing dummy units because tons are made and you don't really need to attach much data to them. If we use handle ID's, then they automatically take up "space" we could use elsewhere. This is kinda a non-issue, but it could make a difference.
  • We are mostly concerned about access times, in the case of unit indexers. The difference between using GetHandleId() - some offset is negligible vs. GetUnitUserData(), so there isn't much incentive to switch if we have an already working version.

I think the *fear* of going over is also a bit unnerving. People don't like the prospect that things *could* break, no matter how unlikely. Take quicksort for example. It is really efficient, but it actually has worst case O(n^2) performance. It is really rare for that to happen, but it definitely scares people away a bit. Same situation here, except in this case the worst thing that could happen is a crash. :eek:
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
The destruction event feature in these libs is much more important than the unique id.

Here is the trend over the years


PUI - only handles the id portion
AutoIndex - more advanced id portion, more reliable recycling and assignment
AIDS - more advanced events, including a better destruction event. Cut back on id portion.
Unit Indexer (phase 1) - first instantaneous destruction event response using kingykingy's method that he mentioned to me in an IM
Unit Indexer (phase 2) - standardized event API
Unit Indexer (phase 3) - events running off of trees, allowing users to inject code wherever they like as well as add new sub-events


What's also always been a big deal in both DDS' and Unit Indexers has been streamlining both global and unit specific events into one common architecture and controlling the order that these events would run in. That was one of the goals of the Trigger lib. There were earlier efforts of course, including one of mine, for a variety of DDS'.


So eh... Aniki, your post is like it's written with the perspective of someone from 7 years ago : P. While a pertinent argument then, it's certainly not a pertinent argument now : ).


The latest trend, one kind of fueled just by me, is putting in excellent memory tracking and error messages and separating all of the logic from the error checking. This hasn't really caught on since it makes the dependency requirement counts explode and it's a lot of work to implement. This was part of a reaction against myself from myself ;D.
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
>8k is possible, dispersion as PurgeandFire stated and even in my own map, I would not dare to assume that there is not a single dynamic leak anywhere. Plus I do not deal in native types, I always immediately grab the wrapper like unit -> struct Unit, which has the con of this overhead but then the object is an integer on its own or but an array read.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
I think a general problem here is that most Unit Indexers are actually two-in-one systems.

A Unit Indexer (in theory) should only index units. So this sole functionality isn't that useful/important since you can achieve the same with hashtables which are just a little bit slower.

However, to achieve the task of indexing units, a Unit Indexer needs events when new unit enters and a unit definitly is removed from the game. Because of this, these events are most of the time shipped directly with the Unit Indexer.

In terms of modularity I think it would be better if those two functionalities would be seperated, because they are basically different things.

So if you need a Unit Indexer is basically a question whether those events are needed. If not, it can still have advantages in terms of performance to use a Unit Indexer, but its not really mandatory for anything.
 
Level 14
Joined
Jun 27, 2008
Messages
1,325
I think a general problem here is that most Unit Indexers are actually two-in-one systems.

A Unit Indexer (in theory) should only index units. So this sole functionality isn't that useful/important since you can achieve the same with hashtables which are just a little bit slower.

However, to achieve the task of indexing units, a Unit Indexer needs events when new unit enters and a unit definitly is removed from the game. Because of this, these events are most of the time shipped directly with the Unit Indexer.

In terms of modularity I think it would be better if those two functionalities would be seperated, because they are basically different things.

So if you need a Unit Indexer is basically a question whether those events are needed. If not, it can still have advantages in terms of performance to use a Unit Indexer, but its not really mandatory for anything.

agree, i never liked the automatic indexers but somehow they are super popular
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
I think a general problem here is that most Unit Indexers are actually two-in-one systems.

A Unit Indexer (in theory) should only index units. So this sole functionality isn't that useful/important since you can achieve the same with hashtables which are just a little bit slower.

However, to achieve the task of indexing units, a Unit Indexer needs events when new unit enters and a unit definitly is removed from the game. Because of this, these events are most of the time shipped directly with the Unit Indexer.

In terms of modularity I think it would be better if those two functionalities would be seperated, because they are basically different things.

So if you need a Unit Indexer is basically a question whether those events are needed. If not, it can still have advantages in terms of performance to use a Unit Indexer, but its not really mandatory for anything.

Well said! However, it would be very tricky to de-couple that utility into separate resources.
 
Level 7
Joined
Oct 19, 2015
Messages
286
Well said! However, it would be very tricky to de-couple that utility into separate resources.
I don't see how it's that difficult. I agree with looking_for_help, it's a 2in1 system that can easily be split. Had this idea years ago, got this code in a map of mine:
JASS:
//! zinc

library UnitExistenceEvent
{
    constant integer ABILITY_ID = 'lvdt'; // Defend-based ability for detecting unit existence failure.
    constant integer ORDER_ID = 852056; // The "undefend" order id, this probably shouldn't be changed.

// END OF CALIBRATION SECTION    
// ================================================================

    type unitFunc extends function(unit);
    unitFunc onEnterCallback[]; integer onEnterCount = 0;
    unitFunc onLeaveCallback[]; integer onLeaveCount = 0;

    group g = CreateGroup();

// ================================================================
// Public functions:

    public function onUnitCreate( unitFunc callback )
    {
        onEnterCallback[onEnterCount] = callback;
        onEnterCount = onEnterCount + 1;
        ForGroup( g, function() {
            onEnterCallback[onEnterCount-1].evaluate( GetEnumUnit() );
        });
    }

    public function onUnitRemove( unitFunc callback )
    {
        onLeaveCallback[onLeaveCount] = callback;
        onLeaveCount = onLeaveCount + 1;
    }

// ================================================================
// System code:

    function UnitEnter( unit u )
    {
        integer i;
        GroupAddUnit( g, u );
        UnitAddAbility( u, ABILITY_ID );
        UnitMakeAbilityPermanent( u, true, ABILITY_ID );
        for (0<=i<onEnterCount) { onEnterCallback[i].evaluate( u ); }
    }

    function UnitLeave( unit u )
    {
        integer i; item it;
        for (UnitInventorySize(u)>i>=0) {
            it = UnitItemInSlot( u, i );
            if (it!=null) RemoveItem(it);
        }
        it = null;
        GroupRemoveUnit( g, u );
        UnitRemoveAbility( u, ABILITY_ID );
        for (onLeaveCount>i>=0) { onLeaveCallback[i].evaluate( u ); }
    }

    function UnitFilter() -> boolean
    {
        unit u = GetFilterUnit();
        if (!(IsUnitInGroup( u, g ))) UnitEnter( u );
        u = null; return false;
    }

    function onInit()
    {
        trigger t;
        integer i;

        boolexpr b = Filter( function UnitFilter );
        region reg = CreateRegion();
        rect rec = GetWorldBounds();

        RegionAddRect( reg, rec );
        RemoveRect( rec ); rec = null;

        t=CreateTrigger();
        TriggerRegisterEnterRegion(t, reg, b);

        t=CreateTrigger();
        for (0 <= i < bj_MAX_PLAYER_SLOTS) {
            SetPlayerAbilityAvailable( Player(i), ABILITY_ID, false );
            TriggerRegisterPlayerUnitEvent( t, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, null );
        }
        TriggerAddAction(t, function() {
            unit u = GetTriggerUnit();
            if (IsUnitInGroup( u, g )) {
                if (GetUnitAbilityLevel( u, ABILITY_ID ) == 0) UnitLeave( u );
            } else if (GetIssuedOrderId() != ORDER_ID) {
                UnitEnter( u );
            }
            u = null;
        });
        for (0 <= i < bj_MAX_PLAYER_SLOTS) {
            // Run callbacks for preplaced units only after leave detection is properly set up.
            GroupEnumUnitsOfPlayer( g, Player(i), b);
        }
        
        // Testing code:
        onUnitCreate( function( unit u ){ BJDebugMsg("Entering "+GetUnitName(u)); });
        onUnitRemove( function( unit u ){ BJDebugMsg("Leaving "+GetUnitName(u)); });
    }
}

//! endzinc

Haven't bothered to write the indexing part of the package, because who needs it when you got hashtables? But I can write it now, because it's trivial:

JASS:
library AnotherUnitIndexer requires UnitExistenceEvent
    private module Init
        private static method onInit takes nothing returns nothing
            call onUnitCreate( thistype.onCreate )
            call onUnitRemove( thistype.onRemove )
        endmethod
    endmodule
    private struct Index
        static method onCreate takes unit u returns nothing
            call SetUnitUserData( u, integer(allocate()) )
        endmethod
        static method onRemove takes unit u returns nothing
            call thistype(GetUnitUserData(u)).deallocate();
        endmethod
        implement Init
    endstruct
endlibrary
 
Level 13
Joined
Nov 7, 2014
Messages
571
got this code in a map of mine:

I think when it's your map (you have full control) you don't have to bother with (onUnitCreate nor onUnitRemove) shenanigans, you could simply write:

JASS:
function CreateUnitEx takes ... returns Unit
    local Unit u = Unit.create()

    // do all kind of setup/initialization depending on parameters maybe for all the kind of systems
    // that one has in his map

    return u
endfunction

function RemoveUnitEx takes Unit u returns nothing
    // do all the destructuring for all the systems that one has in his map
endfunction

I think that's much simpler and elegant than bothering with detecting when units enter the map and when they leave.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
I think when it's your map (you have full control) you don't have to bother with (onUnitCreate nor onUnitRemove) shenanigans, you could simply write:

JASS:
function CreateUnitEx takes ... returns Unit
    local Unit u = Unit.create()

    // do all kind of setup/initialization depending on parameters maybe for all the kind of systems
    // that one has in his map

    return u
endfunction

function RemoveUnitEx takes Unit u returns nothing
    // do all the destructuring for all the systems that one has in his map
endfunction

I think that's much simpler and elegant than bothering with detecting when units enter the map and when they leave.

It actually isn't ; ).

To name a few

Trained Units
Sold Units
Built Units


Also, consider third party resources. You'd have to add a new dependency to any resource that used CreateUnit + edit them.


There are reasons why CreateUnitEx and CreateItemEx aren't used today ^_^. It also makes maintainability a nightmare. Now for any event, you'd have to inject code into the CreateUnitEx function... gg


The only thing CreateUnitEx will give you is a tightly coupled mess : )
 
Level 13
Joined
Nov 7, 2014
Messages
571
It actually isn't ; ).

To name a few

Trained Units
Sold Units
Built Units

You know, CreateUnitEx or equivalent could take the actual native unit handle, instead right?

Also, consider third party resources. You'd have to add a new dependency to any resource that used CreateUnit + edit them.

I personally wouldn't trust 3rd party resources with my units, that's what the Unit struct would be for, i.e handle all the Unit specific things in the map.

There are reasons why CreateUnitEx and CreateItemEx aren't used today ^_^. It also makes maintainability a nightmare. Now for any event, you'd have to inject code into the CreateUnitEx function... gg

I think when it's your map (you have full control)...

The only thing CreateUnitEx will give you is a tightly coupled mess : )

That's your opinion, mine is that it gives you a good idea/overview of what happens with Units in the map:

JASS:
struct Unit
    Slow_Modifier_Array slows // hey, units in my map can be slowed

    // they can also summon other Units
    Unit summoned_by
    Unit_Array summons

    // etc.
endstruct
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
You know, CreateUnitEx or equivalent could take the actual native unit handle, instead right?

I think you maybe didn't understand the problems with this... The game itself removes unused units, so you need some kind of event to detect that (how else would you know when to remove a unit).

I personally wouldn't trust 3rd party resources with my units, that's what the Unit struct would be for, i.e handle all the Unit specific things in the map.

Thats not a very practical approach. Most people don't want (and/or don't have the time) to reinvent the wheel for every system they need in their map.

If you don't want to use 3rd party resources thats ok, but one can't expect other people to do the same.
 
Level 13
Joined
Nov 7, 2014
Messages
571
I think you maybe didn't understand the problems with this... The game itself removes unused units, so you need some kind of event to detect that (how else would you know when to remove a unit).

After a non-hero unit dies the game waits for ~2 seconds then the corpse of the unit starts to sink into the terrain for "Decay Time (sec) - Flesh" (from gameplay constants).
After the sinking ends the "Decay Time (sec) - Bones" countdown begins (88 seconds by default) and after that time the game removes the unit.

That said, one simply has to change the "Decay Time (sec) - Bones" value to some "big enough value" and he/she wouldn't have to worry about the game removing his/her units, thus no detection is required (note that this way also opens the possibility of not having to remove/recreate the units, but perhaps using reincarnation or phoenix (see leandrotp's explanation here)) he/she could reuse the units, maybe).

And about summoned units, he/she would simply set a timed life with a tiny bit longer duration than that of a timer which after expiring would remove the unit.

Thats not a very practical approach. Most people don't want (and/or don't have the time) to reinvent the wheel for every system they need in their map.

If you don't want to use 3rd party resources thats ok, but one can't expect other people to do the same.

Fair enough, I guess.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Aniki, that won't work. Corpses can be exploded by artillery or loaded into meat wagons. They can also be ressurrected or temporarily reanimated. The decay can be paused. A living unit could have been removed from the game via script. I hope that you will one day realize how useful it is to have a debugged system handle these events instead of having hacky solutions which don't cover all cases.
 
Level 13
Joined
Nov 7, 2014
Messages
571
Aniki, that won't work. Corpses can be exploded by artillery or loaded into meat wagons.

EVENT_PLAYER_UNIT_DEATH fires before the unit get's removed. Flying units (some other units, as well) and killed by artillery units don't have corpses, sure.
Is that a problem? Certainly not!
For flying units or otherewise units that when killed don't make a corpse SaveInteger(my_hashtable, NO_CORPSE_UNIT, 'hgru', 1)). We can now easily query that with: LoadInteger(my_hashtable, NO_CORPSE_UNIT, GetUnitTypeId(GetTrigger|DyingUnit())) == 1

Same thing for units killed by artillery, we SaveInteger(my_hashtable, ARTILARY_UNIT, 'hmrt', 1), etc.
Was the unit killed by artillery: LoadInteger(my_hashtable, ARTILARY_UNIT, GetUnitTypeId(GetKillingUnit())) == 1

They can also be ressurrected or temporarily reanimated. The decay can be paused. A living unit could have been removed from the game via script. I hope that you will one day realize how useful it is to have a debugged system handle these events instead of having hacky solutions which don't cover all cases.

I think when it's your map (you have full control)...

I hope that you will one day realize how useful it is to have full control over your map.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
EVENT_PLAYER_UNIT_DEATH fires before the unit get's removed.

Uhm, no?

EVENT_PLAYER_UNIT_DEATH fires when a unit dies, that is something very different.

Flying units (some other units, as well) and killed by artillery units don't have corpses, sure.
Is that a problem? Certainly not!
For flying units or otherewise units that when killed don't make a corpse SaveInteger(my_hashtable, NO_CORPSE_UNIT, 'hgru', 1)). We can now easily query that with: LoadInteger(my_hashtable, NO_CORPSE_UNIT, GetUnitTypeId(GetTrigger|DyingUnit())) == 1

Same thing for units killed by artillery, we SaveInteger(my_hashtable, ARTILARY_UNIT, 'hmrt', 1), etc.
Was the unit killed by artillery: LoadInteger(my_hashtable, ARTILARY_UNIT, GetUnitTypeId(GetKillingUnit())) == 1

Won't work. At least not reliable (and only reliable solutions are acceptable).

You would have to specify dozends of special cases, and you would have to do that for every new map again. Some examples why this is too complex to be solved like this (list is not exhaustive):

  • Units that usually make corpses but are inside a zeppelin when the zeppelin is killed over water - no corpse. You would have to keep track of all units entering/leaving a zeppelin just to account for that.
  • Whats with spells that explode units (finger of death)? Just taking care about artillery is not enough.
  • Whats with spirit linked units that get killed by artillery? The killing unit will be of type artillery but still the unit doesn't explode (because it died of shared damage)?
  • Same applies for damage relection abilities.
  • Units that leave corpses can still die without leaving a corpse (just think of Alchimist Ultimate for example). You would have to track all of those.
  • How would you detect with your method when a corpse is destroyed with an artillery? Not possible at all. Same goes for ghul ability.
  • Whats with units that can be transformed? An acolyte for example leaves a corpse. Once it is transformed to a shadow, it doesn't. You would have to track those transformations or include extra checks.
  • And so on...

Maybe you can now see why this approach is not applicable at all.
 
Level 13
Joined
Nov 7, 2014
Messages
571
How would you detect with your method when a corpse is destroyed with an artillery?
Corpses don't get destroyed by artillery?

Whats with spirit linked units that get killed by artillery? The killing unit will be of type artillery but still the unit doesn't explode (because it died of shared damage)?
It seems that spirit link breaks when a unit would receive a deadly shared damage?

Units that usually make corpses but are inside a zeppelin when the zeppelin is killed over water - no corpse. You would have to keep track of all units entering/leaving a zeppelin just to account for that.
Or call the IsUnitLoaded native.

You would have to specify dozends of special cases, and you would have to do that for every new map again.
You are assuming the map uses the "vanilla" (units killing other units) gameplay, which might not be the case. Even if it is, the code could simply be reused (the list of which unit leaves corpse would probably change, of couse)?

Some examples why this is too complex to be solved like this (list is not exhaustive):

Whats with spells that explode units (finger of death)? Just taking care about artillery is not enough.
Same applies for damage relection abilities.
Units that leave corpses can still die without leaving a corpse (just think of Alchimist Ultimate for example). You would have to track all of those.
Same goes for ghul ability.
Yes, there are abilities that remove the unit but one could simply choose not use them, or write them in jass.


Whats with units that can be transformed? An acolyte for example leaves a corpse. Once it is transformed to a shadow, it doesn't. You would have to track those transformations or include extra checks.
An acolyte has a different type id than a shadow?


Won't work. At least not reliable (and only reliable solutions are acceptable).
Maybe you can now see why this approach is not applicable at all.

Maybe you can see that this approach opens other possibilities, for example a unit being killed by an artillery, having the ability to detect that the map maker could create a text tag, say "Splat" or something.

EVENT_PLAYER_UNIT_DEATH fires when a unit dies, that is something very different.

An example on_death routine, your mileage may vary:
JASS:
function OnUnitDeath takes nothing returns nothing
    local unit u = GetTrigger|DyingUnit()
    local Unit xu = GetUnitUserData(u)
    local boolean need_to_remove_unit

    set need_to_remove_unit = IsUnitLoaded(u) // we care about units dying while being loaded!
    if need_to_remove_unit then
        call RemoveUnitEx(u)
        return
    endif

    set need_to_remove_unit = LoadInteger(my_hashtable, NO_CORPSE_UNIT, GetUnitTypeId(u)) == 1
    if need_to_remove_unit then
        call RemoveUnitEx(u)
        return
    endif

    set need_to_remove_unit = LoadIneteger(my_hashtable, ARTILLERY_UNIT, GetUnitTypeId(GetKillingUnit())) == 1
    if need_to_remove_unit then
        call RemoveUnitEx(u)
        return
    endif

    call TimerStart(xu.bones_decay_timer, 88.0, false, function RemoveUnitExTimer) // when the unit get's revived, pause this timer
endfunction
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
What is this anyway? Another episode of "lets forget what we learned over the last 10 years and do all mistakes again"?

I mean, what do you get out of this? Whats the point?

Refer back to my first reply to this topic. It's ~7 years : P

So eh... Aniki, your post is like it's written with the perspective of someone from 7 years ago : P. While a pertinent argument then, it's certainly not a pertinent argument now : ).
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
An acolyte has a different type id than a shadow?

Thats why I wrote "extra checks". Which again are map specific and therefore bad for maintainance.

Or call the IsUnitLoaded native.

Doesn't work in all cases. Hit a peon with shadow strike and put it in a burrow (or in a zeppelin). When the peon dies, the native will return false although the peon is loaded and doesn't leave a corpse. How do you want to detect this?

Corpses don't get destroyed by artillery?

Artillery can destroy bridges with corpses on. How do you detect that? How do you detect when a corpse gets destroyed by ghul cannibalizm? How do you detect a loaded meatwaggon in a zeppelin getting killed over water? How do you detect devoured units?


Yes, there are abilities that remove the unit but one could simply choose not use them, or write them in jass.

So your "solution" is, "simply don't use any abilities and make everything yourself".

First option, your method is like 100 times more work (you would basically have to recode everything), will be much less efficient (all those extra checks) and it simply won't work reliable due to all the reasons already mentioned, which is the most important thing. Systems that can break in so many situations are just not useful.

Or, second option, you can just use an existing Unit Indexer which uses the standard technique and have a system that works in all cases reliable, is very efficient and is map independent, reusable and therefore almost no work to implement.

So, why should anyone ever choose option 1?

Even if you would get everything right, it would be map specific and with worse performance, so I don't get whats your point?
 
Level 13
Joined
Nov 7, 2014
Messages
571
So your "solution" is, "simply don't use any abilities and make everything yourself".

Don't use any built-in ablities/mechanics that remove units automatically, yes.

First option, your method is like 100 times more work (you would basically have to recode everything).

Again, you are assuming "vanilla" (units killing other units) gameplay that uses blizzard abilities/mechanics that remove
units automatically, which is not the case for every map. And maps that have that kind of gameplay use triggered spells/mechanics.

Artillery can destroy bridges with corpses on. How do you detect that? How do you detect when a corpse gets destroyed by ghul cannibalizm? How do you detect a loaded meatwaggon in a zeppelin getting killed over water? How do you detect devoured units?
There's a difference between detecting when a unit get's removed (this is what unit indexers enable) and detecting the cause for the removal (unit indexers don't know the cause for the removal).

So, why should anyone ever choose option 1?
Even if you would get everything right, it would be map specific and with worse performance, so I don't get whats your point?

My point is: one doesn't have to detect creation(indexing)/removal(deindexing) of units because he/she could easily arrange for them to only be created/removed via script (when the map maker decides CreateUnit/RemoveUnit need to get called), thus the cause for the removal is known without having to detect "artillery destroying bridges with corpses on", etc.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
you know, you are putting SEVERE disadvantages on users by this, and adding HEAPS and HEAPS of code(probably 2000 lines or more with your approach compared to like 50 lines with undefend approach) and calling it better? I dont know, you may see it that way, but I certainly dont.

Answer to your original question: Yes and no, unit indexers allow easy sequential access to units in array, but if you use hashtable, you can give no fucks and use handle ids.

Btw:

Again, you are assuming "vanilla" (units killing other units) gameplay that uses blizzard abilities/mechanics that remove
units automatically, which is not the case for every map.

Afaik you can very hardly, if at all, stop the vanilla effects of decay or corpse explosion or shit like that, + most maps actually use these, because noone is going to code their own artilery units, they just kind of assume when you get smacked by mortars you explode.

Also worth noting is that recoding something built into the game will be less efficient and take a lot of time and a lot of space(essentially the whole combat system, which you indirectly suggest in the quote)
 
Level 7
Joined
Oct 19, 2015
Messages
286
The maps I make all use completely triggered abilities, and I would still rather use an enter/leave detection system than code the entering and leaving myself.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
My point is: one doesn't have to detect creation(indexing)/removal(deindexing) of units because he/she could easily arrange for them to only be created/removed via script

I don't think its "easy".

There are dozends of inbuilt mechanisms that either nobody wants to recode (because they are already there and would be a lot of work to implement) or are not even possible at all.

How do you want to recode a transporting vehicle like a zeppelin with exactly the same interface and behavior like the built in one? Just doing that single unit would be a huge amount of work, if doable at all.
 
Level 13
Joined
Nov 7, 2014
Messages
571
I don't think its "easy".
If one really wanted the to have the mechanic of killing units inside transport/burrow it's not that hard to keep track of that.
When EVENT_PLAYER_UNIT_LOADED happens flag the unit as loaded and move it outisde of the playable map area, then when the unit enters the map (get's unloaded, TriggerRegisterEnterRegion) unset that flag (see AutoEvents for example implementation).

You mentioned before that:
Artillery can destroy bridges with corpses on.

How do you enable that? I know that bridges can be killed with the KillDestructable native, which plays the death animation of the bridge and lowers the units to the floor of the water. The game doesn't seem to remove the units in this case (they are stuck under the water).

About devour... didn't dota have custom devour, so I guess that should possible.

There are dozends of inbuilt mechanisms that either nobody wants to recode (because they are already there and would be a lot of work to implement) or are not even possible at all.
Well that's part of my point, i.e don't use the build-in mechanisms that cause unit removal that are not easy or even possible to detect.
 
Level 7
Joined
Oct 19, 2015
Messages
286
AutoEvents are not really a good example for your cause, since they require AutoIndex. :)

And you're fine with detecting all that stuff just so you can avoid having to detect unit enter/leave events...
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
If one really wanted the to have the mechanic of killing units inside transport/burrow it's not that hard to keep track of that.
When EVENT_PLAYER_UNIT_LOADED happens flag the unit as loaded and move it outisde of the playable map area, then when the unit enters the map (get's unloaded, TriggerRegisterEnterRegion) unset that flag (see AutoEvents for example implementation).

You mentioned before that:


How do you enable that? I know that bridges can be killed with the KillDestructable native, which plays the death animation of the bridge and lowers the units to the floor of the water. The game doesn't seem to remove the units in this case (they are stuck under the water).

About devour... didn't dota have custom devour, so I guess that should possible.


Well that's part of my point, i.e don't use the build-in mechanisms that cause unit removal that are not easy or even possible to detect.

This is getting ridiculous, for no reason you want people to stop using half of inbuilt mechanics.

Just because someone coded custom devour doesnt mean you should assume, when making PUBLICLY available, MULTI-PURPOSE unit indexer, that everyone does so.

Unit Indexers available in Spells section as well as in Jass section(there are none to be honest, one "port") are built to be multipurpose, be used in any map possible. Meanwhile your approach is very limiting, potentially per-map case, because you need to store all units that are artilery, all units that are flying etc, plus you force the maps of this hypothetical resource to not use half of the game mechanics, which means it is essentially useless.

As I said, if you do not want onIndex/Deindex events, just use hashtable, if you want those, I dont see point in coding your own 2500+ loc library, map specific, when there already is 200 loc library that makes 0 assumptions about the map.

You are essentially making hypothetical code up as problems arise from all the various ways units can be removed from the game, making case-by-case code, whereas using undefend, you dont have to worry about literally anything, putting you under hours of useless, wasted work that has been done by others a lot more efficiently before, probably even in less hours.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
It would be a wasteful split. Giving a unit a custom value is useless without the resource running the events, so that will always be more optimal as one resource. For catching when a unit exists ot is removed, yes it can happen without the UnitUserData stuff. But then you'll have reallt wonky stuff like a resource built with enter/leave events while using Hashtables and another resource doing the same but with UnitUserData. The Hashtable script was made slower and bulkier while the UnitUserData script is able to use its index as arrays and achieve cleaner code.

In the end, you'd be convoluting things and not making things easier for new players to pick up on. It's already bad enough that Nestharus' indexer got split into a million pieces.
 
Level 7
Joined
Oct 19, 2015
Messages
286
I disagree, I think it would make sense to split them. The events do not need indexing, I posted a library like that a few pages back. The indexing can then be easily built on top of the events, integrating it doesn't really result in any significant simplification.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Even if you do force the split, thousands of users and maps are already used to typical indexing system.
Don't change something that isn't broken. There's always someone like Aniki around, you should got used to that by now (in regard to '7 years' thingy).

Meanwhile, in tools/ lab sections several things/ issues have been mentioned and require more attention.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
"Don't change something that isn't broken." I really, really hate this saying, because if everyone followed this, we would still be in 70's when it comes to technologies. There is a word for such behaviour, its called advancement of technologies. Not all changes are fixes, some are reversals and some are advancements, they are crucial
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
If you care about efficiency and triggering everything, including unit creation/destruction.
Then it's pretty simple to attach an unique integer to each unit, at the overhead cost of a simple custom function instead of directly Create and Remove unit.

My point is that, really there is no reason to try to use an offset like TimerUtils red version used it.
Because it can be easily broken. And we have safer solutions, and still efficient.
 
Status
Not open for further replies.
Top