Skip to main content

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 certain AttackableUnit 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 using ObjectManager.ResolveNetworkId(networkId).
    You may also use .handle and ObjectManager.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.