diff --git a/src/celestia/celestiacore.cpp b/src/celestia/celestiacore.cpp index 49658aa6..eb69104f 100644 --- a/src/celestia/celestiacore.cpp +++ b/src/celestia/celestiacore.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -179,6 +180,7 @@ CelestiaCore::CelestiaCore() : renderer(new Renderer()), timer(new Timer()), execEnv(new CoreExecutionEnvironment(*this)), + m_luaPlugin(make_unique(this)), m_scriptMaps(make_shared()) { @@ -200,13 +202,6 @@ CelestiaCore::~CelestiaCore() if (movieCapture != nullptr) recordEnd(); -#ifdef CELX - // Clean up all scripts - delete celxScript; - delete luaHook; - delete luaSandbox; -#endif - delete execEnv; delete timer; delete renderer; @@ -326,12 +321,12 @@ void CelestiaCore::cancelScript() runningScript = nullptr; } #ifdef CELX - if (celxScript != nullptr) + if (m_script != nullptr) { - celxScript->cleanup(); if (textEnterMode & KbPassToScript) setTextEnterMode(textEnterMode & ~KbPassToScript); scriptState = ScriptCompleted; + m_script = nullptr; } #endif } @@ -381,43 +376,11 @@ void CelestiaCore::runScript(const fs::path& filename) } } #ifdef CELX - else if (type == Content_CelestiaScript) + else if (m_luaPlugin->isOurFile(localeFilename)) { - ifstream scriptfile(localeFilename.string()); - if (!scriptfile.good()) - { - string errMsg; - errMsg = fmt::sprintf(_("Error opening script '%s'"), localeFilename); - fatalError(errMsg); - } - - if (celxScript == nullptr) - { - celxScript = new LuaState(); - celxScript->init(this); - } - - int status = celxScript->loadScript(scriptfile, localeFilename.string()); // FIXME - if (status != 0) - { - string errMsg = celxScript->getErrorMessage(); - if (errMsg.empty()) - errMsg = _("Unknown error opening script"); - fatalError(errMsg); - } - else - { - // Coroutine execution; control may be transferred between the - // script and Celestia's event loop - if (!celxScript->createThread()) - { - fatalError(_("Script coroutine initialization failed")); - } - else - { - scriptState = sim->getPauseState()?ScriptPaused:ScriptRunning; - } - } + m_script = m_luaPlugin->loadScript(localeFilename); + if (m_script != nullptr) + scriptState = sim->getPauseState() ? ScriptPaused : ScriptRunning; } #endif else @@ -427,7 +390,7 @@ void CelestiaCore::runScript(const fs::path& filename) } -bool checkMask(int modifiers, int mask) +static bool checkMask(int modifiers, int mask) { return (modifiers & mask) == mask; } @@ -439,15 +402,14 @@ void CelestiaCore::mouseButtonDown(float x, float y, int button) mouseMotion = 0.0f; #ifdef CELX - if (celxScript != nullptr) + if (m_script != nullptr) { - if (celxScript->handleMouseButtonEvent(x, y, button, true)) + if (m_script->handleMouseButtonEvent(x, y, button, true)) return; } - - if (luaHook && luaHook->callLuaHook(this, "mousebuttondown", x, y, button)) - return; #endif + if (m_scriptHook != nullptr && m_scriptHook->call("mousebuttondown", x, y, button)) + return; if (views.size() > 1) { @@ -515,15 +477,14 @@ void CelestiaCore::mouseButtonUp(float x, float y, int button) } #ifdef CELX - if (celxScript != nullptr) + if (m_script != nullptr) { - if (celxScript->handleMouseButtonEvent(x, y, button, false)) + if (m_script->handleMouseButtonEvent(x, y, button, false)) return; } - - if (luaHook && luaHook->callLuaHook(this,"mousebuttonup", x, y, button)) - return; #endif + if (m_scriptHook != nullptr && m_scriptHook->call("mousebuttonup", x, y, button)) + return; // If the mouse hasn't moved much since it was pressed, treat this // as a selection or context menu event. Otherwise, assume that the @@ -611,10 +572,8 @@ void CelestiaCore::mouseWheel(float motion, int modifiers) /// x and y are the pixel coordinates relative to the widget. void CelestiaCore::mouseMove(float x, float y) { -#ifdef CELX - if (luaHook && luaHook->callLuaHook(this, "mousemove", x, y)) + if (m_scriptHook != nullptr && m_scriptHook->call("mousemove", x, y)) return; -#endif if (views.size() > 1 && cursorHandler != nullptr) { @@ -676,13 +635,9 @@ void CelestiaCore::mouseMove(float dx, float dy, int modifiers) return; } -#ifdef CELX - if (luaHook && - luaHook->callLuaHook(this,"mousebuttonmove", dx, dy, modifiers)) - { - return; - } -#endif + if (m_scriptHook != nullptr && m_scriptHook->call("mousebuttonmove", dx, dy, modifiers)) + return; + if ((modifiers & (LeftButton | RightButton)) != 0) { @@ -877,15 +832,9 @@ void CelestiaCore::keyDown(int key, int modifiers) { setViewChanged(); -#ifdef CELX - // TODO: should pass modifiers as a Lua table - if (luaHook && luaHook->callLuaHook(this, - "keydown", - (float) key, (float) modifiers)) - { + if (m_scriptHook != nullptr && m_scriptHook->call("keydown", float(key), float(modifiers))) return; - } -#endif + switch (key) { case Key_F1: @@ -1020,9 +969,9 @@ void CelestiaCore::charEntered(const char *c_p, int modifiers) #ifdef CELX - if (celxScript != nullptr && (textEnterMode & KbPassToScript)) + if (m_script != nullptr && (textEnterMode & KbPassToScript)) { - if (c != '\033' && celxScript->charEntered(c_p)) + if (c != '\033' && m_script->charEntered(c_p)) { return; } @@ -1125,25 +1074,18 @@ void CelestiaCore::charEntered(const char *c_p, int modifiers) } #ifdef CELX - if (celxScript != nullptr) + if (m_script != nullptr) { if (c != '\033') { string keyName = getKeyName(c_p, modifiers); - if (celxScript->handleKeyEvent(keyName.c_str())) + if (m_script->handleKeyEvent(keyName.c_str())) return; } } - - if (luaHook) - { - string keyName = getKeyName(c_p, modifiers); - if (luaHook->callLuaHook(this, "charentered", keyName.c_str())) - { - return; - } - } #endif + if (m_scriptHook != nullptr && m_scriptHook->call("charentered", getKeyName(c_p, modifiers).c_str())) + return; char C = toupper(c); switch (C) @@ -1375,7 +1317,7 @@ void CelestiaCore::charEntered(const char *c_p, int modifiers) // potentially confusing side effect of rendering nonfunctional // goto, center, and other movement commands. #ifdef CELX - if (runningScript != nullptr || celxScript != nullptr) + if (runningScript != nullptr || m_script != nullptr) #else if (runningScript != nullptr) #endif @@ -2198,20 +2140,19 @@ void CelestiaCore::tick() } #ifdef CELX - if (celxScript != nullptr) + if (m_script != nullptr) { - celxScript->handleTickEvent(dt); + m_script->handleTickEvent(dt); if (scriptState == ScriptRunning) { - bool finished = celxScript->tick(dt); + bool finished = m_script->tick(dt); if (finished) cancelScript(); } } - - if (luaHook != nullptr) - luaHook->callLuaHook(this, "tick", dt); #endif // CELX + if (m_scriptHook != nullptr) + m_scriptHook->call("tick", dt); sim->update(dt); } @@ -2305,10 +2246,8 @@ void CelestiaCore::resize(GLsizei w, GLsizei h) height = h; setFOVFromZoom(); -#ifdef CELX - if (luaHook && luaHook->callLuaHook(this,"resize", (float) w, (float) h)) + if (m_scriptHook != nullptr && m_scriptHook->call("resize", float(w), float(h))) return; -#endif } @@ -3076,9 +3015,9 @@ void CelestiaCore::setScriptImage(std::unique_ptr &&_image) void CelestiaCore::renderOverlay() { -#ifdef CELX - if (luaHook) luaHook->callLuaHook(this, "renderoverlay"); -#endif + if (m_scriptHook != nullptr) + m_scriptHook->call("renderoverlay"); + if (font == nullptr) return; @@ -3091,7 +3030,7 @@ void CelestiaCore::renderOverlay() overlay->begin(); #ifdef CELX - if (runningScript || celxScript) + if (runningScript || m_script != nullptr) { #else if (runningScript) @@ -4553,146 +4492,9 @@ bool CelestiaCore::referenceMarkEnabled(const string& refMark, Selection sel) co #ifdef CELX -class LuaPathFinder -{ - set dirs; - - public: - const string getLuaPath() const - { - string out; - for (const auto& dir : dirs) - out += (dir / "?.lua;").string(); - return out; - } - - void process(const fs::path& p) - { - auto dir = p.parent_path(); - if (p.extension() == ".lua") - { - if (dirs.count(dir) == 0) - dirs.insert(dir); - } - }; -}; - - -// Initialize the Lua hook table as well as the Lua state for scripted -// objects. The Lua hook operates in a different Lua state than user loaded -// scripts. It always has file system access via the IO package. If the script -// system access policy is "allow", then scripted objects will run in the same -// Lua context as the Lua hook. Sharing state between scripted objects and the -// hook can be very useful, but it gives system access to scripted objects, -// and therefore must be restricted based on the system access policy. bool CelestiaCore::initLuaHook(ProgressNotifier* progressNotifier) { - luaHook = new LuaState(); - luaHook->init(this); - - string LuaPath = "?.lua;celxx/?.lua;"; - - // Find the path for lua files in the extras directories - for (const auto& dir : config->extrasDirs) - { - if (!is_valid_directory(dir)) - continue; - - LuaPathFinder loader; - for (const auto& fn : fs::recursive_directory_iterator(dir)) - loader.process(fn); - LuaPath += loader.getLuaPath(); - } - - // Always grant access for the Lua hook - luaHook->allowSystemAccess(); - - luaHook->setLuaPath(LuaPath); - - int status = 0; - - // Execute the Lua hook initialization script - if (!config->luaHook.empty()) - { - ifstream scriptfile(config->luaHook.string()); - if (!scriptfile.good()) - { - string errMsg; - errMsg = fmt::sprintf(_("Error opening LuaHook '%s'"), config->luaHook); - fatalError(errMsg); - } - - if (progressNotifier) - progressNotifier->update(config->luaHook.string()); - - status = luaHook->loadScript(scriptfile, config->luaHook); - } - else - { - status = luaHook->loadScript(""); - } - - if (status != 0) - { - cerr << "lua hook load failed\n"; - string errMsg = luaHook->getErrorMessage(); - if (errMsg.empty()) - errMsg = _("Unknown error loading hook script"); - fatalError(errMsg); - delete luaHook; - luaHook = nullptr; - } - else - { - // Coroutine execution; control may be transferred between the - // script and Celestia's event loop - if (!luaHook->createThread()) - { - cerr << "hook thread failed\n"; - string errMsg = _("Script coroutine initialization failed"); - fatalError(errMsg); - delete luaHook; - luaHook = nullptr; - } - - if (luaHook) - { - while (!luaHook->tick(0.1)) ; - } - } - - // Set up the script context; if the system access policy is allow, - // it will share the same context as the Lua hook. Otherwise, we - // create a private context. - if (config->scriptSystemAccessPolicy == "allow") - { - if (luaHook) - { - SetScriptedObjectContext(luaHook->getState()); - } - } - else - { - luaSandbox = new LuaState(); - luaSandbox->init(this); - - // Allow access to functions in package because we need 'require' - // But, loadlib is prohibited. - luaSandbox->allowLuaPackageAccess(); - luaSandbox->setLuaPath(LuaPath); - - status = luaSandbox->loadScript(""); - if (status != 0) - { - delete luaSandbox; - luaSandbox = nullptr; - return false; - } - - SetScriptedObjectContext(luaSandbox->getState()); - } - - return true; + return CreateLuaEnvironment(this, config, progressNotifier); } #endif diff --git a/src/celestia/celestiacore.h b/src/celestia/celestiacore.h index 2761a58f..89123c3b 100644 --- a/src/celestia/celestiacore.h +++ b/src/celestia/celestiacore.h @@ -30,7 +30,9 @@ #include "view.h" #ifdef CELX #include +#include #endif +#include #include class Url; @@ -334,6 +336,7 @@ class CelestiaCore // : public Watchable const std::string& getTypedText() const { return typedText; } void setTypedText(const char *); + void setScriptHook(std::unique_ptr &&hook) { m_scriptHook = std::move(hook); } const std::shared_ptr& scriptMaps() const { return m_scriptMaps; } protected: @@ -395,12 +398,10 @@ class CelestiaCore // : public Watchable Execution* runningScript{ nullptr }; ExecutionEnvironment* execEnv{ nullptr }; -#ifdef CELX - LuaState* celxScript{ nullptr }; - LuaState* luaHook{ nullptr }; // Lua hook context - LuaState* luaSandbox{ nullptr }; // Safe Lua context for ssc scripts -#endif // CELX - std::shared_ptr m_scriptMaps; + std::unique_ptr m_script; + std::unique_ptr m_scriptHook; + std::unique_ptr m_luaPlugin; + std::shared_ptr m_scriptMaps; enum ScriptState { diff --git a/src/celscript/lua/CMakeLists.txt b/src/celscript/lua/CMakeLists.txt index aed3e916..919824fd 100644 --- a/src/celscript/lua/CMakeLists.txt +++ b/src/celscript/lua/CMakeLists.txt @@ -24,6 +24,8 @@ set(CELX_SOURCES celx_rotation.h celx_vector.cpp celx_vector.h + luascript.cpp + luascript.h ) add_library(celluascript OBJECT ${CELX_SOURCES}) diff --git a/src/celscript/lua/celx_category.h b/src/celscript/lua/celx_category.h index 1caa573d..ea879d2d 100644 --- a/src/celscript/lua/celx_category.h +++ b/src/celscript/lua/celx_category.h @@ -1,3 +1,12 @@ +// celx_category.h +// +// Copyright (C) 2019, the Celestia Development Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + #pragma once #include "celx_internal.h" diff --git a/src/celscript/lua/celx_misc.cpp b/src/celscript/lua/celx_misc.cpp index b73b00aa..cfae3828 100644 --- a/src/celscript/lua/celx_misc.cpp +++ b/src/celscript/lua/celx_misc.cpp @@ -1,3 +1,11 @@ +// celx_misc.cpp +// +// Copyright (C) 2019, the Celestia Development Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. #include #include "celx_misc.h" diff --git a/src/celscript/lua/celx_misc.h b/src/celscript/lua/celx_misc.h index dff04159..667bbc49 100644 --- a/src/celscript/lua/celx_misc.h +++ b/src/celscript/lua/celx_misc.h @@ -1,3 +1,12 @@ +// celx_misc.h +// +// Copyright (C) 2019, the Celestia Development Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + #pragma once #include "celx_internal.h" diff --git a/src/celscript/lua/luascript.cpp b/src/celscript/lua/luascript.cpp new file mode 100644 index 00000000..ee99dbd4 --- /dev/null +++ b/src/celscript/lua/luascript.cpp @@ -0,0 +1,280 @@ +// luascript.cpp +// +// Copyright (C) 2019, the Celestia Development Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "celx_internal.h" +#include "luascript.h" + +using namespace std; + +namespace celestia +{ +namespace scripts +{ + +LuaScript::LuaScript(CelestiaCore *appcore) : + m_appCore(appcore), + m_celxScript(make_unique()) +{ + m_celxScript->init(m_appCore); +} + +LuaScript::~LuaScript() +{ + m_celxScript->cleanup(); +} + +bool LuaScript::load(ifstream &scriptfile, const fs::path &path, string &errorMsg) +{ + if (m_celxScript->loadScript(scriptfile, path.string()) != 0) + { + errorMsg = m_celxScript->getErrorMessage(); + return false; + } + return true; +} + +bool LuaScript::handleMouseButtonEvent(float x, float y, int button, bool down) +{ + return m_celxScript->handleMouseButtonEvent(x, y, button, down); +} + +bool LuaScript::charEntered(const char* c) +{ + return m_celxScript->charEntered(c); +} + +bool LuaScript::handleKeyEvent(const char* key) +{ + return m_celxScript->handleKeyEvent(key); +} + +bool LuaScript::handleTickEvent(double dt) +{ + return m_celxScript->handleTickEvent(dt); +} + +bool LuaScript::tick(double dt) +{ + return m_celxScript->tick(dt); +} + +bool LuaScriptPlugin::isOurFile(const fs::path &p) const +{ + auto ext = p.extension(); + return ext == ".celx" || ext == ".clx"; +} + +unique_ptr LuaScriptPlugin::loadScript(const fs::path &path) +{ + ifstream scriptfile(path.string()); + if (!scriptfile.good()) + { + appCore()->fatalError(fmt::sprintf(_("Error opening script '%s'"), path)); + return nullptr; + } + + auto script = make_unique(appCore()); + string errMsg; + if (!script->load(scriptfile, path, errMsg)) + { + if (errMsg.empty()) + errMsg = _("Unknown error loading script"); + appCore()->fatalError(errMsg); + return nullptr; + } + // Coroutine execution; control may be transferred between the + // script and Celestia's event loop + if (!script->m_celxScript->createThread()) + { + appCore()->fatalError(_("Script coroutine initialization failed")); + return nullptr; + } + + return script; +} + +bool LuaHook::call(const char *method) const +{ + return m_state->callLuaHook(appCore(), method); +} + +bool LuaHook::call(const char *method, const char *keyName) const +{ + return m_state->callLuaHook(appCore(), method, keyName); +} + +bool LuaHook::call(const char *method, float x, float y) const +{ + return m_state->callLuaHook(appCore(), method, x, y); +} + +bool LuaHook::call(const char *method, float x, float y, int b) const +{ + return m_state->callLuaHook(appCore(), method, x, y, b); +} + +bool LuaHook::call(const char *method, double dt) const +{ + return m_state->callLuaHook(appCore(), method, dt); +} + +class LuaPathFinder +{ + set dirs; + + public: + const string getLuaPath() const + { + string out; + for (const auto& dir : dirs) + out += (dir / "?.lua;").string(); + return out; + } + + void process(const fs::path& p) + { + auto dir = p.parent_path(); + if (p.extension() == ".lua" && dirs.count(dir) == 0) + dirs.insert(dir); + } +}; + +static string lua_path(const CelestiaConfig *config) +{ + string LuaPath = "?.lua;celxx/?.lua;"; + + // Find the path for lua files in the extras directories + for (const auto& dir : config->extrasDirs) + { + if (dir.empty()) + continue; + + if (!is_directory(dir)) + { + fmt::fprintf(cerr, "Path %s doesn't exist or isn't a directory", dir); + continue; + } + + LuaPathFinder loader; + for (const auto& fn : fs::recursive_directory_iterator(dir)) + loader.process(fn); + LuaPath += loader.getLuaPath(); + } + return LuaPath; +} + +// Initialize the Lua hook table as well as the Lua state for scripted +// objects. The Lua hook operates in a different Lua state than user loaded +// scripts. It always has file system access via the IO package. If the script +// system access policy is "allow", then scripted objects will run in the same +// Lua context as the Lua hook. Sharing state between scripted objects and the +// hook can be very useful, but it gives system access to scripted objects, +// and therefore must be restricted based on the system access policy. +bool CreateLuaEnvironment(CelestiaCore *appCore, const CelestiaConfig *config, ProgressNotifier *progressNotifier) +{ + auto LuaPath = lua_path(config); + + LuaState *luaHook = new LuaState(); + luaHook->init(appCore); + + // Always grant access for the Lua hook + luaHook->allowSystemAccess(); + luaHook->setLuaPath(LuaPath); + + int status = 0; + // Execute the Lua hook initialization script + if (!config->luaHook.empty()) + { + ifstream scriptfile(config->luaHook.string()); + if (!scriptfile.good()) + appCore->fatalError(fmt::sprintf(_("Error opening LuaHook '%s'"), config->luaHook)); + + if (progressNotifier != nullptr) + progressNotifier->update(config->luaHook.string()); + + status = luaHook->loadScript(scriptfile, config->luaHook); + } + else + { + status = luaHook->loadScript(""); + } + + if (status != 0) + { + cerr << "lua hook load failed\n"; + string errMsg = luaHook->getErrorMessage(); + if (errMsg.empty()) + errMsg = _("Unknown error loading hook script"); + appCore->fatalError(errMsg); + luaHook = nullptr; + } + else + { + // Coroutine execution; control may be transferred between the + // script and Celestia's event loop + if (!luaHook->createThread()) + { + cerr << "hook thread failed\n"; + appCore->fatalError(_("Script coroutine initialization failed")); + luaHook = nullptr; + } + + if (luaHook != nullptr) + { + auto lh = make_unique(appCore); + lh->m_state = unique_ptr(luaHook); + appCore->setScriptHook(std::move(lh)); + + while (!luaHook->tick(0.1)) ; + } + } + + // Set up the script context; if the system access policy is allow, + // it will share the same context as the Lua hook. Otherwise, we + // create a private context. + if (config->scriptSystemAccessPolicy == "allow") + { + if (luaHook != nullptr) + SetScriptedObjectContext(luaHook->getState()); + } + else + { + auto luaSandbox = new LuaState(); + luaSandbox->init(appCore); + + // Allow access to functions in package because we need 'require' + // But, loadlib is prohibited. + luaSandbox->allowLuaPackageAccess(); + luaSandbox->setLuaPath(LuaPath); + + status = luaSandbox->loadScript(""); + if (status != 0) + { + luaSandbox = nullptr; + return false; + } + + SetScriptedObjectContext(luaSandbox->getState()); + } + + return true; +} + +} +} diff --git a/src/celscript/lua/luascript.h b/src/celscript/lua/luascript.h new file mode 100644 index 00000000..308d477d --- /dev/null +++ b/src/celscript/lua/luascript.h @@ -0,0 +1,87 @@ +// luascript.h +// +// Copyright (C) 2019, the Celestia Development Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +#pragma once + +#include +#include + +class LuaState; +class CelestiaConfig; +class CelestiaCore; +class ProgressNotifier; + +namespace celestia +{ +namespace scripts +{ + +class LuaScript : public IScript +{ + public: + LuaScript(CelestiaCore*); + ~LuaScript() override; + + bool load(std::ifstream&, const fs::path&, std::string&); + + bool handleMouseButtonEvent(float x, float y, int button, bool down) override; + bool charEntered(const char*) override; + bool handleKeyEvent(const char* key) override; + bool handleTickEvent(double dt) override; + bool tick(double) override; + + private: + CelestiaCore *m_appCore; + std::unique_ptr m_celxScript; + + friend class LuaScriptPlugin; +}; + +class LuaScriptPlugin : public IScriptPlugin +{ + public: + LuaScriptPlugin() = delete; + LuaScriptPlugin(CelestiaCore *appCore) : IScriptPlugin(appCore) {}; + ~LuaScriptPlugin() override = default; + LuaScriptPlugin(const LuaScriptPlugin&) = delete; + LuaScriptPlugin(LuaScriptPlugin&&) = delete; + LuaScriptPlugin& operator=(const LuaScriptPlugin&) = delete; + LuaScriptPlugin& operator=(LuaScriptPlugin&&) = delete; + + bool isOurFile(const fs::path&) const override; + std::unique_ptr loadScript(const fs::path&) override; +}; + +class LuaHook : public IScriptHook +{ + public: + LuaHook() = delete; + LuaHook(CelestiaCore *appCore) : IScriptHook(appCore) {}; + ~LuaHook() override = default; + LuaHook(const LuaHook&) = default; + LuaHook(LuaHook&&) = default; + LuaHook& operator=(const LuaHook&) = default; + LuaHook& operator=(LuaHook&&) = default; + + bool call(const char *method) const override; + bool call(const char *method, const char *keyName) const override; + bool call(const char *method, float x, float y) const override; + bool call(const char *method, float x, float y, int b) const override; + bool call(const char *method, double dt) const override; + + private: + std::unique_ptr m_state; + + friend bool CreateLuaEnvironment(CelestiaCore*, const CelestiaConfig*, ProgressNotifier*); +}; + +bool CreateLuaEnvironment(CelestiaCore *appCore, const CelestiaConfig *config, ProgressNotifier *progressNotifier = nullptr); + +} +}