Implementation of the new interface for Lua scripting
parent
d93a1cdf03
commit
dfd177ffc7
|
@ -37,6 +37,7 @@
|
|||
#include <celutil/debug.h>
|
||||
#include <celutil/utf8.h>
|
||||
#include <celcompat/filesystem.h>
|
||||
#include <celcompat/memory.h>
|
||||
#include <Eigen/Geometry>
|
||||
#include <GL/glew.h>
|
||||
#include <iostream>
|
||||
|
@ -179,6 +180,7 @@ CelestiaCore::CelestiaCore() :
|
|||
renderer(new Renderer()),
|
||||
timer(new Timer()),
|
||||
execEnv(new CoreExecutionEnvironment(*this)),
|
||||
m_luaPlugin(make_unique<LuaScriptPlugin>(this)),
|
||||
m_scriptMaps(make_shared<ScriptMaps>())
|
||||
{
|
||||
|
||||
|
@ -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<OverlayImage> &&_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<fs::path> 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
|
||||
|
||||
|
|
|
@ -30,7 +30,9 @@
|
|||
#include "view.h"
|
||||
#ifdef CELX
|
||||
#include <celscript/lua/celx.h>
|
||||
#include <celscript/lua/luascript.h>
|
||||
#endif
|
||||
#include <celscript/common/script.h>
|
||||
#include <celscript/common/scriptmaps.h>
|
||||
|
||||
class Url;
|
||||
|
@ -334,6 +336,7 @@ class CelestiaCore // : public Watchable<CelestiaCore>
|
|||
const std::string& getTypedText() const { return typedText; }
|
||||
void setTypedText(const char *);
|
||||
|
||||
void setScriptHook(std::unique_ptr<celestia::scripts::IScriptHook> &&hook) { m_scriptHook = std::move(hook); }
|
||||
const std::shared_ptr<celestia::scripts::ScriptMaps>& scriptMaps() const { return m_scriptMaps; }
|
||||
|
||||
protected:
|
||||
|
@ -395,12 +398,10 @@ class CelestiaCore // : public Watchable<CelestiaCore>
|
|||
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<celestia::scripts::ScriptMaps> m_scriptMaps;
|
||||
std::unique_ptr<celestia::scripts::IScript> m_script;
|
||||
std::unique_ptr<celestia::scripts::IScriptHook> m_scriptHook;
|
||||
std::unique_ptr<celestia::scripts::LuaScriptPlugin> m_luaPlugin;
|
||||
std::shared_ptr<celestia::scripts::ScriptMaps> m_scriptMaps;
|
||||
|
||||
enum ScriptState
|
||||
{
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 <celutil/debug.h>
|
||||
#include "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"
|
||||
|
|
|
@ -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 <set>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <fmt/printf.h>
|
||||
#include <celcompat/filesystem.h>
|
||||
#include <celcompat/memory.h>
|
||||
#include <celephem/scriptobject.h>
|
||||
#include <celestia/configfile.h>
|
||||
#include <celestia/celestiacore.h>
|
||||
#include <celutil/util.h>
|
||||
#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<LuaState>())
|
||||
{
|
||||
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<IScript> 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<LuaScript>(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<fs::path> 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<LuaHook>(appCore);
|
||||
lh->m_state = unique_ptr<LuaState>(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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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 <celscript/common/script.h>
|
||||
#include <iosfwd>
|
||||
|
||||
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<LuaState> 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<IScript> 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<LuaState> m_state;
|
||||
|
||||
friend bool CreateLuaEnvironment(CelestiaCore*, const CelestiaConfig*, ProgressNotifier*);
|
||||
};
|
||||
|
||||
bool CreateLuaEnvironment(CelestiaCore *appCore, const CelestiaConfig *config, ProgressNotifier *progressNotifier = nullptr);
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue