Callback state management

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Callback state management

John Dunn
I'm attempting to write an asynchronous library that uses callbacks. From script, the callbacks look like
 
foo = Async.New()
 
function asyncCallback( async, data )
  print( "optional data is "..data )
  result, e = async:EndCall()
end
 
foo:BeginCall(asyncCallback, "optional test data")
 
Internally, I'll have a global table which will hold lightuserdata:table. The lightuserdata is a pointer to an internal structure and it's value is a table which contains the Async object, the callback function and the data argument. When the C++ function calls back, it looks up the lightuserdata in the table and calls the function, passing the object and the data as parameters.
 
Ignoring some big holes ( like I'll need to protect the lua_State when the callback comes back on another thread ) is there anything fundamentally wrong with the concept, or the following code?
 
const char* AsyncClass = "Async";
const char* AsyncInfoTable = "AsyncInfoTable";
const char* AsyncField = "Async";
const char* FunctionField = "Function";
const char* DataField = "Data";
 
class LuaAsync;
 
struct AsyncInfo
{
  lua_State* L;
};
 
class LuaAsync
{
protected:
  void CallAsync(AsyncInfo* info)
  {
    // this would actually do something here and get called back
    lua_getglobal(info->L, AsyncInfoTable);
    lua_pushlightuserdata(info->L,info);
    lua_gettable(info->L,-2);
    // remove our userdata entry from the table
    lua_pushlightuserdata(info->L,info);
    lua_pushnil(info->L);
    lua_settable(info->L,-3);
    // get the function and call it, passing in the table as the argument
    lua_getfield(info->L,-1,FunctionField);
    lua_getfield(info->L,-2,AsyncField);
    lua_getfield(info->L,-3,DataField);
    if( lua_pcall(info->L,2,0,0) )
    {
      const char* msg = lua_tostring(info->L, -1);
      lua_pop(info->L, 1);
      if( msg ) std::cout << "error : " << msg << std::endl;
      else std::cout << "error : NULL"<< std::endl;
    }
  }
public:
  int BeginCall( lua_State* L )
  {
    AsyncInfo* info = new AsyncInfo();
    info->L = L;
    lua_getglobal(L, AsyncInfoTable);
    // key is light user data
    lua_pushlightuserdata(L,info);
    // info table is
    lua_newtable(L);
    // function
    lua_pushvalue(L,2);
    lua_setfield(L,-2,FunctionField);
    // async
    lua_pushvalue(L,1);
    lua_setfield(L,-2,AsyncField);
    // data
    lua_pushvalue(L,3);
    lua_setfield(L,-2,DataField);
    lua_settable(L,-3);
    CallAsync(info);
    return 1;
  }

  int EndCall( lua_State* L )
  {
    // this would return a result or data, for right now return nothing...
    return 0;
  }
};
Reply | Threaded
Open this post in threaded view
|

Re: Callback state management

Asko Kauppi

I did this years back for SDL Audio callbacks, worked like this:

- callbacks (C side) push data onto a C-side event queue
- Lua side repeatedly (did this as part of the SDL event loop Lua  
binding) checks whether that queue has anything and
- calls Lua side callback for each event

I found this working, adequate, and safe. The locking solution might  
work, but do you really want to pend in a C-side callback (until the  
lock would be released)?  Depends on the library you're using if this  
would cause problems.

Another solution is to use Lanes or some other genuine multithreading  
solution.

-asko



John Dunn kirjoitti 4.4.2009 kello 21:49:

> I'm attempting to write an asynchronous library that uses callbacks.  
> From script, the callbacks look like
>
> foo = Async.New()
>
> function asyncCallback( async, data )
>   print( "optional data is "..data )
>   result, e = async:EndCall()
> end
>
> foo:BeginCall(asyncCallback, "optional test data")
>
> Internally, I'll have a global table which will hold  
> lightuserdata:table. The lightuserdata is a pointer to an internal  
> structure and it's value is a table which contains the Async object,  
> the callback function and the data argument. When the C++ function  
> calls back, it looks up the lightuserdata in the table and calls the  
> function, passing the object and the data as parameters.
>
> Ignoring some big holes ( like I'll need to protect the lua_State  
> when the callback comes back on another thread ) is there anything  
> fundamentally wrong with the concept, or the following code?
>
> const char* AsyncClass = "Async";
> const char* AsyncInfoTable = "AsyncInfoTable";
> const char* AsyncField = "Async";
> const char* FunctionField = "Function";
> const char* DataField = "Data";
>
> class LuaAsync;
>
> struct AsyncInfo
> {
>   lua_State* L;
> };
>
> class LuaAsync
> {
> protected:
>   void CallAsync(AsyncInfo* info)
>   {
>     // this would actually do something here and get called back
>     lua_getglobal(info->L, AsyncInfoTable);
>     lua_pushlightuserdata(info->L,info);
>     lua_gettable(info->L,-2);
>     // remove our userdata entry from the table
>     lua_pushlightuserdata(info->L,info);
>     lua_pushnil(info->L);
>     lua_settable(info->L,-3);
>     // get the function and call it, passing in the table as the  
> argument
>     lua_getfield(info->L,-1,FunctionField);
>     lua_getfield(info->L,-2,AsyncField);
>     lua_getfield(info->L,-3,DataField);
>     if( lua_pcall(info->L,2,0,0) )
>     {
>       const char* msg = lua_tostring(info->L, -1);
>       lua_pop(info->L, 1);
>       if( msg ) std::cout << "error : " << msg << std::endl;
>       else std::cout << "error : NULL"<< std::endl;
>     }
>   }
> public:
>   int BeginCall( lua_State* L )
>   {
>     AsyncInfo* info = new AsyncInfo();
>     info->L = L;
>     lua_getglobal(L, AsyncInfoTable);
>     // key is light user data
>     lua_pushlightuserdata(L,info);
>     // info table is
>     lua_newtable(L);
>     // function
>     lua_pushvalue(L,2);
>     lua_setfield(L,-2,FunctionField);
>     // async
>     lua_pushvalue(L,1);
>     lua_setfield(L,-2,AsyncField);
>     // data
>     lua_pushvalue(L,3);
>     lua_setfield(L,-2,DataField);
>     lua_settable(L,-3);
>     CallAsync(info);
>     return 1;
>   }
>
>   int EndCall( lua_State* L )
>   {
>     // this would return a result or data, for right now return  
> nothing...
>     return 0;
>   }
> };

Reply | Threaded
Open this post in threaded view
|

Re: Callback state management

Javier Guerra Giraldez
In reply to this post by John Dunn
John Dunn wrote:
> I'm attempting to write an asynchronous library that uses callbacks. >From script, the callbacks look like
>  ......
> Ignoring some big holes ( like I'll need to protect the lua_State when the callback comes back on another thread ) is there anything fundamentally wrong with the concept, or the following code?

what i did for HTT (Helper Threads Toolkit) is to create a queue where you can put the unfinished task, and a couple of functions to get the next finished task from the queue.  that way, you can either block waiting for a task, or poll the queue.

HTT's purpose is to be a common foundation for asynchronous C libraries, so if you're not too far into your design, you might want to use it.  it's on LuaForge: http://luaforge.net/projects/helper-threads/



--
Javier