osb/source/game/scripting/StarLuaComponents.hpp
Kai Blaschke 431a9c00a5
Fixed a huge amount of Clang warnings
On Linux and macOS, using Clang to compile OpenStarbound produces about 400 MB worth of warnings during the build, making the compiler output unreadable and slowing the build down considerably.

99% of the warnings were unqualified uses of std::move and std::forward, which are now all properly qualified.

Fixed a few other minor warnings about non-virtual destructors and some uses of std::move preventing copy elision on temporary objects.

Most remaining warnings are now unused parameters.
2024-02-19 16:55:19 +01:00

375 lines
11 KiB
C++

#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(float dt) 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;
mutable float m_lastDt;
};
// 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:
struct MessageHandler {
Maybe<LuaFunction> function;
String name;
bool passName = true;
bool localOnly = false;
};
StringMap<MessageHandler> 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>(std::move(method)).invoke<Ret>(std::forward<V>(args)...);
} catch (LuaException const& e) {
Logger::error("Exception while invoking lua function '{}'. {}", 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: {}", 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", std::move(storage));
else
m_storage = std::move(storage);
}
template <typename Base>
void LuaStorableComponent<Base>::contextSetup() {
Base::contextSetup();
Base::context()->setPath("storage", std::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);
});
m_lastDt = GlobalTimestep * GlobalTimescale;
Base::addCallbacks("script", std::move(scriptCallbacks));
}
template <typename Base>
unsigned LuaUpdatableComponent<Base>::updateDelta() const {
return m_updatePeriodic.stepCount();
}
template <typename Base>
float LuaUpdatableComponent<Base>::updateDt(float dt) const {
m_lastDt = dt;
return m_updatePeriodic.stepCount() * dt;
}
template <typename Base>
float LuaUpdatableComponent<Base>::updateDt() const {
return m_updatePeriodic.stepCount() * m_lastDt;
}
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", std::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](Variant<String, Json> message, Maybe<LuaFunction> handler) {
MessageHandler handlerInfo = {};
if (Json* config = message.ptr<Json>()) {
handlerInfo.passName = config->getBool("passName", false);
handlerInfo.localOnly = config->getBool("localOnly", false);
handlerInfo.name = config->getString("name");
} else {
handlerInfo.passName = true;
handlerInfo.localOnly = false;
handlerInfo.name = message.get<String>();
}
if (handler) {
handlerInfo.function.emplace(handler.take());
m_messageHandlers.set(handlerInfo.name, handlerInfo);
}
else
m_messageHandlers.remove(handlerInfo.name);
});
Base::addCallbacks("message", std::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 {
if (handler->localOnly) {
if (!localMessage)
return {};
else if (handler->passName)
return handler->function->template invoke<Json>(message, luaUnpack(args));
else
return handler->function->template invoke<Json>(luaUnpack(args));
}
else if (handler->passName)
return handler->function->template invoke<Json>(message, localMessage, luaUnpack(args));
else
return handler->function->template invoke<Json>(localMessage, luaUnpack(args));
} catch (LuaException const& e) {
Logger::error(
"Exception while invoking lua message handler for message '{}'. {}", message, outputException(e, true));
Base::setError(String(printException(e, false)));
}
}
return {};
}
template <typename Base>
void LuaMessageHandlingComponent<Base>::contextShutdown() {
m_messageHandlers.clear();
Base::contextShutdown();
}
}
#endif