Hello,
I had the same problem, and solved it thusly (although I'm also fairly
new to Lua so there's probably a better way).
Each Lua-visible object has a pointer to a linked-list of Lua
references to it. So each userdata contains not just a pointer to the
object, but also prev and next pointers linking it into that object's
list of userdatas.
When the userdata gets garbage-collected, you unlink it from the list,
and when the C object is deleted, you walk through its list of
userdatas and NULL out the object pointer inside each one.
So now when a script passes a stale userdata into a C function, you
can easily detect it because the userdata's enemy pointer will be NULL.
Something like this should work:
typedef struct enemy_ref_s
{
struct userdata_s *prev;
struct userdata_s *next;
class Enemy *enemy;
}
enemy_ref_t;
class Enemy
{
public:
enemy_ref_t *m_refs;
Enemy( void )
{
//Initially has no userdatas pointing to it
m_refs = 0;
}
~Enemy( void )
{
//Invalidate all userdatas pointing to this enemy
for( enemy_ref_t *ref=m_refs; ref; ref=ref->next )
ref->enemy = 0;
}
};
void new_enemy_ud( lua_State *l, Enemy *enemy )
{
//Creates a new Enemy userdata on the stack
enemy_ref_t *ref = (enemy_ref_t *)lua_newuserdata( l,
sizeof(enemy_ref_t) );
luaL_getmetatable( l, "Enemy" ); //this metatable's __gc
should point to 'enemy_ud_gc'
lua_setmetatable( l, -2 );
//Add this ud to the list of refs to this enemy
ref->enemy = enemy;
ref->prev = 0;
ref->next = enemy->m_refs;
if( ref->next ) ref->next->prev = ref;
enemy->m_refs = ref;
}
int enemy_ud_gc( lua_State *l )
{
//Remove from list of userdatas pointing to this enemy
struct userdata_s *ref = (enemy_ref_t *)luaL_checkudata( l, 1,
"Enemy" );
if( ref->prev ) ref->prev->next = ref->next;
if( ref->next ) ref->next->prev = ref->prev;
if( ref->enemy && ud==ud->enemy->m_refs )
ref->enemy->m_refs = ref->next;
return( 0 );
}
This code assumes you've set up a metatable called "Enemy", and it's
__gc points to 'enemy_ud_gc'.
It's a bit long-winded, but it works ok (and should be a lot faster
than scanning all Luas globals).
It might be nicer if each enemy could only have a single userdata
pointing to it, and you always returned the existing reference to a
given enemy instead of creating new ones. However, as a relative
newbie I'm not sure how easy that is to do.
Anyway, hope this helps (or makes sense at least).
Perhaps this is something to address on lua-users.org?
Jens Hassler wrote:
Hi there,
I am using Lua a lot in my game with great success. I exposed my
game API via toLua to my scripts. But now I have a "typical"
C/pointer problem:
In my script I do something like that:
my_enemy = enemy_class:create_enemy()
This returns a userdata (?) pointing to the enemy object which was
created by the C++ function create_enemy.
Now this enemy is destroyed and deleted inside the C++ code and
"my_enemy" is pointing to neverland (and causing a crash when accessed).
So, is there an "elegant" way to set such rogue userdata objects to nil?
Actually I work around this problem with calling a "enemy_dead()"
LUA function from C code when an enemy object is about to be
destroyed. In this function I'm looping through my globals and set
the ones pointing to the enemy to nil.
It'd be perfect If I could reset these variables directly from my
C++ code so that all Lua userdata objects will point to nil when the
enemy is destroyed.
Thanks for your help.
Jens