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

[General] How to declare UnitAlive native from common.ai in Lua ?

Status
Not open for further replies.
Level 12
Joined
Jan 30, 2020
Messages
875
Hello there !

I know that we can use this native from common.ai native UnitAlive takes unit id returns boolean by simply declaring it in the jass script.

But now that I use Lua, how can I declare this native in order to use it in my Lua functions ?
 
Level 12
Joined
Jan 30, 2020
Messages
875
Thats interesting.

I honestly never used that native before, but it does not seem to take the integer id of the unit but a unit variable.
Why this syntax from Blizzard then "takes unit id" when a unit is definitely not an integer.

I suppose checking if an integer is dead would not make any sense, so what am I missing there ?
 
Level 20
Joined
Aug 13, 2013
Messages
1,696
I'm not experienced with Lua, but I think not IsUnitType(unit, UNIT_TYPE_DEAD) is fine.
I've encountered some problems with UnitAlive by using it on a nulled unit before.
Anyway, about that parameter named "id" is up to the user's preference so it doesn't matter.
If you feel uncomfortable with IsUnitType then a concatenation of GetUnitTypeId(unit) != 0
is best or just even use GetWidgetLife(widget) > 0.405 is ok but not 100% safe alone.

EDIT:

If you're curious enough about what problem I've encountered when using it
on a nulled unit then it returns false and after that the thread will probably not
execute anymore actions after exit. Take note that the reason why it's used on
a nulled unit is that only by accident so I guess it's safe when you're careful.
 
Last edited:
Level 12
Joined
Jan 30, 2020
Messages
875
@JAKEZINC :
Thanks for the information, if the native crashes the thread with a nil parameter, it is something to take into consideration.
Yes, so far I always had used not IsUnitType(unit, UNIT_TYPE_DEAD) to check the alive status of a unit, and after reading some stuff in this very forum since my return in December, many seemed to believe that the common.ai native was better (maybe because the native had a single parameter ?).

Anyways I had forgotten about it until I started analyzing your Fire Storm vJass code.
And I wasn't sure I could use it straight away in Lua, but now I know I could if I wanted to.

By the way, I have not really had time to do any vJass besides simple scopes, and I have not yet used OOP in my Lua script.
But your code is really nicely written and quite explicit, this will help me a lot.

My map is quite uncommon, so is my script, so I can't simply translate your spell, but as soon as I will understand the mechanics, I will adapt it to the speechifies of my map. I really look forward to it, as it is really a beautiful spell. I really appreciate you sharing it !
The first 3rd party spell in my map will be totally worth the effort, especially as it does not even use any external model.

Again, thank you !!!

@iown_azz :
Thanks, but no.
This BJ from Blizzard.j is nothing but a wrapper for another wrapper :
JASS:
function IsUnitDeadBJ takes unit whichUnit returns boolean
    return GetUnitState(whichUnit, UNIT_STATE_LIFE) <= 0
endfunction

//===========================================================================
function IsUnitAliveBJ takes unit whichUnit returns boolean
    return not IsUnitDeadBJ(whichUnit)
endfunction
And please note this "GetUnitState(whichUnit, UNIT_STATE_LIFE) <= 0" is not reliable, because some spells like Tranquility (not sure if it still does today) can actually heal dead units HP without making them alive again.
In other words, the unit would be dead but with positive HP, then the BJ function would return false as if the unit was still alive.
"not IsUnitType(unit, UNIT_TYPE_DEAD)" seems to be a perfect pick, and has never let me down before.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
not IsUnitType(u, UNIT_TYPE_DEAD) alone is not a sufficient solution since it returns a false positive if the unit was removed by decaying or via unit removing functions, as null units dont have UNIT_TYPE_DEAD and thus falsely register as alive.

If you want to catch all corner cases, this one is absolutely failure-proof:
JASS:
function IsUnitAlive takes unit u returns boolean
    return not (GetUnitTypeId(u) == 0 or IsUnitType(u, UNIT_TYPE_DEAD))
endfunction
Notice how I dont use u == null because the check fails on shadow references (when a unit is removed from the game, its reference isnt removed which causes this check to fail). Combining the dead type check with a UnitType == 0 check always works as desired even on shadow references.


I have never heard of native UnitAlive crashing threads though. Maybe its a Lua-only bug? It definitely didnt crash in JASS before (didnt test for 1.32 yet).
 
