lua-users home
lua-l archive

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


It was thus said that the Great Geoff Smith once stated:
> Hello Andrew
>  
> Thanks again for trying to assist,  I think we just proved its tricky to
> formulate a clear question on a mailing list and to understand a partially
> complete piece of code out of context.
>  
> Let me try to clarify the question again
>  
> I want to be able to call a fixed name Lua function on my btn userdata
> instance.
>  
> btn1 = newButton(x,y, etc)
>  
> function btn1:action()
>       print("this button was pressed")
> end
> 
> I think I know how to code this alternate technique, but having a fixed
> btn1:action function seemed a better idea and saves the user having to
> register a function when the button is created.
>  
> And anyway this is bugging the heck out of me now and I want to solve it !
> :) If I ever figure it out I will write a web page up on it. The technique
> to call a fixed name Lua defined function on a userdata instance is
> currently sadly lacking in all of the Lua literature out there.

  I took a stab at it, using your original code as a base.  I think this
will work, but this is untested code.  It's commented so you should be able
to figure out what I did.  Please don't hesitate to ask any questions.

  -spc

static lua_State *L_Store;

/***************************************************************
* Call a fixed Lua function based on the object.  There's a reference to the
* Lua based object in the Lua registry (see comment in buttonlua_new()). We
* then get the "action" field (which should be a function); if it doesn't
* exist, we just exit (no action, no foul).  Otherwise, we call the function
* with the Lua based object as a parameter.
****************************************************************/

void button_event_to_lua(OSLIB_OBJECT *const obj)
{
  lua_pushlightuserdata(L_Store,obj);
  lua_gettable(L,LUA_REGISTRY);  
  lua_getfield(L,-1,"action");
  
  if (lua_isnil(L,-1))
  {
    lua_pop(L,2);
    return
  }
  
  lua_pushvalue(L,-2);
  lua_call(L,1,0);
  lua_pop(L,1);
}

/************************************************************************/

static int buttonlua___tostring(lua_State *const L)
{
  /*-------------------------------------------------------------------------
  ; luaL_checkudata() will either never return (if the userdata isn't of the
  ; right type---it throws an error) or will always return and never return
  ; NULL.  So there's no reason to check the result from luaL_checkudata().
  ;-------------------------------------------------------------------------*/
  
  lua_pushfstring(
  	L,
  	"button:%p",
  	(void *)luaL_checkudata(L,1,GRAPHICS_BUTTON_TYPE)
  );
  return 1;
}

/****************************************************************************
* Lua has determined that this is garbage, and is calling our finalizer. We
* destroy the reference to our data in the Lua registry, then destroy our
* object.
*
* Ohh, I just thought of something---is the registry keep weak references?  
* If not, then Lua will never consider this for garbage collection, and
* we're responsible for destroying it.  Hmm ... we might have to create our
* own reference table with weak values, but I'll leave that up as an
* exercise for the reader.
****************************************************************************/

static int buttonlua___gc(lua_State *const L)
{
  OSLIB_OBJECT **button = luaL_checkudata(L,1,GRAPHICS_BUTTON_TYPE);
  
  lua_pushlightuserdata(L,*button);
  lua_pusnnil(L);
  lua_settable(L,LUA_REGISTRY);

  destoryObjectByPtr(*button);
  return 0;
}

/****************************************************************************/

static int buttonlua___index(lua_State *L)
{
  lua_getfenv(L,1);
  lua_replace(L,1);
  lua_gettable(L,1);
  return 1;
}

/****************************************************************************/

static int buttonlua___newindex(lua_State *const L)
{
  lua_getfenv(L,1);
  lua_replace(L,1);
  lua_settable(L,1);
  return 0;
}

/**************************************************************************/

static int buttonlua_show(lua_State *L)
{
  OSLIB_OBJECT **button = luaL_checkudata(L,1,GRAPHICS_BUTTON_TYPE);

  OSLIB_InvalidateObject(*button,1);
  OSLIB_ShowObject(*button,1);
  return 0;
}

/**************************************************************************/

static const luaL_Reg MapMethods[] =
{
  { "show"		, buttonlua_show	} ,
  { NULL		, NULL			}
};

static int buttonlua_new(Lua_State *L)
{
  int            x;
  int            y;
  int            width;
  int            height;
  int            radius;
  const char    *text;
  const char    *name;
  OSLIB_OBJECT **button;
  
  /*-------------------------------------------------------------------
  ; there is no need to check the number of arguments---the various
  ; luaL_check*() functions will error out if any of the arguments are
  ; missing.
  ;--------------------------------------------------------------------*/
  
  name   = luaL_checkstring(L,1);
  x      = luaL_checkint(L,2);
  y      = luaL_checkint(L,3);
  width  = luaL_checkint(L,4);
  ht     = luaL_checkint(L,5);
  radius = luaL_checkint(L,6);
  text   = luaL_checkstring(L,7);
  
  button = lua_newuserdata(L,sizeof(OSLIB_OBJECT));
  memset(button,0,sizeof(OSLIB_OBJECT));
  *button = createObject(LIB_GRAPHICS_BUTTON,name);
  
  /*-------------------------------------------------------------------
  ; If the button couldn't be created, return nil.  Since there's no
  ; reference to the userdata still on the stack, it will be reclaimed in
  ; the next garbage collection cycle, so we don't worry about it.
  ;----------------------------------------------------------------------*/
  
  if (*button == NULL)
  {
    lua_pushnil(L);
    return 1;
  }
  
  /*----------------------------------------------------------------------
  ; push a reference to our userdata into the Lua registry.  This way, we
  ; can find it based on the object pointer the rest of the system uses.
  ;----------------------------------------------------------------------*/
  
  lua_pushlightuserdata(L,*button);
  lua_pushvalue(L,-2);
  lua_settable(L,LUA_REGISTRY);
  
  /*-----------------------------------------------------------------
  ; create an environment for our userdata to store arbitrary Lua values (so
  ; we can treat our userdata as a Lua table).  Since the __index and
  ; __newindex methods refer to this table, we need to store non-metamethods
  ; here, so prepopulate with some functions.
  ;---------------------------------------------------------------*/
  
  lua_createtable(L,0,0);
  luaL_register(L,NULL,MapMethods);
  lua_setfenv(L,-2);
  
  /*-------------------------------------
  ; initialize our object
  ;--------------------------------------*/
  
  OSLIBx_SetTextUtf8(*button,buttonText);
  (*button)->position.x = x;
  (*button)->position.y = y;
  (*button)->size.cx    = width;
  (*button)->size.cy    = ht;
  (*button)->radius     = radius;
  
  luaL_getmetatable(L,GRAPHICS_BUTTON_TYPE);  
  lua_setmetatable(L,-2);
  return 1;
}

/**************************************************************************/

static const luaL_Reg MetaMap[] =
{
  { "__tostring"	, buttonlua___tostring	} ,
  { "__gc"		, buttonlua___gc	} ,
  { "__index"		, buttonlua___index	} ,
  { "__newindex"	, buttonlua___newindex	} ,
  { NULL		, NULL			}
};

static const luaL_Reg Map[] =
{
  { "newButton"		, buttonlua_new		} ,
  { NULL		, NULL			}
};

LUALIB_API int luaopen_BookButton(lua_State *L)
{
  L_Store = L;
  
  luaL_newmetatable(L,GRAPHICS_BUTTON_TYPE);
  luaL_register(L,NULL,MetaMap);  
  luaL_register(L,"graphics",Map);
  
  return 1;
}