diff --git a/Channel.cpp b/Channel.cpp index 0942839..a729860 100644 --- a/Channel.cpp +++ b/Channel.cpp @@ -5,31 +5,66 @@ #include #include #include +#include using namespace std; /** module Channel create(name: string): Channel, isCreated: boolean open(name: string): Channel or nil + Opening non-exist channels return nil. collect() class Channel - put(value: nil, number, boolean, string) - get(): nil, number, boolean, string + ChannelDataTypes: nil, integer, number, boolean, string, SurfaceObject + + put(value: ChannelDataTypes) + get(): ChannelDataTypes + + try_put(value: ChannelDataTypes, timeout_ms: int): isSuccess: boolean + try_get(timeout_ms: int): isSuccess: boolean, value: ChannelDataTypes - try_put(value: nil, number, boolean, string, timeout_ms: int): isSuccess: boolean - try_get(timeout_ms: int): isSuccess: boolean, value: nil, number, boolean, string wait([timeout_ms: int]): hasValue: boolean Block until has value if no timeout specified. Return false on reaching timeout. Negative timeout is regarded as 0. Zero timeout means instant check-and-return. */ +template +static ShareableResource* lua_tofullres(lua_State* L, int index) +{ + return (ShareableResource*)lua_touserdata(L, index); +} + +enum class ChannelDataType +{ + SUBTYPE_NONE = 0, + SUBTYPE_NIL, + SUBTYPE_INTEGER, + SUBTYPE_NUMBER, + SUBTYPE_STRING, + SUBTYPE_BOOLEAN, + SUBTYPE_ENGINE_SURFACE +}; + +struct ChannelPackage +{ +public: + ChannelDataType subtype; // LuaEngine specified types. + string subname; // LUA_TUSERDATA + variant> value; + + ChannelPackage() : subtype(ChannelDataType::SUBTYPE_NONE) + { + + } +}; + class Channel { public: int capacity; - queue> bus; + queue bus; mutex m; condition_variable cond; @@ -52,57 +87,91 @@ int channel_dtor(lua_State* L) return 0; } -static bool push_to_lua(lua_State* L, int type, const string& value) +static bool push_to_lua(lua_State* L, const ChannelPackage& pack) { - switch (type) + switch (pack.subtype) { - case LUA_TNIL: + case ChannelDataType::SUBTYPE_NIL: lua_pushnil(L); return true; - case LUA_TNUMBER: - lua_stringtonumber(L, value.c_str()); + case ChannelDataType::SUBTYPE_INTEGER: + lua_pushinteger(L, get(pack.value)); return true; - case LUA_TBOOLEAN: - lua_pushboolean(L, value.empty()); + case ChannelDataType::SUBTYPE_NUMBER: + lua_pushnumber(L, get(pack.value)); return true; - case LUA_TSTRING: - lua_pushlstring(L, value.data(), value.size()); + case ChannelDataType::SUBTYPE_BOOLEAN: + lua_pushboolean(L, get(pack.value)); + return true; + case ChannelDataType::SUBTYPE_STRING: + lua_pushlstring(L, get(pack.value).data(), get(pack.value).size()); + return true; + case ChannelDataType::SUBTYPE_ENGINE_SURFACE: + put_surface(L, get>(pack.value)); return true; default: return false; } } -static bool get_from_lua(lua_State* L, int index, int& type, string& value) +static bool get_from_lua(lua_State* L, int index, ChannelPackage& pack) { - type = lua_type(L, index); + int type = lua_type(L, index); switch (type) { case LUA_TNIL: - value = string(); + pack.subtype = ChannelDataType::SUBTYPE_NIL; return true; case LUA_TNUMBER: - value = lua_tostring(L, index); - return true; - case LUA_TBOOLEAN: { - if (lua_toboolean(L, index)) + int isnum; + lua_Integer val = lua_tointegerx(L, index, &isnum); + if (isnum) { - value = "true"; + pack.subtype = ChannelDataType::SUBTYPE_INTEGER; + pack.value = val; } else { - value = string(); + pack.subtype = ChannelDataType::SUBTYPE_NUMBER; + pack.value = lua_tonumber(L, index); } return true; } + case LUA_TBOOLEAN: + pack.subtype = ChannelDataType::SUBTYPE_BOOLEAN; + pack.value = (bool)lua_toboolean(L, index); + return true; case LUA_TSTRING: { size_t sz; const char* p = lua_tolstring(L, index, &sz); - value = string(p, sz); + pack.subtype = ChannelDataType::SUBTYPE_STRING; + pack.value = string(p, sz); return true; } + case LUA_TUSERDATA: + { + bool ok = false; + if (lua_getmetatable(L, index)) + { + int mtype = lua_getfield(L, -1, "__name"); + if (mtype == LUA_TSTRING) + { + const char* tname = lua_tostring(L, -1); + if (strcmp(tname, "LuaEngineSurface") == 0) + { + ok = true; + auto pres = lua_tofullres(L, index); + pres->enable_share(); + pack.subtype = ChannelDataType::SUBTYPE_ENGINE_SURFACE; + pack.value = pres->sp; + } + } + lua_pop(L, 2); // getfield, metatable. + } + return ok; + } default: return false; } @@ -113,23 +182,24 @@ int channel_get(lua_State* L) { auto c = lua_checkblock(L, 1, "LuaChannel"); - int type; - string value; + ChannelPackage pack; { unique_lock ulk(c->sp->m); c->sp->cond.wait(ulk, [&]() { return !c->sp->bus.empty(); }); // Un-serialize from string to Lua object - tie(type, value) = c->sp->bus.front(); + pack = c->sp->bus.front(); c->sp->bus.pop(); c->sp->cond.notify_all(); } - if (!push_to_lua(L, type, value)) + if (!push_to_lua(L, pack)) { - return luaL_error(L, "channel_get: unsupported type %s", lua_typename(L, type)); + SDL_Log("channel_get: invalid package get from channel. Returning none instead.\n"); + return 0; } + return 1; } @@ -139,8 +209,7 @@ int channel_try_get(lua_State* L) int ms = luaL_checkinteger(L, 2); bool success; - int type; - string value; + ChannelPackage pack; { unique_lock ulk(c->sp->m); @@ -155,7 +224,7 @@ int channel_try_get(lua_State* L) if (success) { - tie(type, value) = c->sp->bus.front(); + pack = c->sp->bus.front(); c->sp->bus.pop(); c->sp->cond.notify_all(); } @@ -164,9 +233,10 @@ int channel_try_get(lua_State* L) if (success) { lua_pushboolean(L, true); - if (!push_to_lua(L, type, value)) + if (!push_to_lua(L, pack)) { - return luaL_error(L, "channel_try_get: unsupported type %s", lua_typename(L, type)); + SDL_Log("channel_try_get: invalid package get from channel. Returning none instead.\n"); + return 1; } return 2; } @@ -182,17 +252,16 @@ int channel_put(lua_State* L) { auto c = lua_checkblock(L, 1, "LuaChannel"); - int type; - string value; - if (!get_from_lua(L, 2, type, value)) + ChannelPackage pack; + if (!get_from_lua(L, 2, pack)) { - return luaL_error(L, "channel_put: unsupported type %s", lua_typename(L, type)); + return luaL_error(L, "channel_put: unsupported type %s", lua_typename(L, lua_type(L, 2))); } { unique_lock ulk(c->sp->m); c->sp->cond.wait(ulk, [&]() { return (c->sp->capacity > 0 && c->sp->bus.size() < c->sp->capacity) || (c->sp->capacity == 0 && c->sp->bus.empty()); }); - c->sp->bus.emplace(type, value); + c->sp->bus.push(pack); c->sp->cond.notify_all(); } @@ -205,11 +274,10 @@ int channel_try_put(lua_State* L) int ms = luaL_checkinteger(L, 2); bool success; - int type; - string value; - if (!get_from_lua(L, 3, type, value)) + ChannelPackage pack; + if (!get_from_lua(L, 3, pack)) { - return luaL_error(L, "channel_try_put: unsupported type %s", lua_typename(L, type)); + return luaL_error(L, "channel_try_put: unsupported type %s", lua_typename(L, lua_type(L, 3))); } { @@ -225,7 +293,7 @@ int channel_try_put(lua_State* L) if (success) { - c->sp->bus.emplace(type, value); + c->sp->bus.push(pack); c->sp->cond.notify_all(); } } diff --git a/LuaEngine.cpp b/LuaEngine.cpp index a7fd300..6398143 100644 --- a/LuaEngine.cpp +++ b/LuaEngine.cpp @@ -16,6 +16,7 @@ void InitLuaEngine(lua_State* L) InitSocketSelector(L); InitThread(L); InitChannel(L); + InitSurface(L); } lua_State* CreateLuaEngine() diff --git a/Surface.cpp b/Surface.cpp index 66a9186..2f2aa25 100644 --- a/Surface.cpp +++ b/Surface.cpp @@ -1,22 +1,135 @@ #include "include.h" +using namespace std; + +/** +module Surface + load(filename: string): Surface + loadmem(data: string): Surface + +class Surface + enableColorKey(r: int, g: int, b: int) + disableColorKey(r: int, g: int, b: int) + enableRLE() + disableRLE() +*/ + +int surface_enablecolorkey(lua_State* L) +{ + auto surf = lua_checkres(L, 1, "LuaEngineSurface"); + int r = luaL_checkinteger(L, 2); + int g = luaL_checkinteger(L, 2); + int b = luaL_checkinteger(L, 2); + if (SDL_SetColorKey(surf, 1, SDL_MapRGB(surf->format, r, g, b)) != 0) + { + return SDLError(L, SDL_SetColorKey); + } + return 0; +} + +int surface_disablecolorkey(lua_State* L) +{ + auto surf = lua_checkres(L, 1, "LuaEngineSurface"); + int r = luaL_checkinteger(L, 2); + int g = luaL_checkinteger(L, 2); + int b = luaL_checkinteger(L, 2); + if (SDL_SetColorKey(surf, 0, SDL_MapRGB(surf->format, r, g, b)) != 0) + { + return SDLError(L, SDL_SetColorKey); + } + return 0; +} + +int surface_enableRLE(lua_State* L) +{ + auto surf = lua_checkres(L, 1, "LuaEngineSurface"); + if (SDL_SetSurfaceRLE(surf, 1) != 0) + { + return SDLError(L, SDL_SetSurfaceRLE); + } + return 0; +} + +int surface_disableRLE(lua_State* L) +{ + auto surf = lua_checkres(L, 1, "LuaEngineSurface"); + if (SDL_SetSurfaceRLE(surf, 0) != 0) + { + return SDLError(L, SDL_SetSurfaceRLE); + } + return 0; +} int surface_close(lua_State* L) { - auto surf = lua_checkpointer(L, 1, "LuaEngineSurface"); - SDL_FreeSurface(surf); + auto surf = lua_checkfullres(L, 1, "LuaEngineSurface"); + surf->~ShareableResource(); return 0; } // shared -void put_surface(lua_State* L, SDL_Surface* surf) +void put_surface_meta(lua_State* L) { - lua_newpointer(L, surf); if (luaL_newmetatable(L, "LuaEngineSurface")) { lua_setfield_function(L, "__gc", surface_close); lua_newtable(L); - + lua_setfield_function(L, "enableColorKey", surface_enablecolorkey); + lua_setfield_function(L, "disableColorKey", surface_disablecolorkey); + lua_setfield_function(L, "enableRLE", surface_enableRLE); + lua_setfield_function(L, "disableRLE", surface_disableRLE); lua_setfield(L, -2, "__index"); } lua_setmetatable(L, -2); } + +// global +void put_surface(lua_State* L, SDL_Surface* surf) +{ + lua_newres(L, surf, SDL_FreeSurface); + put_surface_meta(L); +} + +// global +void put_surface(lua_State* L, const shared_ptr& surf) +{ + lua_newres(L, surf); + put_surface_meta(L); +} + +int surface_load(lua_State* L) +{ + const char* filename = luaL_checkstring(L, 1); + SDL_Surface* surf = IMG_Load(filename); + if (!surf) + { + return IMGError(L, IMG_Load); + } + put_surface(L, surf); + return 1; +} + +int surface_loadmem(lua_State* L) +{ + size_t datasz; + const char* data = luaL_checklstring(L, 1, &datasz); + SDL_RWops* src = SDL_RWFromConstMem(data, datasz); + SDL_Surface* surf = IMG_Load_RW(src, 0); + SDL_RWclose(src); + if (!surf) + { + return IMGError(L, IMG_Load_RW); + } + put_surface(L, surf); + return 1; +} + +void InitSurface(lua_State* L) +{ + lua_getglobal(L, "package"); + lua_getfield(L, -1, "loaded"); + lua_newtable(L); + lua_setfield_function(L, "load", surface_load); + lua_setfield_function(L, "loadmem", surface_loadmem); + lua_setfield(L, -2, "Surface"); + lua_pop(L, 2); +} diff --git a/Thread.cpp b/Thread.cpp index b496a3d..bfbae87 100644 --- a/Thread.cpp +++ b/Thread.cpp @@ -4,13 +4,39 @@ #include using namespace std; +/** +class Thread + constructor(code: string, name: string, args: table/array) + code can be binary or text. + Only the following types of value can be included in args: + number, boolean, string + wait(): boolean + Block until thread finished or errored. + If return after blocks, return true. Otherwise, return false. + get(): boolean, ... + Block until thread finished or errored. + If thread finished successfully, return true and values returned from thread. + Otherwise, return false and error message. + status(): string + pending, running, finished, errored + +NOTICE + You should always call wait() method on Thread objects. + By default, Thread objects are not required to be joined/waited. + If a running Thread object is garbage-collected, in order not to block gc, a distached thread is spawned to wait it and clean up lua state. + If the main thread exited, other running threads will be killed, causing undefined behavior. + +REMARK + Channels are encouraged to be used as a method of exchanging values between Threads. It supports more data types and is bidirectional. +*/ + class LuaThread { private: void _WorkerEntry() { status = 1; - if (lua_pcall(L, nArgs, LUA_MULTRET, 0)) + if (lua_pcall(L, nArgs, LUA_MULTRET, 1)) { status = 3; } @@ -21,23 +47,40 @@ private: } public: lua_State* L; - int nArgs; + atomic nArgs; // 0 Not started 1 Running 2 Finished 3 Errored atomic status; - thread td; + shared_ptr sptd; - LuaThread(lua_State* subLVM, int subArgs) : L(subLVM), nArgs(subArgs), status(0), td(&LuaThread::_WorkerEntry, this) - { - - } + LuaThread(lua_State* subLVM, int subArgs) : L(subLVM), nArgs(subArgs), status(0), sptd(make_shared(&LuaThread::_WorkerEntry, this)) {} ~LuaThread() { - if (td.joinable()) + if (sptd->joinable()) { - td.join(); + if (status < 2) + { + SDL_Log("LuaThread %d being collected while running, spawning a new backgroud thread and wait for it.\n", sptd->get_id()); + thread ntd([](lua_State* L, shared_ptr spoldtd) + { + thread::id oldtid = spoldtd->get_id(); + spoldtd->join(); + SDL_Log("LuaThread %d finished in background, cleaning up.\n", oldtid); + lua_close(L); + }, L, sptd); + L = nullptr; + ntd.detach(); + } + else + { + sptd->join(); + lua_close(L); + } + } + else + { + lua_close(L); } - lua_close(L); } }; @@ -51,9 +94,9 @@ int thread_dtor(lua_State* L) int thread_wait(lua_State* L) { auto t = lua_checkblock(L, 1, "LuaThread"); - if (t->td.joinable()) + if (t->sptd->joinable()) { - t->td.join(); + t->sptd->join(); lua_pushboolean(L, 1); } else @@ -66,18 +109,18 @@ int thread_wait(lua_State* L) int thread_get(lua_State* L) { auto t = lua_checkblock(L, 1, "LuaThread"); - if (t->td.joinable()) + if (t->sptd->joinable()) { - t->td.join(); + t->sptd->join(); } if (t->status == 2) { - lua_pushboolean(L, 1); + lua_pushboolean(L, true); } else { - lua_pushboolean(L, 0); + lua_pushboolean(L, false); } int stackTop = lua_gettop(t->L); @@ -95,8 +138,12 @@ int thread_get(lua_State* L) lua_pushboolean(L, lua_toboolean(t->L, i)); break; case LUA_TSTRING: - lua_pushstring(L, lua_tostring(t->L, i)); + { + size_t datalen; + const char* data = lua_tolstring(t->L, -1, &datalen); + lua_pushlstring(L, data, datalen); break; + } default: return luaL_error(L, "thread_get: return value #%d has unsupported type: %s", i, lua_typename(t->L, lua_type(t->L, i))); } @@ -108,7 +155,30 @@ int thread_get(lua_State* L) int thread_status(lua_State* L) { auto t = lua_checkblock(L, 1, "LuaThread"); - lua_pushinteger(L, t->status); + switch (t->status) + { + case 0: + lua_pushstring(L, "pending"); + break; + case 1: + lua_pushstring(L, "running"); + break; + case 2: + lua_pushstring(L, "finished"); + break; + case 3: + lua_pushstring(L, "errored"); + break; + default: + lua_pushstring(L, "unknown"); + } + return 1; +} + +static int thread_traceback(lua_State* L) +{ + luaL_traceback(L, L, NULL, 1); + SDL_Log("ThreadError: %s\n", lua_tostring(L, -1)); return 1; } @@ -116,11 +186,14 @@ int thread_new(lua_State* L) { size_t codelen; const char* code = luaL_checklstring(L, 1, &codelen); - + const char* cname = luaL_checkstring(L, 2); + luaL_checktype(L, 3, LUA_TTABLE); + lua_State* subL = CreateLuaEngine(); + lua_pushcfunction(L, thread_traceback); // compile - if (luaL_loadbuffer(subL, code, codelen, "ThreadMain")) + if (luaL_loadbuffer(subL, code, codelen, cname)) { // Compile error, cannot load. Return error message to caller. lua_pushnil(L); @@ -130,36 +203,40 @@ int thread_new(lua_State* L) } // push args - int stackTop = lua_gettop(L); - for (int idx = 2; idx <= stackTop; idx++) + bool reachEnd = false; + int idxArgs = 1; + for (; !reachEnd; idxArgs++) { - switch (lua_type(L, idx)) + int type = lua_geti(L, 3, idxArgs); + switch (type) { case LUA_TNIL: - lua_pushnil(subL); + SDL_Log("Total %d args for new thread\n", idxArgs - 1); + reachEnd = true; break; case LUA_TNUMBER: - lua_pushnumber(subL, lua_tonumber(L, idx)); + lua_pushnumber(subL, lua_tonumber(L, -1)); break; case LUA_TBOOLEAN: - lua_pushboolean(subL, lua_toboolean(L, idx)); + lua_pushboolean(subL, lua_toboolean(L, -1)); break; case LUA_TSTRING: { size_t datalen; - const char* data = lua_tolstring(L, idx, &datalen); + const char* data = lua_tolstring(L, -1, &datalen); lua_pushlstring(subL, data, datalen); break; } default: lua_pushnil(L); - lua_pushfstring(L, "thread_create: parameter #%d has unsupported type: %s", idx, lua_typename(L, lua_type(L, idx))); + lua_pushfstring(L, "thread_create: parameter #%d has unsupported type: %s", idxArgs, lua_typename(L, lua_type(L, -2))); lua_close(subL); return 2; } + lua_pop(L, 1); } - auto c = new (lua_newblock(L)) LuaThread(subL, stackTop - 1); + auto c = new (lua_newblock(L)) LuaThread(subL, idxArgs - 2); // Here idxArgs starts from 1, accumulate to 2 at least. if (luaL_newmetatable(L, "LuaThread")) { lua_setfield_function(L, "__gc", thread_dtor); diff --git a/include.h b/include.h index 1ccd75e..7af4f2f 100644 --- a/include.h +++ b/include.h @@ -1,5 +1,6 @@ #pragma once #include "LuaEngine.h" +#include #define SDLError(L, prompt) luaL_error(L, #prompt ": %s", SDL_GetError()) #define TTFError(L, prompt) luaL_error(L, #prompt ": %s", TTF_GetError()) @@ -21,6 +22,76 @@ PointerType* lua_checkpointer(lua_State* L, int idx, const char* name) return *(PointerType**)luaL_checkudata(L, idx, name); } +template +class ShareableResource +{ +public: + std::atomic isShared; + ResourceType* ptr; + void (*fnDeleter)(ResourceType*); + std::shared_ptr sp; + + ShareableResource(ResourceType* in, void (*delfn)(ResourceType*)) : isShared(false), ptr(in), fnDeleter(delfn) + { + + } + + ShareableResource(const std::shared_ptr& in) : isShared(true), ptr(nullptr), fnDeleter(nullptr), sp(in) + { + + } + + ~ShareableResource() + { + if (ptr) + { + fnDeleter(ptr); + ptr = nullptr; + } + } + + void enable_share() + { + bool b = false; + if (isShared.compare_exchange_strong(b, true)) + { + sp.reset(ptr, fnDeleter); + ptr = nullptr; + } + } + + ResourceType* get() + { + if (!isShared) return ptr; + else return sp.get(); + } +}; + +template +void lua_newres(lua_State* L, const std::shared_ptr& sp) +{ + new (lua_newuserdata(L, sizeof(ShareableResource))) ShareableResource(sp); +} + +template +void lua_newres(lua_State* L, ResourceType* p, void(*delfn)(ResourceType*)) +{ + new (lua_newuserdata(L, sizeof(ShareableResource))) ShareableResource(p, delfn); +} + +template +ResourceType* lua_checkres(lua_State* L, int idx, const char* name) +{ + auto pres = (ShareableResource*)luaL_checkudata(L, idx, name); + return pres->get(); +} + +template +ShareableResource* lua_checkfullres(lua_State* L, int idx, const char* name) +{ + return (ShareableResource*)luaL_checkudata(L, idx, name); +} + template T* lua_newblock(lua_State* L) { @@ -40,13 +111,17 @@ T* lua_testblock(lua_State* L, int idx, const char* name) } // Shared Functions -void put_surface(lua_State* L, SDL_Surface* surf); // Surface +// Surface +void put_surface(lua_State* L, SDL_Surface* surf); +void put_surface(lua_State* L, const std::shared_ptr& surf); + void put_texture(lua_State* L, SDL_Texture* text); // Texture const char* VirtualKeyToString(SDL_Keycode vkey); // Event // Init Functions void InitWindow(lua_State* L); void InitRenderer(lua_State* L); +void InitSurface(lua_State* L); void InitFont(lua_State* L); void InitMusic(lua_State* L); void InitEvent(lua_State* L);