Last edited:
Level 12
Joined
Jan 30, 2020
Messages
875
Thanks for the information !

Actually, I wanted to make sure once and for all - in Lua as I am not going back to Jass - so I wrote this simple function that is called by a "+test" chat command event :
Lua:
function IsAlive(title, u)
    local s=", "
    local a1=UnitAlive(u)
    local a2=not(IsUnitType(u, UNIT_TYPE_DEAD))
    local a3=not(GetUnitTypeId(u)==0 or IsUnitType(u, UNIT_TYPE_DEAD))
    local a4=not(u==0 or IsUnitType(u, UNIT_TYPE_DEAD))
    print(title, a1, s, a2, s, a3, s, a4)
end

function Test()
    local u=CreateUnit(Player(0), FourCC("HBU1"), 0, 0, 0)
    IsAlive("Alive result: ",u)
    KillUnit(u)
    IsAlive("Killed result: ",u)
    ReviveHero(u, 0, 0, false)
    IsAlive("Revived result: ",u)
    RemoveUnit(u)
    IsAlive("Removed result: ",u)
    print("Done !")
end
And here is the result ingame :

IsAliveTest.png


So I guess (at least in Lua) we only need UnitAlive(u) or IsUnitType(u, UNIT_TYPE_DEAD)
And none of the tests crashed the thread.

Unless there is something else I missed.

EDIT :

Oops thats what I call not reaching one's own results :D
Ok so it seems none of our tests worked for a removed living unit !


Maybe an easier workaround would be to hook the RemoveUnit native to add "KillUnit" to it, although this would cause massive issues with the UNIT_DEATH events...
 
Last edited:
Level 12
Joined
Jan 30, 2020
Messages
875
Ok so I altered the code this way, using a basic tower as a unit :
Lua:
function IsAlive(title, u)
    local a1=UnitAlive(u)
    local a2=not(IsUnitType(u, UNIT_TYPE_DEAD))
    local a3=not(GetUnitTypeId(u)==0 or IsUnitType(u, UNIT_TYPE_DEAD))
    local a4=not(u==0 or IsUnitType(u, UNIT_TYPE_DEAD))
    print(title, a1, S, a2, S, a3, S, a4)
end

function Test()
    S=","
    local u=CreateUnit(Player(0), FourCC("uBLK"), 0, 0, 0)
    IsAlive("Alive result: ",u)
    KillUnit(u)
    IsAlive("Killed result: ",u)
    u=CreateUnit(Player(0), FourCC("uBLK"), 0, 0, 0)
    RemoveUnit(u)
    IsAlive("Removed result: ",u)
    print("Done !")
end

But it seems I get the same issue there :

IsAliveTest.2.png



So back to square one.


This said, in the hero context, what your wrote reminded me of a lot of issues I had with killing heroes with the KillUnit native, back in April, and that I never managed to fix without a complete workaround : Weird KillUnit() on heroes issue !
 
Level 12
Joined
Jan 30, 2020
Messages
875
First, your message made me realize I made a mistake in my last test :
"local a4=not(u==0 or IsUnitType(u, UNIT_TYPE_DEAD))" should in fact have been "local a4=not(u==nil or IsUnitType(u, UNIT_TYPE_DEAD))"

This said it didn't change a thing because removing the unit indeed does not remove its reference (I guess garbage collector does not run instantly :D)
I could see that by simply adding "print(u)" after the removal, and it clearly shows the unit reference.

So I suppose we can conclude that in some cases, it would be worth nilling agents that are destroyed or removed for safety in Lua rather than waiting for the garbage collector to come into play. I think I will hook all my destroying natives and replacing them with a version that nills their reference...

Now, this could be a Lua specific issue, so I grabbed an old version of my map from April, and made a jass version of the test :

JASS:
native UnitAlive takes unit id returns boolean

function IsAlive takes string title, unit u returns nothing
    local string S=" , "
    local boolean a1=UnitAlive(u)
    local boolean a2=not(IsUnitType(u, UNIT_TYPE_DEAD))
    local boolean a3=not(GetUnitTypeId(u)==0 or IsUnitType(u, UNIT_TYPE_DEAD))
    local boolean a4=not(u==null or IsUnitType(u, UNIT_TYPE_DEAD))
    call BJDebugMsg(title + B2S(a1) + S + B2S(a2) + S + B2S(a3) + S + B2S(a4))
