[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: Turning an entire script into a coroutine
- From: Rici Lake <lua@...>
- Date: Sun, 13 Feb 2005 12:44:26 -0500
I think this is easier than you think it is :)
See below
On 13-Feb-05, at 7:57 AM, Michael Abbott wrote:
This doesn't appear possible by what I can tell because the yield()
would need to know what coroutine this script is in relation to (a
script is not a coroutine right?). Please tell me I'm wrong because
I'm not proud of the next solution I started implementing. So I ended
up doing something like this:
function player_idle()
while true do
coroutine.yield(1)
end
end
return coroutine.wrap(player_idle)
Note that player_idle does not need to be a global. It's value is only
used to pass to coroutine.wrap. You could call coroutine.wrap just
given the value of player_idle, and you wouldn't need to do it from the
script file.
By the way, a script file is not a coroutine, but it is a function,
indistinguishable from other functions.
Now, there is nothing stopping you from constructing a character string
and compiling it. So, for example, consider the following Lua code:
function loadCoroutine(filename)
-- slurp the whole script
local script = assert(io.open(filename)):read"*a"
-- create a coroutine factory out of it
local chunk = assert(loadstring(string.format(
"return function() %s end",
script),
filename))
-- and get the coroutine
local worked, func = pcall(chunk)
if worked then return coroutine.wrap(func)
else error(func)
end
end
-- The error handling could be better but that gives you some idea of
where the checks should go.
Of course, you could do the same thing from C. The easiest way to do it
is to just brute force turn the above code into C, but a cleaner
solution is to write your own chunk reader and use lua_load. There is a
slight difference between the Lua coroutine library and the C coroutine
API, though: read the reference manual carefully.
The following code is not guaranteed to do anything, not even compile.
But it might give you some ideas. I didn't do anything other than type
it into the message :( I have to catch a plane in a few hours. If it
actually works, let me know.
typedef enum LoaderState {
LS_PREFIX, LS_FILE, LS_SUFFIX, LS_DONE, LS_ERROR
} LoaderState;
typedef struct LoaderContext {
FILE *file;
const char *prefix;
const char *suffix;
LoaderState state;
char inbuf[BUFSIZ];
} LoaderContext;
/* Simple state machine to feed chunks to Lua */
const char *loader (lua_State *L, void *uctxt, size_t *siz) {
LoaderContext *ctxt = uctxt;
switch (ctxt->state) {
case LS_PREFIX: {
*size = strlen(ctxt->prefix);
ctxt->state = LS_FILE;
return ctxt->prefix;
}
case LS_FILE: {
size_t nr = fread(ctxt->inbuf, 1, BUFSIZ, ctxt->file);
if (nr != 0) {
*size = nr;
return ctxt->inbuf;
} else if (ferror(ctxt->file)) {
ctxt->state = LS_ERROR;
strerror_r(errno, ctxt->inbuf, BUFSIZ);
*size = 0;
return NULL;
}
ctxt->state = LS_SUFFIX;
}
/* FALL THROUGH ON EOF */
case LS_SUFFIX: {
*size = strlen(ctxt->suffix);
ctxt->state = LS_DONE;
return ctxt->suffix;
}
case LS_ERROR:
case LS_DONE: {
*size = 0;
return NULL;
}
default: {
/* Oops. */
assert(0, "Internal error");
}
}
}
LoaderContext *contextNew (lua_State *L, FILE *file,
const char *prefix, const char *suffix) {
LoaderContext *self = lua_pushuserdata(L, sizeof(LoaderContext);
/* TODO: add a __gc metamethod which closes the file */
self->file = file;
self->prefix = prefix;
self->suffix = suffix;
self->state = LC_PREFIX;
return self;
}
static const char pfx = "return function () ";
static const char sfx = " end";
void loadCoroutine (lua_State *L, const char *filename) {
LoaderContext *ctxt;
int status;
FILE *f;
lua_State *thread;
/* Create the thread. After this, everything will happen
in the thread
*/
thread = lua_newthread(L);
/* Now read the file */
f = fopen(filename, "r");
if (f == NULL) { /* TODO: handle error */ }
/* Make the loader */
ctxt = contextNew(thread, f, "return function() ", " end");
/* Load it */
status = lua_load(thread, loader, ctxt, filename);
/*
If there was a read error, then we should report that
rather than the syntax error
*/
/* TODO: Make this an object method */
if (ctxt->state == LC_ERROR) {
lua_pop(thread, 1);
lua_pushstring(thread, ctxt->inbuf);
status = LUA_ERRRUN;
}
/* Ditch the context */
fclose(f);
lua_replace(thread, 1); /* overwrite the udata with the chunk */
/* call the factory */
if (status == 0) status = lua_pcall(thread, 0, 0, 0);
/*
At this point, the only thing on the thread's stack is
either the chunk or an error message. If it's an error,
we'll signal the error in the calling state. If the
top of the thread stack is the chunk, we can just return,
because the thread is already on top of the main stack.
*/
if (status != 0) {
lua_xmove(thread, L, 1);
lua_error(L);
}
}