lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


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