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

summaryrefslogtreecommitdiff
path: root/source/game/scripting/StarLuaComponents.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/game/scripting/StarLuaComponents.hpp')
-rw-r--r--source/game/scripting/StarLuaComponents.hpp333
1 files changed, 333 insertions, 0 deletions
diff --git a/source/game/scripting/StarLuaComponents.hpp b/source/game/scripting/StarLuaComponents.hpp
new file mode 100644
index 0000000..dec2b23
--- /dev/null
+++ b/source/game/scripting/StarLuaComponents.hpp
@@ -0,0 +1,333 @@
+#ifndef STAR_LUA_COMPONENT_HPP
+#define STAR_LUA_COMPONENT_HPP
+
+#include "StarPeriodic.hpp"
+#include "StarLogging.hpp"
+#include "StarListener.hpp"
+#include "StarWorld.hpp"
+#include "StarWorldLuaBindings.hpp"
+
+namespace Star {
+
+STAR_EXCEPTION(LuaComponentException, LuaException);
+
+// 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).
+//
+// Callbacks can be added and removed whether or not the context is initialized
+// or not, they will be added back during a call to init. 'root' callbacks are
+// available by default as well as an ephemeral 'self' table.
+//
+// All script function calls (init / uninit / invoke) guard against missing
+// functions. If the function is missing, it will do nothing and return
+// nothing. If the function exists but throws an error, the error will be
+// logged and the component will go into the error state.
+//
+// Whenever an error is set, all function calls or eval will fail until the
+// error is cleared by re-initializing.
+//
+// If 'autoReInit' is set, Monitors Root for reloads, and if a root reload
+// occurs, will automatically (on the next call to invoke) uninit and then
+// re-init the script before calling invoke. 'autoReInit' defaults to true.
+class LuaBaseComponent {
+public:
+ LuaBaseComponent();
+ // The LuaBaseComponent destructor does NOT call the 'unint' entry point in
+ // the script. In order to do so, uninit() must be called manually before
+ // destruction. This is because during destruction, it is highly likely that
+ // callbacks may not be valid, and highly likely that exceptions could be
+ // thrown.
+ virtual ~LuaBaseComponent();
+
+ LuaBaseComponent(LuaBaseComponent const& component) = delete;
+ LuaBaseComponent& operator=(LuaBaseComponent const& component) = delete;
+
+ StringList const& scripts() const;
+ void setScript(String script);
+ void setScripts(StringList scripts);
+
+ void addCallbacks(String groupName, LuaCallbacks callbacks);
+ bool removeCallbacks(String const& groupName);
+
+ // If true, component will automatically uninit and re-init when root is
+ // reloaded.
+ bool autoReInit() const;
+ void setAutoReInit(bool autoReInit);
+
+ // Lua components require access to a LuaRoot object to initialize /
+ // uninitialize.
+ void setLuaRoot(LuaRootPtr luaRoot);
+ LuaRootPtr const& luaRoot();
+
+ // init returns true on success, false if there has been an error
+ // initializing the script. LuaRoot must be set before calling or this will
+ // always fail. Calls the 'init' entry point on the script context.
+ bool init();
+ // uninit will uninitialize the LuaComponent if it is currently initialized.
+ // This calls the 'uninit' entry point on the script context before
+ // destroying the context.
+ void uninit();
+
+ bool initialized() const;
+
+ template <typename Ret = LuaValue, typename... V>
+ Maybe<Ret> invoke(String const& name, V&&... args);
+
+ template <typename Ret = LuaValue>
+ Maybe<LuaValue> eval(String const& code);
+
+ // Returns last error, if there has been an error. Errors can only be
+ // cleared by re-initializing the context.
+ Maybe<String> const& error() const;
+
+ Maybe<LuaContext> const& context() const;
+ Maybe<LuaContext>& context();
+
+protected:
+ virtual void contextSetup();
+ virtual void contextShutdown();
+
+ void setError(String error);
+
+ // Checks the initialization state of the script, while also reloading the
+ // script and clearing the error state if a root reload has occurred.
+ bool checkInitialization();
+
+private:
+ StringList m_scripts;
+ StringMap<LuaCallbacks> m_callbacks;
+ LuaRootPtr m_luaRoot;
+ TrackerListenerPtr m_reloadTracker;
+ Maybe<LuaContext> m_context;
+ Maybe<String> m_error;
+};
+
+// Wraps a basic lua component to add a persistent storage table translated
+// into JSON that can be stored outside of the script context.
+template <typename Base>
+class LuaStorableComponent : public Base {
+public:
+ JsonObject getScriptStorage() const;
+ void setScriptStorage(JsonObject storage);
+
+protected:
+ virtual void contextSetup() override;
+ virtual void contextShutdown() override;
+
+private:
+ JsonObject m_storage;
+};
+
+// Wraps a basic lua component with an 'update' method and an embedded tick
+// rate. Every call to 'update' here will only call the internal script
+// 'update' at the configured delta. Adds a update tick controls under the
+// 'script' callback table.
+template <typename Base>
+class LuaUpdatableComponent : public Base {
+public:
+ LuaUpdatableComponent();
+
+ unsigned updateDelta() const;
+ float updateDt() const;
+ void setUpdateDelta(unsigned updateDelta);
+
+ // Returns true if the next update will call the internal script update
+ // method.
+ bool updateReady() const;
+
+ template <typename Ret = LuaValue, typename... V>
+ Maybe<Ret> update(V&&... args);
+
+private:
+ Periodic m_updatePeriodic;
+};
+
+// Wraps a basic lua component so that world callbacks are added on init, and
+// removed on uninit, and sets the world LuaRoot as the LuaBaseComponent
+// LuaRoot automatically.
+template <typename Base>
+class LuaWorldComponent : public Base {
+public:
+ void init(World* world);
+ void uninit();
+
+protected:
+ using Base::setLuaRoot;
+ using Base::init;
+};
+
+// Component for scripts which can be used as entity message handlers, provides
+// a 'message' table with 'setHandler' callback to set message handlers.
+template <typename Base>
+class LuaMessageHandlingComponent : public Base {
+public:
+ LuaMessageHandlingComponent();
+
+ Maybe<Json> handleMessage(String const& message, bool localMessage, JsonArray const& args = {});
+
+protected:
+ virtual void contextShutdown() override;
+
+private:
+ StringMap<LuaFunction> m_messageHandlers;
+};
+
+template <typename Ret, typename... V>
+Maybe<Ret> LuaBaseComponent::invoke(String const& name, V&&... args) {
+ if (!checkInitialization())
+ return {};
+
+ try {
+ auto method = m_context->getPath(name);
+ if (method == LuaNil)
+ return {};
+ return m_context->luaTo<LuaFunction>(move(method)).invoke<Ret>(forward<V>(args)...);
+ } catch (LuaException const& e) {
+ Logger::error("Exception while invoking lua function '%s'. %s", name, outputException(e, true));
+ setError(printException(e, false));
+ return {};
+ }
+}
+
+template <typename Ret>
+Maybe<LuaValue> LuaBaseComponent::eval(String const& code) {
+ if (!checkInitialization())
+ return {};
+
+ try {
+ return m_context->eval<Ret>(code);
+ } catch (LuaException const& e) {
+ Logger::error("Exception while evaluating lua in context: %s", outputException(e, true));
+ return {};
+ }
+}
+
+template <typename Base>
+JsonObject LuaStorableComponent<Base>::getScriptStorage() const {
+ if (Base::initialized())
+ return Base::context()->template getPath<JsonObject>("storage");
+ else
+ return m_storage;
+}
+
+template <typename Base>
+void LuaStorableComponent<Base>::setScriptStorage(JsonObject storage) {
+ if (Base::initialized())
+ Base::context()->setPath("storage", move(storage));
+ else
+ m_storage = move(storage);
+}
+
+template <typename Base>
+void LuaStorableComponent<Base>::contextSetup() {
+ Base::contextSetup();
+ Base::context()->setPath("storage", move(m_storage));
+}
+
+template <typename Base>
+void LuaStorableComponent<Base>::contextShutdown() {
+ m_storage = Base::context()->template getPath<JsonObject>("storage");
+ Base::contextShutdown();
+}
+
+template <typename Base>
+LuaUpdatableComponent<Base>::LuaUpdatableComponent() {
+ m_updatePeriodic.setStepCount(1);
+
+ LuaCallbacks scriptCallbacks;
+ scriptCallbacks.registerCallback("updateDt", [this]() {
+ return updateDt();
+ });
+ scriptCallbacks.registerCallback("setUpdateDelta", [this](unsigned d) {
+ setUpdateDelta(d);
+ });
+
+ Base::addCallbacks("script", move(scriptCallbacks));
+}
+
+template <typename Base>
+unsigned LuaUpdatableComponent<Base>::updateDelta() const {
+ return m_updatePeriodic.stepCount();
+}
+
+template <typename Base>
+float LuaUpdatableComponent<Base>::updateDt() const {
+ return m_updatePeriodic.stepCount() * WorldTimestep;
+}
+
+template <typename Base>
+void LuaUpdatableComponent<Base>::setUpdateDelta(unsigned updateDelta) {
+ m_updatePeriodic.setStepCount(updateDelta);
+}
+
+template <typename Base>
+bool LuaUpdatableComponent<Base>::updateReady() const {
+ return m_updatePeriodic.ready();
+}
+
+template <typename Base>
+template <typename Ret, typename... V>
+Maybe<Ret> LuaUpdatableComponent<Base>::update(V&&... args) {
+ if (!m_updatePeriodic.tick())
+ return {};
+
+ return Base::template invoke<Ret>("update", forward<V>(args)...);
+}
+
+template <typename Base>
+void LuaWorldComponent<Base>::init(World* world) {
+ if (Base::initialized())
+ uninit();
+
+ Base::setLuaRoot(world->luaRoot());
+ Base::addCallbacks("world", LuaBindings::makeWorldCallbacks(world));
+ Base::init();
+}
+
+template <typename Base>
+void LuaWorldComponent<Base>::uninit() {
+ Base::uninit();
+ Base::removeCallbacks("world");
+}
+
+template <typename Base>
+LuaMessageHandlingComponent<Base>::LuaMessageHandlingComponent() {
+ LuaCallbacks scriptCallbacks;
+ scriptCallbacks.registerCallback("setHandler",
+ [this](String message, Maybe<LuaFunction> handler) {
+ if (handler)
+ m_messageHandlers.set(move(message), handler.take());
+ else
+ m_messageHandlers.remove(message);
+ });
+
+ Base::addCallbacks("message", move(scriptCallbacks));
+}
+
+template <typename Base>
+Maybe<Json> LuaMessageHandlingComponent<Base>::handleMessage(
+ String const& message, bool localMessage, JsonArray const& args) {
+ if (!Base::initialized())
+ return {};
+
+ if (auto handler = m_messageHandlers.ptr(message)) {
+ try {
+ return handler->template invoke<Json>(message, localMessage, luaUnpack(args));
+ } catch (LuaException const& e) {
+ Logger::error(
+ "Exception while invoking lua message handler for message '%s'. %s", message, outputException(e, true));
+ Base::setError(String(printException(e, false)));
+ }
+ }
+ return {};
+}
+
+template <typename Base>
+void LuaMessageHandlingComponent<Base>::contextShutdown() {
+ m_messageHandlers.clear();
+ Base::contextShutdown();
+}
+}
+
+#endif