Skip to content

Commit

Permalink
feat: Begin work on lege.task module (structured concurrency)
Browse files Browse the repository at this point in the history
Tasks are organised into a tree higherarchy to implement structured
concurrency. Each task has a name, a parent (except for toplevel tasks),
a coroutine to run on, and a list  (table)of children. Blocking tasks is
also implemented.

Tasks are not yet executed by a reactor.
  • Loading branch information
mcb2003 committed Jun 7, 2024
1 parent 1dfd57c commit b8d82b2
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/builtins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ void register_builtins(EngineImpl &e) {
e.load(luaopen_lege_readonly, "lege.readonly");
e.load(luaopen_lege_strict, "lege.strict");
e.load(luaopen_lege_struct, "lege.struct");
e.load(luaopen_lege_task, "lege.task");
e.load(luaopen_lege_weak, "lege.weak");
e.load(luaopen_lege_vec2, "lege.vec2");
e.load(luaopen_lege_vec3, "lege.vec3");
Expand Down
1 change: 1 addition & 0 deletions lib/builtins.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ int luaopen_lege_log(lua_State *L);
int luaopen_lege_readonly(lua_State *L);
int luaopen_lege_strict(lua_State *L);
int luaopen_lege_struct(lua_State *L);
int luaopen_lege_task(lua_State *L);
int luaopen_lege_weak(lua_State *L);
int luaopen_lege_vec2(lua_State *L);
int luaopen_lege_vec3(lua_State *L);
Expand Down
1 change: 1 addition & 0 deletions rt/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ add_library(lege-rt STATIC
lua/stack.cpp
lua/state.cpp
lua/table_view.cpp
modules/task.cpp
modules/weak.cpp
runtime.cpp
util.cpp
Expand Down
254 changes: 254 additions & 0 deletions rt/modules/task.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
#include "lua/helpers.hpp"
#include "modules/weak.hpp"

namespace lua = lege::lua;
namespace weak = lege::weak;

#define ENV_NAME "lege.task.env"
#define MT_NAME "lege.task.mt"
// this is useful for more than just metatables
#define luaL_newregistrytable(L, name) (luaL_newmetatable((L), (name)))

static int l_get_support_tables(lua_State *L) {
lua_pushvalue(L, lua_upvalueindex(1));
return 1;
}

static bool get_current_task(lua_State *L, bool pop_global_env = true) {
lua_pushvalue(L, lua_upvalueindex(1));
lua_pushliteral(L, "by_thread");
lua_rawget(L, -2);
if (pop_global_env) {
lua_replace(L, -2);
}
bool is_main = lua_pushthread(L);
lua_rawget(L, -2);
lua_replace(L, -2);
return is_main;
}

static bool get_current_task_env(lua_State *L) {
bool is_main = get_current_task(L);
lua_getfenv(L, -1);
lua_replace(L, -2);
return is_main;
}

static bool get_current_task_children(lua_State *L) {
bool is_main = get_current_task_env(L);
if (is_main) {
lua_pushvalue(L, lua_upvalueindex(1));
lua_replace(L, -2);
lua_pushliteral(L, "toplevels");
} else {
lua_pushliteral(L, "children");
}
lua_rawget(L, -2);
lua_replace(L, -2);
return is_main;
}

static int l_current(lua_State *L) {
get_current_task(L);
return 1;
}

static int l_tostring(lua_State *L) {
luaL_checkudata(L, 1, MT_NAME);
lua_settop(L, 1);

lua_getfenv(L, 1);
lua_pushliteral(L, "name");
lua_rawget(L, -2);
const char *name = lua_tostring(L, -1);

lua_pushfstring(L, "Task '%s': %p", name, lua_topointer(L, 1));
return 1;
}

static int l_index(lua_State *L) {
luaL_checkudata(L, 1, MT_NAME);
lua_settop(L, 2);

// Fields come from the environment table
lua_getfenv(L, 1);
lua_pushvalue(L, 2);
lua_rawget(L, -2);

if (lua_isnil(L, -1)) {
// Raise a helpful error message instead of just returning nil
const char *field = lua_tostring(L, 2);
luaL_callmeta(L, 1, "__tostring");
const char *task = lua_tostring(L, -1);
return luaL_error(L, "No field '%s' on %s", field, task);
}

return 1;
}

static int l_newindex(lua_State *L) {
luaL_checkudata(L, 1, MT_NAME);

const char *field = lua_tostring(L, 2);
luaL_callmeta(L, 1, "__tostring");
const char *task = lua_tostring(L, -1);
return luaL_error(L, "Cannot set field '%s' on %s", field, task);
}

static void make_task(lua_State *L, int nameindex, int funcindex) {
nameindex = lua::absindex(L, nameindex);
funcindex = lua::absindex(L, funcindex);

// Make the new task
lua_newuserdata(L, 0);

// Make the coroutine the task runs on
lua_State *co = lua_newthread(L);
// Push the task's function on the coroutine's stack
lua_pushvalue(L, funcindex);
lua_xmove(L, co, 1);

if (luaL_newmetatable(L, MT_NAME)) {
// Set up metatable
lua_pushliteral(L, "__tostring");
lua_pushcfunction(L, l_tostring);
lua_rawset(L, -3);

lua_pushliteral(L, "__index");
lua_pushcfunction(L, l_index);
lua_rawset(L, -3);

lua_pushliteral(L, "__newindex");
lua_pushcfunction(L, l_newindex);
lua_rawset(L, -3);
}
lua_setmetatable(L, -3);

// Create an associated environment table, used to store Lua values
lua_createtable(L, 0, 2);

// env.name = name
lua_pushliteral(L, "name");
lua_pushvalue(L, nameindex);
lua_rawset(L, -3);

// env.children = {}
lua_pushliteral(L, "children");
lua_newtable(L);
lua_rawset(L, -3);

// Env.co = co
lua_pushliteral(L, "co");
lua_pushvalue(L, -3);
lua_rawset(L, -3);

// env.parent = parent
lua_pushliteral(L, "parent");
bool parent_is_main = get_current_task(L);
if (parent_is_main) {
// We still want a non-nil value so that __index doesn't error
lua_pushboolean(L, false);
lua_replace(L, -2);
}
lua_rawset(L, -3);

lua_setfenv(L, -3);

// global_env.by_thread[co] = task
lua_pushvalue(L, lua_upvalueindex(1));
lua_pushliteral(L, "by_thread");
lua_rawget(L, -2);
lua_pushvalue(L, -3); // Coroutine
lua_pushvalue(L, -5); // Task
lua_rawset(L, -3);

lua_pop(L, 1); // Pop by_thread

// Schedule the task
lua_pushliteral(L, "pending");
lua_rawget(L, -2);
lua_pushvalue(L, -4); // Task
lua_pushboolean(L, true);
lua_rawset(L, -3);

lua_pop(L, 3); // Pop pending, global_env, and co
}

static int l_spawn(lua_State *L) {
luaL_checkstring(L, 1);
luaL_checktype(L, 2, LUA_TFUNCTION);
lua_settop(L, 2);

make_task(L, 1, 2);

get_current_task_children(L);
lua_pushvalue(L, 3);
lua_pushboolean(L, true);
lua_rawset(L, -3);

lua_settop(L, 3);
return 1;
}

static int l_block(lua_State *L) {
if (!lua_isyieldable(L)) {
return luaL_error(L, "Cannot block the main task");
}

lua_settop(L, 0);

// false == don't pop the global environment
get_current_task(L, false);

// global_env.pending[current_task] = nil
lua_pushliteral(L, "pending");
lua_rawget(L, -3);
lua_pushvalue(L, -2); // Current task
lua_pushnil(L);
lua_rawset(L, -3);

lua_pushliteral(L, "blocked");
lua_rawget(L, -4); // global_env
lua_pushvalue(L, -3); // Current task
lua_pushboolean(L, true);
lua_rawset(L, -3);

return lua_yield(L, 0);
}

static const luaL_Reg TASK_FUNCS[]{{"get_support_tables", l_get_support_tables},
{"current", l_current},
{"spawn", l_spawn},
{"block", l_block},
{nullptr, nullptr}};

static void make_support_tables(lua_State *L) {
if (luaL_newregistrytable(L, ENV_NAME)) {
lua_pushliteral(L, "by_thread");
weak::new_kv(L);
lua_rawset(L, -3);

// Tasks that can run
lua_pushliteral(L, "pending");
lua_newtable(L);
lua_rawset(L, -3);

// Tasks waiting for something (probably I/O)
lua_pushliteral(L, "blocked");
lua_newtable(L);
lua_rawset(L, -3);

// Tasks spawned from the main thread
lua_pushliteral(L, "toplevels");
lua_newtable(L);
lua_rawset(L, -3);
}
}

extern "C" int luaopen_lege_task(lua_State *L) {
weak::require(L);
luaL_newlibtable(L, TASK_FUNCS);
make_support_tables(L);
luaL_setfuncs(L, TASK_FUNCS, 1);
return 1;
}

0 comments on commit b8d82b2

Please sign in to comment.