Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
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 ?
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.
@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.
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).
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 :
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 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...
I think in your case the removal test failed because you tried to remove a hero unit.
Those can not be cleanly removed from the game and linger around even when removed via RemoveUnit.
This is why GetUnitTypeId == 0 failed in your case. Repeat your test with a normal unit and see if it works.
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 :
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 !
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 )
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 :
As you can see, and surprisingly, all tests worked perfectly but failed after the removal...
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.
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 :
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.
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.
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
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.
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.
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.
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.
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 !
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).
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.
I think it goes for all kinds of tables, yeah.
Not 100% sure as handle cleanup is done in the game engine. But I cannot imagine a list key not counting as reference.
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.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.