Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/game/scripting
diff options
context:
space:
mode:
Diffstat (limited to 'source/game/scripting')
-rw-r--r--source/game/scripting/StarLuaComponents.cpp43
-rw-r--r--source/game/scripting/StarLuaComponents.hpp7
-rw-r--r--source/game/scripting/StarScriptableThread.cpp152
-rw-r--r--source/game/scripting/StarScriptableThread.hpp71
4 files changed, 272 insertions, 1 deletions
diff --git a/source/game/scripting/StarLuaComponents.cpp b/source/game/scripting/StarLuaComponents.cpp
index 9a1b1c4..7a6db8b 100644
--- a/source/game/scripting/StarLuaComponents.cpp
+++ b/source/game/scripting/StarLuaComponents.cpp
@@ -1,16 +1,22 @@
#include "StarLuaComponents.hpp"
#include "StarUtilityLuaBindings.hpp"
#include "StarRootLuaBindings.hpp"
+#include "StarScriptableThread.hpp"
+#include "StarRpcThreadPromise.hpp"
+#include "StarLuaGameConverters.hpp"
namespace Star {
LuaBaseComponent::LuaBaseComponent() {
addCallbacks("sb", LuaBindings::makeUtilityCallbacks());
addCallbacks("root", LuaBindings::makeRootCallbacks());
+ addCallbacks("threads", makeThreadsCallbacks());
setAutoReInit(true);
}
-LuaBaseComponent::~LuaBaseComponent() {}
+LuaBaseComponent::~LuaBaseComponent() {
+ m_threads.clear();
+}
StringList const& LuaBaseComponent::scripts() const {
return m_scripts;
@@ -109,6 +115,9 @@ void LuaBaseComponent::uninit() {
contextShutdown();
m_context.reset();
}
+ for (auto p : m_threads) {
+ p.second->stop();
+ }
m_error.reset();
}
@@ -152,4 +161,36 @@ bool LuaBaseComponent::checkInitialization() {
return initialized();
}
+LuaCallbacks LuaBaseComponent::makeThreadsCallbacks() {
+ LuaCallbacks callbacks;
+
+ callbacks.registerCallback("create", [this](Json parameters) {
+ auto name = parameters.getString("name");
+ if (m_threads.contains(name)) {
+ m_threads.get(name)->stop();
+ m_threads.remove(name);
+ }
+ auto thread = make_shared<ScriptableThread>(parameters);
+ thread->setPause(false);
+ thread->start();
+ m_threads.set(name,thread);
+ return name;
+ });
+ callbacks.registerCallback("setPause", [this](String const& threadName, bool paused) {
+ m_threads.get(threadName)->setPause(paused);
+ });
+ callbacks.registerCallback("stop", [this](String const& threadName) {
+ m_threads.get(threadName)->stop();
+ m_threads.remove(threadName);
+ });
+ callbacks.registerCallback("sendMessage", [this](String const& threadName, String const& message, LuaVariadic<Json> args) {
+ auto pair = RpcThreadPromise<Json>::createPair();
+ RecursiveMutexLocker locker(m_threadLock);
+ m_threads.get(threadName)->passMessage({ message, args, pair.second });
+ return pair.first;
+ });
+
+ return callbacks;
+}
+
}
diff --git a/source/game/scripting/StarLuaComponents.hpp b/source/game/scripting/StarLuaComponents.hpp
index e3d1d20..b6c19ac 100644
--- a/source/game/scripting/StarLuaComponents.hpp
+++ b/source/game/scripting/StarLuaComponents.hpp
@@ -10,6 +10,8 @@ namespace Star {
STAR_EXCEPTION(LuaComponentException, LuaException);
+STAR_CLASS(ScriptableThread);
+
// Basic lua component that can be initialized (takes and then owns a script
// context, calls the script context's init function) and uninitialized
// (releases the context, calls the context 'uninit' function).
@@ -94,12 +96,17 @@ protected:
bool checkInitialization();
private:
+ LuaCallbacks makeThreadsCallbacks();
+
StringList m_scripts;
StringMap<LuaCallbacks> m_callbacks;
LuaRootPtr m_luaRoot;
TrackerListenerPtr m_reloadTracker;
Maybe<LuaContext> m_context;
Maybe<String> m_error;
+
+ StringMap<shared_ptr<ScriptableThread>> m_threads;
+ mutable RecursiveMutex m_threadLock;
};
// Wraps a basic lua component to add a persistent storage table translated
diff --git a/source/game/scripting/StarScriptableThread.cpp b/source/game/scripting/StarScriptableThread.cpp
new file mode 100644
index 0000000..cbd32ad
--- /dev/null
+++ b/source/game/scripting/StarScriptableThread.cpp
@@ -0,0 +1,152 @@
+#include "StarScriptableThread.hpp"
+#include "StarLuaRoot.hpp"
+#include "StarLuaComponents.hpp"
+#include "StarConfigLuaBindings.hpp"
+#include "StarTickRateMonitor.hpp"
+#include "StarNpc.hpp"
+#include "StarRoot.hpp"
+#include "StarJsonExtra.hpp"
+#include "StarLogging.hpp"
+#include "StarAssets.hpp"
+
+namespace Star {
+
+ScriptableThread::ScriptableThread(Json parameters)
+ : Thread("ScriptableThread: " + parameters.getString("name")), // TODO
+ m_stop(false),
+ m_errorOccurred(false),
+ m_shouldExpire(true),
+ m_parameters(std::move(parameters)) {
+ m_luaRoot = make_shared<LuaRoot>();
+ m_name = m_parameters.getString("name");
+
+ m_timestep = 1.0f / m_parameters.getFloat("tickRate",60.0f);
+
+ // since thread's not blocking anything important, allow modifying the instruction limit
+ if (auto instructionLimit = m_parameters.optUInt("instructionLimit"))
+ m_luaRoot->luaEngine().setInstructionLimit(instructionLimit.value());
+
+ m_luaRoot->addCallbacks("thread", makeThreadCallbacks());
+ m_luaRoot->addCallbacks(
+ "config", LuaBindings::makeConfigCallbacks(bind(&ScriptableThread::configValue, this, _1, _2)));
+
+ for (auto& p : m_parameters.getObject("scripts")) {
+ auto scriptComponent = make_shared<ScriptComponent>();
+ scriptComponent->setLuaRoot(m_luaRoot);
+ scriptComponent->setScripts(jsonToStringList(p.second.toArray()));
+
+ m_scriptContexts.set(p.first, scriptComponent);
+ scriptComponent->init();
+ }
+}
+
+ScriptableThread::~ScriptableThread() {
+ m_stop = true;
+
+ m_scriptContexts.clear();
+
+ join();
+}
+
+void ScriptableThread::start() {
+ m_stop = false;
+ m_errorOccurred = false;
+ Thread::start();
+}
+
+void ScriptableThread::stop() {
+ m_stop = true;
+ Thread::join();
+}
+
+void ScriptableThread::setPause(bool pause) {
+ m_pause = pause;
+}
+
+bool ScriptableThread::errorOccurred() {
+ return m_errorOccurred;
+}
+
+bool ScriptableThread::shouldExpire() {
+ return m_shouldExpire;
+}
+
+void ScriptableThread::passMessage(Message&& message) {
+ RecursiveMutexLocker locker(m_messageMutex);
+ m_messages.append(std::move(message));
+}
+
+void ScriptableThread::run() {
+ try {
+ auto& root = Root::singleton();
+
+ double updateMeasureWindow = m_parameters.getDouble("updateMeasureWindow",0.5);
+ TickRateApproacher tickApproacher(1.0f / m_timestep, updateMeasureWindow);
+
+ while (!m_stop && !m_errorOccurred) {
+ LogMap::set(strf("lua_{}_update", m_name), strf("{:4.2f}Hz", tickApproacher.rate()));
+
+ update();
+ tickApproacher.setTargetTickRate(1.0f / m_timestep);
+ tickApproacher.tick();
+
+ double spareTime = tickApproacher.spareTime();
+
+ int64_t spareMilliseconds = floor(spareTime * 1000);
+ if (spareMilliseconds > 0)
+ Thread::sleepPrecise(spareMilliseconds);
+ }
+ } catch (std::exception const& e) {
+ Logger::error("ScriptableThread exception caught: {}", outputException(e, true));
+ m_errorOccurred = true;
+ }
+ for (auto& p : m_scriptContexts)
+ p.second->uninit();
+}
+
+Maybe<Json> ScriptableThread::receiveMessage(String const& message, JsonArray const& args) {
+ Maybe<Json> result;
+ for (auto& p : m_scriptContexts) {
+ result = p.second->handleMessage(message, true, args);
+ if (result)
+ break;
+ }
+ return result;
+}
+
+void ScriptableThread::update() {
+ float dt = m_timestep;
+
+ if (dt > 0.0f && !m_pause) {
+ for (auto& p : m_scriptContexts) {
+ p.second->update(p.second->updateDt(dt));
+ }
+ }
+
+ List<Message> messages;
+ {
+ RecursiveMutexLocker locker(m_messageMutex);
+ messages = std::move(m_messages);
+ }
+ for (auto& message : messages) {
+ if (auto resp = receiveMessage(message.message, message.args))
+ message.promise.fulfill(*resp);
+ else
+ message.promise.fail("Message not handled by thread");
+ }
+}
+
+LuaCallbacks ScriptableThread::makeThreadCallbacks() {
+ LuaCallbacks callbacks;
+
+ callbacks.registerCallback("stop", [this]() {
+ m_stop = true;
+ });
+
+ return callbacks;
+}
+
+Json ScriptableThread::configValue(String const& name, Json def) const {
+ return m_parameters.get(name, std::move(def));
+}
+}
diff --git a/source/game/scripting/StarScriptableThread.hpp b/source/game/scripting/StarScriptableThread.hpp
new file mode 100644
index 0000000..65f007e
--- /dev/null
+++ b/source/game/scripting/StarScriptableThread.hpp
@@ -0,0 +1,71 @@
+#pragma once
+
+#include "StarThread.hpp"
+#include "StarLuaRoot.hpp"
+#include "StarLuaComponents.hpp"
+#include "StarRpcThreadPromise.hpp"
+
+namespace Star {
+
+STAR_CLASS(ScriptableThread);
+
+// Runs a Lua in a separate thread and guards exceptions that occur in
+// it. All methods are designed to not throw exceptions, but will instead log
+// the error and trigger the ScriptableThread error state.
+class ScriptableThread : public Thread {
+public:
+ struct Message {
+ String message;
+ JsonArray args;
+ RpcThreadPromiseKeeper<Json> promise;
+ };
+
+ typedef LuaMessageHandlingComponent<LuaUpdatableComponent<LuaBaseComponent>> ScriptComponent;
+ typedef shared_ptr<ScriptComponent> ScriptComponentPtr;
+
+ ScriptableThread(Json parameters);
+ ~ScriptableThread();
+
+ void start();
+ // Signals the ScriptableThread to stop and then joins it
+ void stop();
+ void setPause(bool pause);
+
+ // An exception occurred and the
+ // ScriptableThread has stopped running.
+ bool errorOccurred();
+ bool shouldExpire();
+
+ //
+ void passMessage(Message&& message);
+
+protected:
+ virtual void run();
+
+private:
+ void update();
+ Maybe<Json> receiveMessage(String const& message, JsonArray const& args);
+
+ mutable RecursiveMutex m_mutex;
+
+ LuaRootPtr m_luaRoot;
+ StringMap<ScriptComponentPtr> m_scriptContexts;
+
+ Json m_parameters;
+ String m_name;
+
+ float m_timestep;
+
+ mutable RecursiveMutex m_messageMutex;
+ List<Message> m_messages;
+
+ atomic<bool> m_stop;
+ atomic<bool> m_pause;
+ mutable atomic<bool> m_errorOccurred;
+ mutable atomic<bool> m_shouldExpire;
+
+ LuaCallbacks makeThreadCallbacks();
+ Json configValue(String const& name, Json def) const;
+};
+
+}