Wc3C.net (http://www.wc3c.net/forums.php)
-   Scripts (http://www.wc3c.net/forumdisplay.php?f=737)
-   -   AutoIndex (http://www.wc3c.net/showthread.php?t=105643)

grim001 04-24-2009 07:23 PM

Optional add-on library: AutoEvents

AutoIndex is a very simple script to utilize. Just call GetUnitId(unit) to get get the unique value assigned to a particular unit. The GetUnitId function is extremely fast because it inlines directly to a GetUnitUserData call. AutoIndex automatically assigns an ID to each unit as it enters the map, and instantly frees that ID as the unit leaves the map. Detection of leaving units is accomplished in constant time without a periodic scan.

AutoIndex uses UnitUserData by default. If something else in your map would conflict with that, you can set the UseUnitUserData configuration constant to false, and a hashtable will be used instead. Note that hashtables are about 60% slower.

If you turn on debug mode, AutoIndex will be able to display several helpful error messages. The following issues will be detected:
  • Passing a removed or decayed unit to GetUnitId
  • Code outside of AutoIndex has overwritten a unit's UserData value.
  • GetUnitId was used on a filtered unit (a unit you don't want indexed).

AutoIndex provides events upon indexing or deindexing units. This effectively allows you to notice when units enter or leave the game. Also included are the AutoData, AutoCreate, and AutoDestroy modules, which allow you to fully utilize AutoIndex's enter/leave detection capabilities in conjunction with your structs.

Expand JASS:

moyack 04-24-2009 07:41 PM

Lol, your comments are longer that the code itself.... that's what I call a good code. Seriously. :)

akolyt0r 04-24-2009 07:59 PM

CON: you can only have 8190 units totally in your map, with regular unit-indexing, you can have 8190 indexed units in your map.

This will for example index dummy units aswell, and if you got really many triggered spells in a 12 player map with lots of units/heros/whatsoever this might hit the limit...

maybe this system shouldnt index dummy units (+= one dummy unitid constant ..), dunno...
I would add this as an optional condition ...

grim001 04-24-2009 08:00 PM

CON: Warcraft 3 crashed a long time before you had 8000 units in your map.

Magissia 04-25-2009 02:32 AM

2 Attachment(s)
In this map (look attachments)

A trigger spawn 8000 footmans, my computer don't crashed, just had some strange polys' that go away

So the game don't crash before 8000 units please

akolyt0r 04-25-2009 03:08 AM

1 Attachment(s)
no problem to have 8000 dummy units ...

grim001 04-25-2009 03:13 AM

The answer is: no.

dead_or_alivex 04-25-2009 03:14 AM

^ His point was that most maps don't reach the limit so easily. In any case, couldn't struct storage space simply be increased to handle more than 8190 units? (Edit: Oops, posted a bit late. I was responding to Magissia.)

Amazingly concise for something so elaborate - great job.


I am tired of the issues associated with H2I(u)-0x100000
What issues are these? (I use that method quite a bit.)

grim001 04-25-2009 03:35 AM

Increasing storage space would have no effect on performance except when allocating or freeing an ID. All you would have to do is add [size] next to the AutoIndex struct declaration. So it would be a great idea, except that no real map has ever, or will ever, be created that uses more than 8000 units at the same time. I'm not going to respond to any more stuff about that since it is so outlandish that it doesn't warrant discussion.

dead_or_alivex, before this I used H2I(u)-0x100000 all the time, so I think it is the next best option. But here are the downsides compared to automatic unit indexing:
1.) It is slower.
2.) It is possible to break it if handles get too high (rarely a problem in practice)
3.) It forces you to type a lot of code in every system (arrays, set/get functions, MaxHandles constant).
4.) It generates even mode code once compiled because of bigarrays.
5.) You are obligated to include a note explaining how the user should deal with the MaxHandles constant in every system.

Captain Griffen 04-25-2009 11:45 AM

If you're using 8000 units in a map, you're doing it wrong.

Anyway...I think you should have a GetUnitId function. It'll get inlined, but it'll make it far more implementation independant, and make systems much more interoperable (which, given you can only have one unit indexing system in a map using GetUnitUserData...is incredibly important). Not using a wrapper function because you think it's cooler really doesn't cut it now that they have no performance hit and provide potentially important forward/backward compatability.

Vexorian 04-25-2009 03:05 PM


2.) It is possible to break it if handles get too high (rarely a problem in practice)
Not that rare.

It requires a very messed up map, but for some reason those maps are not rare.