endfunction

function Test takes nothing returns nothing
    local unit u=CreateUnit(Player(0), 'uBLK', 0, 0, 0)
    call IsAlive("Alive result: ",u)
    call KillUnit(u)
    call IsAlive("Killed result: ",u)
    set u=CreateUnit(Player(0), 'uBLK', 0, 0, 0)
    call RemoveUnit(u)
    call IsAlive("Removed result: ",u)
    call BJDebugMsg("Done !")
endfunction

And the result is here :

IsAliveTest.Jass.png


As you can see, and surprisingly, all tests worked perfectly but failed after the removal...

EDIT : had to fix stupid things I wrote ^^
 
Last edited:
Level 9
Joined
Mar 26, 2017
Messages
376
Apparently, if a little bit of time passes after removing a unit (0.001 seconds), the tests will return false.
So in practical situations it will work well.

Appears like UnitAlive also works well on a nulled unit.
 
Last edited:
Level 12
Joined
Jan 30, 2020
Messages
875
Ok I should probably rewrite my test with a short timer then.

EDIT :

Right, here's the new test :
Lua:
function IsAlive(u)
    local a1=UnitAlive(u)
    local a2=not(IsUnitType(u, UNIT_TYPE_DEAD))
    local a3=not(GetUnitTypeId(u)==0 or IsUnitType(u, UNIT_TYPE_DEAD))
    local a4=not(u==nil or IsUnitType(u, UNIT_TYPE_DEAD))
    return a1,a2,a3,a4
end

function Test()
    S=" , "
    local u,t=CreateUnit(Player(0), FourCC("uBLK"), 0, 0, 0),CreateTimer()
    print("Alive result: ",IsAlive(u))
    KillUnit(u)
    print("Killed result: ",IsAlive(u))
    u=CreateUnit(Player(0), FourCC("uBLK"), 0, 0, 0)
    RemoveUnit(u)
    TimerData[t]=u
    TimerStart(t, 0.01, false, function()
        local t=GetExpiredTimer()
        local u=TimerData[t]
        DestroyTimer(t)
        print("Removed result: ",IsAlive(u))
        print("Done !")
    end)
end

And indeed thee 0.01 timer made a difference :

IsAliveTest.LuaFinal.png


And as @Zwiebelchen explained, not IsUnitType(u, UNIT_TYPE_DEAD) is not enough, and needs (GetUnitTypeId==0) as an extra test while (u==nil) failed.

So I guess it was not a specific Lua issue, but just that the removal or destruction of agents is not instant whatever the scripting language.
This said, IsUnitAlive gets the same results, and might be the best choice in the end.

@pr114 : yes it confirms what you added in your edit !

I still think I will hook all my agent destructors to add nilling for safety...

Thanks a lot for all the input guys, this is really worth knowing.
 
Last edited:
Just a guess: it's known that "defend" is a detector ability, as all units gets order "undefend" for example, when they are about being removed. So it could mean that, when a unit is being removed, it stills lasts at least 1 operation thread, before it's removed, to make cleanup. As one also access the unit on those "deindex" events, which gets fired when unit is removed.
 
Level 12
Joined
Jan 30, 2020
Messages
875
Yes there are probably quite a few things the engine needs to adapt when a unit is removed, and that would explain the issue there.

As for events i don't think they are altered in any way, and will just not fire because the unit cannot trigger them anymore.
Thats why it can be a good idea to destroy triggers when they become obsolete.

The best thing to do I suppose is keep the nulling of units just after removal in Jass, and hook the natives in Lua to incorporate the nilling :)
 
Level 9
Joined
Mar 26, 2017
Messages
376
If I understand right, it might mean that using a trigger on the order 'undefend' units that are about to be deleted can be accessed?

I don't think it is ever beneficial to nil units after removal. The only application I can think of is removing killed/deleted units from lists. Otherwise the unit's list index will be in use until completion of decay and garbage collection.
 
If I understand right, it might mean that using a trigger on the order 'undefend' units that are about to be deleted can be accessed?

UnitIndexer most useful purpose (in not gui) is imo providing this leave event by catching "undefend". Here's TriggerHappy's implementation [System] UnitDex - Unit Indexer.

