#include "lua_thread.h"
#include <string.h>
#include <assert.h>
#include <lua5.1/lauxlib.h>
void lua_thread_init (struct lua_thread *thread, struct nexus_lua *lua, lua_thread_cb cb_func, void *cb_arg)
{
// zero
memset(thread, 0, sizeof(*thread));
// store
thread->lua = lua;
thread->cb_func = cb_func;
thread->cb_arg = cb_arg;
}
/**
* Thread completed, cleanup and call user callback with given error (NULL for success).
*/
static void lua_thread_done (lua_State *L, struct lua_thread *thread, error_t *err)
{
// remove from registry so thread can be GC'd
lua_pushthread(L);
lua_pushnil(L);
lua_settable(L, LUA_REGISTRYINDEX);
// drop our ref
thread->L = NULL;
// call
thread->cb_func(err, thread->cb_arg);
}
/**
* Execute the lua_resume with zero arguments. If the thread finished, this returns zero, if it yielded, >0. For
* errors, returns -err_t.
*/
static int lua_thread_resume (lua_State *L, struct lua_thread *thread, error_t *err)
{
int ret;
(void) thread;
// invoke it
switch ((ret = lua_resume(L, 0))) {
case LUA_YIELD:
// ok, running, wait
return 1;
case 0:
// done
return 0;
default:
// let caller handle
// XXX: backtrace...
return -nexus_lua_error(L, ret, err);
}
}
struct lua_thread_start_ctx {
struct lua_thread *thread;
const char *chunk;
};
/**
* Setup the given newthread's state
*/
static int lua_thread_setup (lua_State *L)
{
struct lua_thread_start_ctx *ctx;
// read the ctx argument off the stack
if ((ctx = lua_touserdata(L, 1)) == NULL)
return luaL_error(L, "lua_touserdata");
// push the thread state as the key
lua_pushthread(L);
// push the lua_thread as the value
lua_pushlightuserdata(L, ctx->thread);
// store L -> lua_thread mapping in the registry
lua_settable(L, LUA_REGISTRYINDEX);
// clean up the stack
lua_pop(L, 1);
// ok
return 0;
}
/**
* Create a new thread, set it up, and run the initial resume, returning the boolean result of that
* (true for yielded, false for done).
*/
static int _lua_thread_start (lua_State *L)
{
struct lua_thread_start_ctx *ctx;
lua_State *TL;
error_t err;
int ret;
// read the ctx argument off the stack
if ((ctx = lua_touserdata(L, 1)) == NULL)
return luaL_error(L, "lua_touserdata");
else
lua_pop(L, 1);
// check
assert(!ctx->thread->L);
// create new thread
TL = lua_newthread(L);
// create thread and do setup on it
if (nexus_lua_error(TL, lua_cpcall(TL, lua_thread_setup, ctx), &err))
goto error;
// load the chunk as a lua function into the thread's state
if (nexus_lua_error(TL, luaL_loadstring(TL, ctx->chunk), &err))
goto error;
// initial resume on the loaded chunk
if ((ret = lua_thread_resume(TL, ctx->thread, &err)) < 0)
goto error;
// yielded?
if (ret)
// store
ctx->thread->L = TL;
// pop thread to release for GC
lua_pop(L, 1);
return 0;
error:
// pop thread to release for GC
lua_pop(L, 1);
// drop ref
ctx->thread->L = NULL;
// pass on error
return luaL_error(L, "%s", error_msg(&err));
}
int lua_thread_start (struct lua_thread *thread, const char *chunk, error_t *err)
{
struct lua_thread_start_ctx ctx = { thread, chunk };
// sanity-check
assert(!thread->L);
// use protected mode to setup
if (nexus_lua_error(thread->lua->st, lua_cpcall(thread->lua->st, _lua_thread_start, &ctx), err))
return -ERROR_CODE(err);
// return true if yielded, false if done
return (thread->L != NULL);
}
void lua_thread_resume_state (lua_State *L)
{
struct lua_thread *thread;
error_t err;
int ret;
// lookup the thread's context
lua_pushthread(L);
lua_gettable(L, LUA_REGISTRYINDEX);
// get it as a pointer
if ((thread = lua_touserdata(L, -1)) == NULL)
luaL_error(L, "lua_thread_resume_state: lua_touserdata");
// handle the resume
if ((ret = lua_thread_resume(L, thread, &err)) < 0)
// notify user
return lua_thread_done(thread->L, thread, &err);
// finished?
if (ret == 0)
return lua_thread_done(thread->L, thread, NULL);
}