Like Griffen said, Make a GetUnit**** function thingy. Guys wanting speed can make it dependent on the UserData implementation (Err, not really, it'll get inlined) but if there was a GetUnit*** function you wouldn't have to make it dependent.

You can make an AutoIndex that uses handle table, Table's only problem was the lack of auto destruction, this effectively fixes it . People that already use UserData in one of those zillion other systems could just use the Table version if they had to implement a system/spell requiring it. The rest, would still see the UserData native inlined anyway. Sounds like win-win to me...


Increasing storage space would have no effect on performance except when allocating or freeing an ID.
In order to actually be able to use the index, you would need to also enlarge the arrays to which this is used as index.

Could just have an exceptions list/hash for typeids - assuming this problem is actually important.

Mr.Malte 04-25-2009 04:59 PM

I think it could make some maps really slower.
In my map for example, I've got a spell which creates 45 units per second.
If you implement this library, that spell would lag, wouldn't it.
Also if you don't use this a lot, it could be slower than other systems. Anyways, I am gonna do speed tests with this.

Vexorian 04-25-2009 05:21 PM


I think it could make some maps really slower.
In my map for example, I've got a spell which creates 45 units per second.
If you implement this library, that spell would lag, wouldn't it.


Also if you don't use this a lot, it could be slower than other systems. .


Mr.Malte 04-25-2009 05:49 PM

Ok. Then I'll test this.
This seems great.

grim001 04-25-2009 07:04 PM


Originally Posted by Captain Griffen
I think you should have a GetUnitId function.



Originally Posted by Vexorian
You can make an AutoIndex that uses handle table

Pretty good idea, handle table would not have been possible if you are relying on the UnitUserData becomes 0 trick for removal detection, but with this, any storage method is possible.

So I'll make it as soon as you make Table initialize its gamecache in a struct init, otherwise it would fail on every unit entering the map or created from a a unitEntersMap method.


Originally Posted by Mr.Malte
I think it could make some maps really slower.
In my map for example, I've got a spell which creates 45 units per second.

Seriously, creasing 45 units per second will lag your map. If you need to do something like that, you should recycle those units. Indexing an entering unit is like 0.001% of the cost of actually creating a unit.

Vexorian 04-25-2009 08:08 PM

Err, I have approved this, leaves and unitlist, but I am only going to post this notice here.

Anitarf 04-26-2009 05:06 AM

Shouldn't the AutoIndex returned by the create method be explicitly typecast to integer before being assigned as the unit's custom value, or did we stop enforcing that? Will JassHelper ever report errors on implicit struct to integer typecasting or is it considered correct usage now?

grim001 04-26-2009 05:29 AM

Fact is if it was ever "fixed" it would break a lot of things.

Anitarf 04-26-2009 05:36 AM


Originally Posted by grim001
Fact is if it was ever "fixed" it would break a lot of things.

Yes it would, which, depending on Vex's mood, could also be interpreted as "people suffering the consequences of doing things wrong". That's why I'd like to hear a final decision on this matter so we can all know what to do from there on.

Vexorian 04-26-2009 12:36 PM


Originally Posted by grim001
Fact is if it was ever "fixed" it would break a lot of things.

And it will.

Type safety is our collective friend. Code is shorter but you pay a huge price.


Shouldn't the AutoIndex returned by the create method be explicitly typecast to integer before being assigned as the unit's custom value

custom type -> integer , should work as implicit cast, the same with children->parent. Though it would probably show a warning "implicit cast!!!"

The opposite: integer->custom type or parent -> children would need an explicit cast.

So, this script is probably valid vJass. But if it did something like:

Collapse JASS:
    static method unitLeavesMap takes unit u returns nothing
     local AutoIndex a = GetUnitUserData(u)
        call a.destroy()
It would be invalid.

grim001 05-02-2009 06:54 AM

Huge update, AutoIndex has now absorbed the functionality of UnitEntersMapModule and UnitLeavesMapModule. The undefend method of removal detection has been improved a lot, since multiple defend-based skills on the same unit will no longer break it.

Also now gives you a filterfunc for ignoring stuff like dummy casters or private system units.

Vexorian 05-04-2009 04:06 AM

I was able to move it.

Tyrande_ma3x 05-04-2009 02:08 PM

I get a compile error when I try to save my map with this:
Expected: "type", "struct", "interface", "function", "keyword" or "scope"
on this line:
Collapse JASS:
    private static method unitLeavesMapCaller takes nothing returns boolean

and I have no idea why...

EDIT: Nvm, I was using an old jasshelper...

Tyrande_ma3x 05-05-2009 06:09 PM

Is it me or this script doesn't work? I always get a 0 when I use GetUnitId()...
Collapse JASS:
    local integer id = GetUnitId(cast)
    call BJDebugMsg(I2S(id))
    // Always inputs 0

grim001 05-05-2009 07:18 PM

I had a GetFilterUnit() where there should have been a GetTriggerUnit(), leftover from some change. It wasn't indexing units that enter the map after init, but that's been fixed. If you were one of the (one? two?) people using this you should go update.

Tyrande_ma3x 05-09-2009 04:06 PM

It worked after that one but now it seems like it returns a different index every time...

Let me show you the situation:
Collapse JASS:
private function AssignAsHero takes nothing returns boolean
    local unit cast = GetTriggerUnit()
    local integer id = GetUnitId(cast)
    if IsUnitType(cast, UNIT_TYPE_HERO) == true and (IS_HERO[id] == false or IS_HERO[id] == null) then
        call BJDebugMsg(I2S(id)) 
// That one printed 0.
        set IS_HERO[id] = true 
// This boolean should identify if a unit is a Hero.
// So far IS_HERO[0] should be = true.
    set cast = null
    return false

private function Init takes nothing returns nothing
    local trigger trig = CreateTrigger()
    call TriggerRegisterEnterRectSimple(trig, bj_mapInitialPlayableArea)
    call TriggerAddCondition(trig, Condition(function AssignAsHero))
I store a "property" to show if a specific unit is a Hero.

Then I have a function that picks some units in a rect and attempts to move them somewhere:
Collapse JASS:
private function ChooseArena takes nothing returns nothing
    local timer tim = GetExpiredTimer()
    local integer a = GetRandomInt(0, 1)
    local rect temp 
    call GroupEnumUnitsInRange(ENUM_GROUP, GetRectCenterX(gg_rct_Hero_Selection), GetRectCenterY(gg_rct_Hero_Selection), 3500., Condition(function GetHeroes))
    if a == 0 then
        set temp = gg_rct_Arena_One
        set temp = gg_rct_Arena_Two
        set fir = FirstOfGroup(ENUM_GROUP)
        exitwhen fir == null
        call SetUnitX(fir, GetRandomReal(GetRectMinX(temp), GetRectMaxX(temp)))
        call SetUnitY(fir, GetRandomReal(GetRectMinY(temp), GetRectMaxY(temp)))
        call IssueImmediateOrder(fir, "stop")
        call UnitResetCooldown(fir)
        call SetWidgetLife(fir, 999999.)
        call SetUnitState(fir, UNIT_STATE_MANA, 999999.)
        call UnitRemoveBuffs(fir, true, true)
        call GroupRemoveUnit(ENUM_GROUP, fir)
    call ReleaseTimer(tim)
    set tim = null
    set temp = null

As it seems, units never get moved to the other rect.
Collapse JASS:
private function GetHeroes takes nothing returns boolean
    local integer a = GetUnitIndex(GetFilterUnit())
    call BJDebugMsg(I2S(a))
    return IS_HERO[GetUnitIndex(GetFilterUnit())] == true

In my test I had only 1 Hero in gg_rct_Hero_Selection - a Warden which was assigned index 0. But when I enum the rect it returns a different value for it - 6...

grim001 05-09-2009 08:57 PM

Anytime you get 0 for the index, that means the unit hasn't been indexed yet. You are using a "Unit Enters Map" trigger that's executing before the "Unit Enters Map" trigger of AutoIndex.

Since I can't control the execution of triggers that you create, you've got two solutions: use UnitEntersMapModule to notice units entering the map, or wait until later tonight, I will add a couple new functions to register when units enter and leave the map using normal trigger syntax.

grim001 05-11-2009 11:51 PM

I'm going to give this one more overhaul sometime in the next day or two, then it should be stable. I will probably just get rid of UnitEnters/LeavesMap modules, which will significantly shorten the script and documentation, and get it under the 20k character limit. I will change RegisterUnitEntersMap and RegisterUnitLeavesMap to RegisterUnitIndexed and RegisterUnitDeindexed, respectively. They will be able to replace the functionality of the modules once function interfaces work with static methods.

grim001 05-13-2009 04:45 AM

It is updated with the previously mentioned changed, and it should stay this way now. I wasn't able to get it under the 20k character limit so I am still stuck with the lame .txt files. If for some reason you miss the modules, remember that soon function interfaces will work with static methods, meaning you will be able to accomplish the same thing without them.

Mr.Malte 05-13-2009 12:01 PM

You should make a possibilty to detect whether the units UserData are affected, for people who are unsure.
I made a system a few days ago that allows this:
It is a bit buggy when you make AllowDummies true, but it doesn't matter, since the system is not public.

grim001 05-13-2009 06:22 PM

It would be a good idea for a debug option, but AutoIndex sets the UserData as soon as units come into the map. It wouldn't be able to detect if some other system changed it after that, without doing something crazy like attaching the index the unit is "supposed" to have to the unit using Table. Then there is the fact that the UserData version doesn't need Table, and the Table version wouldn't have this problem to begin with.

Mr.Malte 05-13-2009 07:19 PM

My system works the same way like.
If a debug value is activated, and a unit enters the map, I save initiali Index and Unit.

so Index[ind] = ind
set Unit[ind] = IndexedUnit

And every 30. seconds I loop through all stored Indexes.
If GetUnitUserData of the Indexed unit isnt the stored Initial Index, UnitUserData was affected...

Tyrande_ma3x 05-14-2009 04:46 PM

I'm really glad with the newest changes and I'm happy to say that this system works like a charm.

There was a little inconsistency in your first post, though:

Originally Posted by First Post
You can use RegisterUnitEntersMap/RegisterUnitLeavesMap to execute specific functions

Those functions were replaced with different names, right?

grim001 05-14-2009 10:06 PM


I also posted new versions, the last ones were perfectly fine, but I was forced to make an incredibly small change to make AutoIndex compatible with the approved version of StatusDetect. You should update if you plan to use StatusDetect.

Tyrande_ma3x 05-17-2009 06:29 PM

Ok, there's something really wrong about this - now it unassigns indexes right after a unit's death ... That shouldn't work like that... After some unit dies (I tested with Heroes mainly), GetUnitId returns 0 for it.

grim001 05-17-2009 08:07 PM

I can't get it to do that. Will you post code or a test map?

New versions posted. RemoveUnitEx is gone. I found the most insane hack of a way to detect RemoveUnit. It also now handles units with Locust correctly. I would appreciate if people test the new version to see if there are any fixes that need to be made, I would like to declare AutoIndex to be done sometime soon.

grim001 05-20-2009 10:58 AM

New version is posted. Tyrande_ma3x's bug is fixed. Turns out that repeatedly killing and reviving heroes could cause them to get deindexed.

The entire removal detection part has actually been redone from scratch as the aforementioned flaw was unresolvable with the old method. I like the new way of doing it much better, although it relies on a crazy UnitState glitch that I found, it really does make everything work better.

wraithseeker 05-20-2009 03:04 PM

Grim I'm not sure if that AutoData module you posted to me works or not, check it out in the Triggers and Scripts. Might be a problem with it or maybe CustomStun system just bugs.

Troll-Brain 05-20-2009 04:04 PM

Collapse JASS:
private static method removeCheck takes nothing returns boolean
            set .removing[GetUnitId(GetTriggerUnit())] = not .removing[GetUnitId(GetTriggerUnit())]
            call SetWidgetLife(GetTriggerUnit(), .1)
            //Please don't ask me to explain what this method is doing, it would take way too long.
            //It's a crazy UnitState glitch that makes it possible to notice units being removed.
        return false
Sorry, but i have to ask.

grim001 05-20-2009 04:30 PM

Just consider all of the following stuff:

Turns out that UNIT_STATE_LIFE and undefend orders are the two triggers that will fire for a unit after RemoveUnit is used on a unit but before it leaves the game. Using the combination of those, I was able to find a pattern that is unique to units that had RemoveUnit used on them.

If a unit has multiple UNIT_STATE_LIFE events registered, and one of them is for = 0. life, all of the unit state events registered for a value lower (but not equal to) the unit's max health will fire when the unit dies. So when the 0 life event fires, it makes the = 0.7923512 event fire, for no reason at all. And somehow the events behave differently if you change the unit's life to not-zero during the event, and that difference is what makes units being removed by RemoveUnit distinguishable.

It is possible for a unit that is not being removed to have the method you quoted fire twice, the solution was to use a boolean and make it negate itself. So only when it runs once will the unit be considered removed.

It is really just a horrible hack, the code makes no sense, but it empirically produces the correct result. What is amazing is that I can't figure out a way to break the detection anymore. So go ahead and try.

All times are GMT. The time now is 06:02 AM.

Powered by vBulletin (Copyright ©2000 - 2020, Jelsoft Enterprises Ltd).
Hosted by www.OICcam.com
IT Support and Services provided by Executive IT Services