4

This has been driving me nuts for over a week. Below are two snippets of Lua code from a robot player in the game Bitfighter (written in C++, using a variant of LuaWrapper for binding).

When I first start the Lua script, both work exactly as expected. But after some minutes of intense object creation and destruction, variant 2 stops working, and gives me the following error:

robot.lua:253: attempt to call missing or unknown method 'getEnergy' (a nil value)

To my eyes these should function identically. Who can explain the difference?

Notes: target is a (heavy) userdata representing a C++ object. getEnergy and getHealth are properly registered C++ functions. I can reproduce this differing behavior easily. This is Lua 5.1, using the luavec mod.

Variant 1 - always works

local mt = getmetatable(target)
local pow = mt.getEnergy(target) + mt.getHealth(target)

Variant 2 - starts failing after script has been running for an arbitrary amount of time

 local pow = target:getEnergy() + target:getHealth()
Watusimoto
  • 1,773
  • 1
  • 23
  • 38

3 Answers3

5

To track what happens when it stops working you can wrap the call in pcall and explore what happened with the target value:

local ok, res = pcall(function() return target:getEnergy() + target:getHealth() end)
if not ok then
  local s = "Invalid target value: "..tostring(target).." "..type(target).."\n"
  for k, v in pairs(target) do s = s.."target "..tostring(k).." "..tostring(v).."\n" end
  for k, v in pairs(getmetatable(target)) do s = s.."meta "..tostring(k).." "..tostring(v).."\n" end
  -- add anything else that helps you figure out what happened to target
  error(res..s)
end
local pow = res
Paul Kulchenko
  • 25,884
  • 3
  • 38
  • 56
  • This hasn't led to an solution, but I'm accepting this answer because it's a useful debugging technique that I had overlooked. – Watusimoto Feb 17 '13 at 21:08
1

I strongly suspect the issue is that you have a class or struct similar to this:

struct Foo
{
    Bar bar;
    // Other fields follow
}

And that you've exposed both Foo and Bar to Lua via LuaWrapper. The important bit here is that bar is the first field on your Foo struct. Alternatively, you may have some class that inherits from some other base class and both the derived and base class are exposed to LuaWrapper.

LuaWrapper uses an function called an Identifier to uniquely track each object (like whether or not the given object has already been added to the Lua state). By default it uses the object address as a key. In cases like the one posed above it is possible that both Foo and Bar have the same address in memory, and thus LuaWrapper can get confused.

This may result in grabbing the wrong object's metatable when attempting to look up a method. Clearly, since it's looking at the wrong metatable it won't find the method you want, and so it will appear as if your metatable has mysteriously lost entries.

I've checked in a change that tracks each object's data per-type rather than in one giant pile. If you update your copy LuaWrapper to latest one from the repository I'm fairly certain your problem will be fixed.

Alex
  • 14,973
  • 13
  • 59
  • 94
  • I think you are at least partially correct, and I'm hoping (really, really hoping!) your recent updates to LuaWrapper have fixed the underlying issue, but it still leaves the question about why one of my examples works and the other fails. I would expect that both would fail in the scenario you describe. – Watusimoto Feb 26 '13 at 10:39
0

When you say it stops working at some stage, but works fine before... Could it be that you overwrite the .getEnergy function anywhere at runtime? Maybe you're running a foo.getEnergy = nil instead of a foo.getEnergy == nil somewhere? Sounds like it might be a late initialization gone awry :)

ckaotik
  • 191
  • 1
  • 5
  • That is not happening; the proof is in that the first variant continues to work. If I overwrote getEnergy, both would break. And also, I tried dumping the metatable before each call, and confirmed the function still exists. – Watusimoto Feb 03 '13 at 23:23