Example, by his code:
JASS:
local trigger t         = CreateTrigger()
 call TriggerAddCondition(t, Filter(function thistype.onLeave))
// Detect "undefend"
call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
in his onLeave function he handles some stuff, and fires the event onLeave for others, to catch it.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
It is always recommended to clean up variables after use, even with a garbage collection in place. Its just a cleaner style of programming to free your used memory immediately instead of waiting for the delayed cleanup.

Then again maybe thats just my taste.

Hooking RemoveUnit is bad style. I just dislike hooks in general for artifically slowing down code even in places where it isnt needed.

You just proved that UnitAlive is safe to use unless you use it on a removed unit inside the same thread. One could argue that this never happens in practice unless your code is some awesome piece of mom's spaghetti code.

In general, it is much easier to just do sanity checks in your code than to rely on a hook to do the dirty work.
The cases in which you would ever call UnitAlive right after RemoveUnit are non-existant.
 
Last edited:
The cases in which you would ever call UnitAlive right after RemoveUnit are non-existant.
Why not? One could easily make a trigger like:

Event
--- something happens to player 1
Actions
---- remove units on map by player 1 matching condition
---- pick units by all players on map matching condition, which are alive, add 1000 gold to owner of unit

Maybe not in same function even, but an other function might run, which then will make some checks for unit alive. In same thread where units were removed. One could guess they are removed and not alive.
 
Level 12
Joined
Jan 30, 2020
Messages
875
Yes you are probably right, and since the latest small change I made I realize I don't even do anymore alive test in my map anymore.

Besides, @Dr Super Good did bring to my attention that Lua not having any reference counter, nilling variables has no specific benefit.
I will still keep the hook on remove unit I have because it allows me to always get rid of the custom HP Bar, as I would anyways have to call it in my map everytime a unit is removed (with the only exception at map init and configuration, but then I only declare the hook when the game actually starts.

Luckily, according to some tests, it had virtually no performance impact. But thats just setting a value and a visibility so thats probably the reason why.

Thanks for you input nevertheless, learning a bit more every day :)

@IcemanBo , yes indeed that also makes sense. I suppose the most important is to know what you are doing, then decide weather or not using a hook would make sense !
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,190
Besides, @Dr Super Good did bring to my attention that Lua not having any reference counter, nilling variables has no specific benefit.
One still has to nil all references to a table or non primitive native type in order for Lua to garbage collect it. Garbage collection is done periodically by iterating the entire state graph and then freeing up anything that cannot be reached (why there is no need for reference counters).
 
Level 12
Joined
Jan 30, 2020
Messages
875
Oh well thanks indeed for clarifying that, I did not know !

Please feel assured that I really appreciate the time you spend explaining all these points that have confused me so far !
 
Level 9
Joined
Mar 26, 2017
Messages
376
I believe the 'nilling variables has no specific benefit' refers to the practice of nilling locals in JASS. The local variable itself and the handle it points to never got removed and became inaccessible after ending a function. In lua, local variables get removed immediately after ending a function (and not as part of garbage collection).

Since globals are not incrementing during playtime they will be relatively few. It is not worth it to clean them up. If you really care about micro-optimisation, you would have to consider that nilling globals has to consequence of increasing the amount of operations in your code.

The only situation I can think of where nilling is warranted is when you index your units in an incrementing global list. When a unit gets removed, you need to manually nill its list index. Otherwise the list reference will prevent cleanup of the handle.
 
Level 12
Joined
Jan 30, 2020
Messages
875
Yes thanks for this precision, I understand what you mean.

I seem to remember reading something about tables. Does this affect any kind of table or only arrays ?

I am asking because I keep using global tables to save object data, using objects as index (like in hashtables I suppose).
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Why not? One could easily make a trigger like:

Event
--- something happens to player 1
Actions
---- remove units on map by player 1 matching condition
---- pick units by all players on map matching condition, which are alive, add 1000 gold to owner of unit

Maybe not in same function even, but an other function might run, which then will make some checks for unit alive. In same thread where units were removed. One could guess they are removed and not alive.
Your example would still work fine either way, as picking units naturally will not consider removed units and also doesn't even use references in variables to begin with.
 
Last edited:
Status
Not open for further replies.
Top