C++ objects refs in Lua
This includes:
- SDK classes & structs
- Game entities:
GameObject
derived classes - Callback args:
CastArgs
,CastSpellArgs
,IssueOrderArgs
etc - And so on...
Avoid storing such objects in Lua variables which go out of your current usage scope unless you can handle disposal of such objects yourself.
This means it's fine to store instances of things you create and dispose yourself, such as Menu
components, SDKSpell
and things like this. But it's not fine to store game objects from OnObjectCreate
event or cast args from OnProcessSpell
event. This can lead to unexpected behavior, errors and even crashes.
Problem
Explanation
Think of basic example:
You have a variable called
myTarget
for your main target and during runtime you want it to store certainAttackableUnit
as target to attack.
So you end up with some code which assigns minions or enemy champions to myTarget
and rely on this code to check if it's viable target:
local myTarget
Callback.Bind(CallbackType.OnObjectCreate, function(obj)
-- Assign some minion to myTarget
end)
function AttackMyTarget()
if myTarget and myTarget:IsValid() then
-- Attack myTarget
end
end
On paper this should work fine, however, what if you assign a minion which gets killed and game disposes of it, but your Lua script still holds reference or pointer to it?
myTarget
won't be nil, but it will point to invalid object. Previously, in LS, calling myTarget:IsValid()
method would throw exception, possibly corrupt the thread or even cause the crash. Recently this part was improved, we prevent crash in this particular example (GameObject:IsValid()
), but we don't do it in other cases.
DelayAction
This problem can also happen in not so obvious scenarios, such as using DelayAction
. Think of this code:
Callback.Bind(CallbackType.OnTick, function()
-- body of any event where you find some target, store it in a variable and then pass it to action function argument of DelayAction:
local myTarget = GetSomeTarget()
Common.DelayAction(function()
if myTarget and myTarget:IsValid() then
AttackTarget(myTarget)
end
end, 0)
end)
If myTarget
will be disposed - calling myTarget:IsValid()
will throw exception. This may happen even with 0 delay in DelayAction
, which is supposed to delay action only by 1 tick. Sometimes objects can be created and deleted in the same tick.
Solution
- If it's
GameObject
or its derived class then simply store.networkId
and retrieve the object later usingObjectManager.ResolveNetworkId(networkId)
.
You may also use.handle
andObjectManager.ResolveHandle(handle)
in some cases, but we suggest using.networkId
instead and here's why. - If it's some other class/structure such as buff info, spell info, Evade skillshot or something like that - just store the data you need specifically, don't store the whole object.