#include "LuaEngine.h" #include #include #include #include "SDL2/include/SDL.h" #include "SDL2/include/SDL_mixer.h" #include "SDL2/include/SDL_ttf.h" #include "SDL2/include/SDL_image.h" #include "PlatAPI.h" using namespace std; void check(lua_State* L, const vector& typearr) { int sz = typearr.size(); for (int i = 0; i < sz; i++) { if (lua_type(L, i + 1) != typearr[i]) { luaL_error(L, "Bad argument #%d. %s expected, got %s", i + 1, lua_typename(L, typearr[i]), lua_typename(L, lua_type(L, i + 1))); } } } struct LuaTimerCallbackData { void* p; int id; int type; // 0=setTimeout, 1=setInterval }; Uint32 LuaTimerCallbackGate(Uint32 interval, void* param) { SDL_Event e; e.type = SDL_USEREVENT; e.user.code = 1001; e.user.data1 = param; SDL_PushEvent(&e); return ((LuaTimerCallbackData*)(param))->type ? interval : 0; } #define setfn(L, cfunc, name) lua_pushcfunction(L, cfunc); lua_setfield(L, -2, name) struct Window { SDL_Window* wnd; static int close(lua_State* L) { check(L, { LUA_TUSERDATA }); cout << "In Window::close" << endl; Window* p = (Window*)lua_touserdata(L, 1); if (p->wnd) { // release data table lua_pushlightuserdata(L, p); lua_pushnil(L); lua_settable(L, LUA_REGISTRYINDEX); SDL_DestroyWindow(p->wnd); p->wnd = nullptr; } return 0; } static int start(lua_State* L) { check(L, { LUA_TUSERDATA }); Window* p = (Window*)lua_touserdata(L, 1); lua_settop(L, 1); lua_pushlightuserdata(L, p); lua_gettable(L, LUA_REGISTRYINDEX); bool running = true; SDL_Event e; while (running && SDL_WaitEvent(&e)) { switch (e.type) { case SDL_MOUSEBUTTONDOWN: { if (lua_getfield(L, -1, "onclick") != LUA_TNIL) { lua_pushnumber(L, e.button.x); lua_pushnumber(L, e.button.y); lua_call(L, 2, LUA_MULTRET); } else lua_pop(L, 1); break; } case SDL_QUIT: { if (lua_getfield(L, -1, "onquit") != LUA_TNIL) { lua_call(L, 0, LUA_MULTRET); } else lua_pop(L, 1); running = 0; break; } case SDL_KEYDOWN: { if (lua_getfield(L, -1, "onkeydown") != LUA_TNIL) { lua_pushnumber(L, e.key.keysym.sym); lua_call(L, 1, LUA_MULTRET); } else lua_pop(L, 1); break; } case SDL_USEREVENT: { switch (e.user.code) { case 1001: { LuaTimerCallbackData* pd = (LuaTimerCallbackData* )e.user.data1; lua_pushlightuserdata(L, pd->p); lua_gettable(L, LUA_REGISTRYINDEX); lua_pushfstring(L, "tmcb%d", pd->id); lua_gettable(L, -2); delete pd; lua_call(L, 0, LUA_MULTRET); lua_pop(L, 1); break; } } break; } } } return 0; } static int set_event_handler(lua_State* L) { check(L, { LUA_TUSERDATA, LUA_TSTRING, LUA_TFUNCTION }); Window* p = (Window*)lua_touserdata(L, 1); lua_pushlightuserdata(L, p); lua_gettable(L, LUA_REGISTRYINDEX); lua_pushfstring(L, "on%s", lua_tostring(L, 2)); lua_pushvalue(L, 3); lua_settable(L, -3); return 0; } static int setTimeout(lua_State* L) { check(L, { LUA_TUSERDATA, LUA_TFUNCTION, LUA_TNUMBER }); // ms Window* p = (Window*)lua_touserdata(L, 1); LuaTimerCallbackData* pd = new LuaTimerCallbackData(); pd->p = p; pd->type = 0; pd->id = SDL_AddTimer(lua_tointeger(L, 3), LuaTimerCallbackGate, pd); lua_pushlightuserdata(L, p); lua_gettable(L, LUA_REGISTRYINDEX); lua_pushfstring(L, "tmcb%d", pd->id); lua_pushvalue(L, 2); lua_settable(L, -3); return 0; } static int create(lua_State* L) { check(L, { LUA_TSTRING, LUA_TNUMBER, LUA_TNUMBER }); cout << "In Window::create" << endl; Window* p = (Window*) lua_newuserdata(L, sizeof(Window)); // Data table (for storing lua values) lua_pushlightuserdata(L, p); lua_newtable(L); lua_settable(L, LUA_REGISTRYINDEX); // Metatable lua_getfield(L, LUA_REGISTRYINDEX, "__window_mt"); if (lua_type(L, -1) == LUA_TNIL) { lua_pop(L, 1); lua_newtable(L); // GC lua_pushcfunction(L, close); lua_setfield(L, -2, "__gc"); // Fields lua_newtable(L); lua_pushstring(L, "window"); lua_setfield(L, -2, "type"); setfn(L, close, "close"); setfn(L, set_event_handler, "on"); setfn(L, start, "start"); setfn(L, setTimeout, "setTimeout"); // Binding lua_setfield(L, -2, "__index"); lua_setfield(L, LUA_REGISTRYINDEX, "__window_mt"); lua_getfield(L, LUA_REGISTRYINDEX, "__window_mt"); } lua_setmetatable(L, -2); // Internal logic p->wnd = SDL_CreateWindow(lua_tostring(L, 1), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, lua_tointeger(L, 2), lua_tointeger(L, 3), SDL_WINDOW_SHOWN); return 1; } }; struct Texture { SDL_Texture* text; int w; int h; static int close(lua_State* L) { check(L, { LUA_TUSERDATA }); cout << "In Texture::close" << endl; Texture* t = (Texture*)lua_touserdata(L, 1); if (t->text) { SDL_DestroyTexture(t->text); t->text = nullptr; } return 0; } static void create(lua_State* L) // -2, +1 { // 此函数由Renderer在C层直接调用. 调用前需要先放一个renderer userdata和lightuserdata在栈上, 值就是当前的SDL_Texture. // 调用后栈上会留下Texture的userdata. // 因此在Lua层没有直接创建Texture的方法. cout << "In Texture::create" << endl; Texture* p = (Texture*)lua_newuserdata(L, sizeof(Texture)); // Metatable lua_getfield(L, LUA_REGISTRYINDEX, "__texture_mt"); if (lua_type(L, -1) == LUA_TNIL) { lua_pop(L, 1); lua_newtable(L); // GC lua_pushcfunction(L, close); lua_setfield(L, -2, "__gc"); // Fields lua_newtable(L); lua_pushstring(L, "texture"); lua_setfield(L, -2, "type"); setfn(L, close, "close"); // Binding lua_setfield(L, -2, "__index"); lua_setfield(L, LUA_REGISTRYINDEX, "__texture_mt"); lua_getfield(L, LUA_REGISTRYINDEX, "__texture_mt"); } lua_setmetatable(L, -2); p->text = (SDL_Texture * )lua_touserdata(L, -2); SDL_QueryTexture(p->text, NULL, NULL, &p->w, &p->h); lua_pushvalue(L, -3); lua_setuservalue(L, -2); // 绑定这个Rendere userdata // stack balance lua_remove(L, lua_gettop(L) - 1); // 删除lightuserdata lua_remove(L, lua_gettop(L) - 1); // 删除rendere userdata } }; int LuaRectPointToRect(lua_State* L, int index, SDL_Rect& rect) { if (lua_getfield(L, index, "type") != LUA_TSTRING) { lua_pop(L, 1); return -1; } const char* type = lua_tostring(L, -1); if (strcmp(type, "point") == 0) { lua_pop(L, 1); lua_getfield(L, index, "x"); rect.x = lua_tointeger(L, -1); lua_pop(L, 1); lua_getfield(L, index, "y"); rect.y = lua_tointeger(L, -1); lua_pop(L, 1); return 1; } else if (strcmp(type, "rect") == 0) { lua_pop(L, 1); lua_getfield(L, index, "x"); rect.x = lua_tointeger(L, -1); lua_pop(L, 1); lua_getfield(L, index, "y"); rect.y = lua_tointeger(L, -1); lua_pop(L, 1); lua_getfield(L, index, "w"); rect.w = lua_tointeger(L, -1); lua_pop(L, 1); lua_getfield(L, index, "h"); rect.h = lua_tointeger(L, -1); lua_pop(L, 1); return 0; } else { return -1; } } struct Surface { SDL_Surface* surf; static int close(lua_State* L) { check(L, { LUA_TUSERDATA }); cout << "In Surface::close" << endl; Surface* p = (Surface*)lua_touserdata(L, 1); if (p->surf) { SDL_FreeSurface(p->surf); p->surf = nullptr; } return 0; } static void create(lua_State* L) // -1, +1 { cout << "In Surface::create" << endl; Surface* p = (Surface*)lua_newuserdata(L, sizeof(Surface)); // Metatable lua_getfield(L, LUA_REGISTRYINDEX, "__surface_mt"); if (lua_type(L, -1) == LUA_TNIL) { lua_pop(L, 1); lua_newtable(L); // GC lua_pushcfunction(L, close); lua_setfield(L, -2, "__gc"); // Fields lua_newtable(L); lua_pushstring(L, "surface"); lua_setfield(L, -2, "type"); setfn(L, close, "close"); // Binding lua_setfield(L, -2, "__index"); lua_setfield(L, LUA_REGISTRYINDEX, "__surface_mt"); lua_getfield(L, LUA_REGISTRYINDEX, "__surface_mt"); } lua_setmetatable(L, -2); p->surf = (SDL_Surface*)lua_touserdata(L, -2); // stack balance lua_remove(L, lua_gettop(L) - 1); // 删除lightuserdata } }; struct Renderer { SDL_Renderer* rnd; static int close(lua_State* L) { check(L, { LUA_TUSERDATA }); cout << "In Renderer::close" << endl; Renderer* p = (Renderer*)lua_touserdata(L, 1); if (p->rnd) { SDL_DestroyRenderer(p->rnd); p->rnd = nullptr; } return 0; } static int update(lua_State* L) { check(L, { LUA_TUSERDATA }); Renderer* p = (Renderer*)lua_touserdata(L, 1); SDL_RenderPresent(p->rnd); return 0; } static int clear(lua_State* L) { check(L, { LUA_TUSERDATA }); Renderer* p = (Renderer*)lua_touserdata(L, 1); SDL_RenderClear(p->rnd); return 0; } static int loadTexture(lua_State* L) { check(L, { LUA_TUSERDATA, LUA_TSTRING }); Renderer* p = (Renderer*)lua_touserdata(L, 1); const char* filepath = lua_tostring(L, 2); SDL_Texture* text = IMG_LoadTexture(p->rnd, filepath); lua_pushvalue(L, 1); lua_pushlightuserdata(L, text); Texture::create(L); return 1; } static int copy(lua_State* L) { check(L, { LUA_TUSERDATA, LUA_TUSERDATA, LUA_TTABLE, LUA_TTABLE }); Renderer* p = (Renderer*)lua_touserdata(L, 1); return 0; } static int copyTo(lua_State* L) { check(L, { LUA_TUSERDATA, LUA_TUSERDATA, LUA_TTABLE }); Renderer* r = (Renderer*)lua_touserdata(L, 1); Texture* t = (Texture*)lua_touserdata(L, 2); SDL_Rect rect; int ret = LuaRectPointToRect(L, 3, rect); if (ret < 0) { luaL_error(L, "Bad argument #%d. Point or Rect expected, got %s", 3, lua_typename(L, lua_type(L, 3))); } if (ret == 1) { rect.w = t->w; rect.h = t->h; } SDL_RenderCopy(r->rnd, t->text, NULL, &rect); return 0; } static int create(lua_State* L) { check(L, { LUA_TUSERDATA }); cout << "In Renderer::create" << endl; Renderer* p = (Renderer*)lua_newuserdata(L, sizeof(Renderer)); lua_getfield(L, LUA_REGISTRYINDEX, "__renderer_mt"); if (lua_type(L, -1) == LUA_TNIL) { lua_pop(L, 1); lua_newtable(L); // GC lua_pushcfunction(L, close); lua_setfield(L, -2, "__gc"); // Fields lua_newtable(L); lua_pushstring(L, "renderer"); lua_setfield(L, -2, "type"); setfn(L, close, "close"); setfn(L, update, "update"); setfn(L, clear, "clear"); setfn(L, loadTexture, "loadTexture"); setfn(L, copy, "copy"); setfn(L, copyTo, "copyTo"); // Set __index of metatable. lua_setfield(L, -2, "__index"); lua_setfield(L, LUA_REGISTRYINDEX, "__renderer_mt"); lua_getfield(L, LUA_REGISTRYINDEX, "__renderer_mt"); } lua_setmetatable(L, -2); // 绑定当前Renderer的对象到自身, 防止SDL_Window被提前销毁. lua_pushvalue(L, 1); lua_setuservalue(L, -2); // Internal logic p->rnd = SDL_CreateRenderer(static_cast(lua_touserdata(L, 1))->wnd, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE); return 1; } }; int LuaColorToColor(lua_State* L, int index, SDL_Color& c) { if (lua_getfield(L, index, "type") != LUA_TSTRING) { lua_pop(L, 1); return -1; } const char* type = lua_tostring(L, -1); if (strcmp(type, "color") == 0) { lua_pop(L, 1); lua_getfield(L, index, "r"); c.r = lua_tointeger(L, -1); lua_pop(L, 1); lua_getfield(L, index, "g"); c.g = lua_tointeger(L, -1); lua_pop(L, 1); lua_getfield(L, index, "b"); c.b = lua_tointeger(L, -1); lua_pop(L, 1); lua_getfield(L, index, "a"); c.a = lua_tointeger(L, -1); lua_pop(L, 1); return 0; } else { return -1; } } struct Font { TTF_Font* font; static int close(lua_State* L) { check(L, { LUA_TUSERDATA }); cout << "In Font::close" << endl; Font* f = (Font*)lua_touserdata(L, 1); if (f->font) { TTF_CloseFont(f->font); f->font = nullptr; } return 0; } static int renderText(lua_State* L) { Renderer* r = nullptr; Font* f = nullptr; const char* text = nullptr; SDL_Color c; if (lua_gettop(L) >= 4) { check(L, { LUA_TUSERDATA, LUA_TUSERDATA, LUA_TSTRING, LUA_TTABLE }); f = (Font*)lua_touserdata(L, 1); r = (Renderer*)lua_touserdata(L, 2); text = lua_tostring(L, 3); if (LuaColorToColor(L, 4, c) < 0) { luaL_error(L, "Bad argument #%d. Color expected, got %s", 4, lua_typename(L, lua_type(L, 4))); } } else { check(L, { LUA_TUSERDATA, LUA_TSTRING, LUA_TTABLE }); f = (Font*)lua_touserdata(L, 1); text = lua_tostring(L, 3); if (LuaColorToColor(L, 4, c) < 0) { luaL_error(L, "Bad argument #%d. Color expected, got %s", 4, lua_typename(L, lua_type(L, 4))); } } SDL_Surface* surf = TTF_RenderText_Blended(f->font, text, c); if (r) { SDL_Texture* text = SDL_CreateTextureFromSurface(r->rnd, surf); SDL_FreeSurface(surf); lua_pushvalue(L, 2); lua_pushlightuserdata(L, text); Texture::create(L); } else { lua_pushlightuserdata(L, surf); Surface::create(L); } return 1; } static int create(lua_State* L) { check(L, { LUA_TSTRING, LUA_TNUMBER }); cout << "In Font::create" << endl; Font* f = (Font*)lua_newuserdata(L, sizeof(Font)); lua_getfield(L, LUA_REGISTRYINDEX, "__font_mt"); if (lua_type(L, -1) == LUA_TNIL) { lua_pop(L, 1); lua_newtable(L); // GC lua_pushcfunction(L, close); lua_setfield(L, -2, "__gc"); // Fields lua_newtable(L); lua_pushstring(L, "font"); lua_setfield(L, -2, "type"); setfn(L, close, "close"); setfn(L, renderText, "renderText"); // Set __index of metatable. lua_setfield(L, -2, "__index"); lua_setfield(L, LUA_REGISTRYINDEX, "__font_mt"); lua_getfield(L, LUA_REGISTRYINDEX, "__font_mt"); } lua_setmetatable(L, -2); const char* filename = lua_tostring(L, 1); int fontsz = lua_tointeger(L, 2); f->font = TTF_OpenFont(filename, fontsz); return 1; } }; struct Music { Mix_Music* music; static int close(lua_State* L) { check(L, { LUA_TUSERDATA }); cout << "In Music::close" << endl; Music* m = (Music*)lua_touserdata(L, 1); if (m->music) { Mix_FreeMusic(m->music); m->music = nullptr; } return 0; } static int create(lua_State* L) { check(L, { LUA_TSTRING }); cout << "In Music::create" << endl; Music* m = (Music*)lua_newuserdata(L, sizeof(Music)); lua_getfield(L, LUA_REGISTRYINDEX, "__music_mt"); if (lua_type(L, -1) == LUA_TNIL) { lua_pop(L, 1); lua_newtable(L); // GC lua_pushcfunction(L, close); lua_setfield(L, -2, "__gc"); // Fields lua_newtable(L); lua_pushstring(L, "music"); lua_setfield(L, -2, "type"); setfn(L, close, "close"); // Set __index of metatable. lua_setfield(L, -2, "__index"); lua_setfield(L, LUA_REGISTRYINDEX, "__music_mt"); lua_getfield(L, LUA_REGISTRYINDEX, "__music_mt"); } lua_setmetatable(L, -2); const char* filename = lua_tostring(L, 1); m->music = Mix_LoadMUS(filename); cout << "Load music error: " << SDL_GetError() << endl; return 1; } }; struct MusicPlayer { static int close(lua_State* L) { check(L, { LUA_TUSERDATA }); cout << "In MusicPlayer::close" << endl; return 0; } static int play(lua_State* L) { check(L, { LUA_TUSERDATA, LUA_TUSERDATA }); MusicPlayer* p = (MusicPlayer*)lua_touserdata(L, 1); Music* m = (Music*)lua_touserdata(L, 2); cout << "Play music ret: " << Mix_PlayMusic(m->music, 1) << " " << SDL_GetError() << endl; return 0; } static int create(lua_State* L) { cout << "In MusicPlayer::create" << endl; MusicPlayer* m = (MusicPlayer*)lua_newuserdata(L, sizeof(MusicPlayer)); lua_getfield(L, LUA_REGISTRYINDEX, "__musicplayer_mt"); if (lua_type(L, -1) == LUA_TNIL) { lua_pop(L, 1); lua_newtable(L); // GC lua_pushcfunction(L, close); lua_setfield(L, -2, "__gc"); // Fields lua_newtable(L); lua_pushstring(L, "musicplayer"); lua_setfield(L, -2, "type"); setfn(L, close, "close"); setfn(L, play, "play"); // Set __index of metatable. lua_setfield(L, -2, "__index"); lua_setfield(L, LUA_REGISTRYINDEX, "__musicplayer_mt"); lua_getfield(L, LUA_REGISTRYINDEX, "__musicplayer_mt"); } lua_setmetatable(L, -2); return 1; } }; struct LibFS { static int listdir(lua_State* L) { check(L, { LUA_TSTRING }); vector vec = ListDir(lua_tostring(L, 1)); int sz = vec.size(); lua_newtable(L); for (lua_Integer i = 0; i < sz; i++) { const FileInfo& info = vec[i]; lua_newtable(L); lua_pushstring(L, info.name.c_str()); lua_setfield(L, -2, "name"); lua_pushstring(L, info.type ? "dir" : "file"); lua_setfield(L, -2, "type"); lua_pushinteger(L, info.size); lua_setfield(L, -2, "size"); lua_seti(L, -2, i + 1); } return 1; } static int create(lua_State* L) { lua_newtable(L); setfn(L, listdir, "listdir"); return 1; } }; #define reg_in_lua(L, cfunc, name) lua_pushcfunction(L, cfunc); lua_setglobal(L, name) void check_init(lua_State* L, const char* name, int ret, int expected = 0) { if (ret != expected) { luaL_error(L, "Failed to initialize %s. ret: %d. expected: %d. SDLError: %s", name, ret, expected, SDL_GetError()); } } int InitEngine(lua_State* L) { int ret; ret = SDL_Init(SDL_INIT_EVERYTHING); check_init(L, "SDL", ret); ret = IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG); check_init(L, "IMG", ret, IMG_INIT_JPG | IMG_INIT_PNG); ret = TTF_Init(); check_init(L, "TTF", ret); ret = Mix_Init(MIX_INIT_MP3 | MIX_INIT_MOD | MIX_INIT_OGG ); check_init(L, "MIX", ret, MIX_INIT_MP3 | MIX_INIT_MOD | MIX_INIT_OGG); ret = Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 512); check_init(L, "AUDIO", ret); Mix_AllocateChannels(16); reg_in_lua(L, Window::create, "Window"); reg_in_lua(L, Renderer::create, "Renderer"); reg_in_lua(L, Font::create, "Font"); reg_in_lua(L, Music::create, "Music"); reg_in_lua(L, MusicPlayer::create, "MusicPlayer"); LibFS::create(L); lua_setglobal(L, "fs"); return 0; }