Move all Lua code to separate classes, expose most TransformComponent's methods

This commit is contained in:
SeanOMik 2022-12-05 00:42:47 -05:00
parent 51206d60d4
commit db0cdf32a1
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
11 changed files with 629 additions and 387 deletions

View File

@ -11,6 +11,7 @@
#include "simpleengine/gfx/model.h" #include "simpleengine/gfx/model.h"
#include "simpleengine/gfx/renderer.h" #include "simpleengine/gfx/renderer.h"
#include "simpleengine/gfx/texture.h" #include "simpleengine/gfx/texture.h"
#include "simpleengine/scripting/scripting_system.h"
#include "simpleengine/vector.h" #include "simpleengine/vector.h"
#include <glm/gtx/string_cast.hpp> #include <glm/gtx/string_cast.hpp>
#include <optional> #include <optional>
@ -29,9 +30,6 @@
#include <simpleengine/log/logger.h> #include <simpleengine/log/logger.h>
#include <simpleengine/physics/physics_system.h> #include <simpleengine/physics/physics_system.h>
#include <simpleengine/scripting/ecs_bindings.h>
#include <simpleengine/scripting/entt_meta_helper.h>
#include "entt/entity/fwd.hpp" #include "entt/entity/fwd.hpp"
#include <assimp/material.h> #include <assimp/material.h>
@ -47,215 +45,25 @@
#include <sol/load_result.hpp> #include <sol/load_result.hpp>
#include <spdlog/common.h> #include <spdlog/common.h>
#include <sstream> #include <sstream>
#include <stdint.h>
#include <BulletCollision/CollisionShapes/btStaticPlaneShape.h> #include <BulletCollision/CollisionShapes/btStaticPlaneShape.h>
namespace se = simpleengine; namespace se = simpleengine;
#define AUTO_ARG(x) decltype(x), x
class LuaTestScriptEvent : public se::Renderable {
public:
sol::state lua;
sol::protected_function_result script_res;
entt::registry registry;
static void lua_panic_handler(sol::optional<std::string> maybe_msg) {
std::cerr << "Lua panic: ";
if(maybe_msg) {
std::cerr << maybe_msg->c_str();
}
std::cerr << std::endl;
}
static int lua_exception_handler(lua_State* L, sol::optional<const std::exception&> maybe_exception, sol::string_view description) {
std::cerr << "Lua Exception: ";
if(maybe_exception) {
const std::exception& ex = *maybe_exception;
std::cerr << ex.what();
} else {
std::cerr.write(description.data(), description.size());
}
std::cerr << std::endl;
return sol::stack::push(L, description);
}
static std::string lua_error_handler(lua_State* lstate, std::string msg) {
std::cerr << "Lua error: " << msg << std::endl;
return msg;
}
static sol::protected_function_result lua_protected_function_handler(lua_State* lstate, sol::protected_function_result result) {
std::cerr << "Lua protected function error" << std::endl;
sol::type t = sol::type_of(lstate, result.stack_index());
std::string err = "sol: ";
err += to_string(result.status());
err += " error";
std::exception_ptr eptr = std::current_exception();
if (eptr) {
err += " with a ";
try {
std::rethrow_exception(eptr);
}
catch (const std::exception& ex) {
err += "std::exception -- ";
err.append(ex.what());
}
catch (const std::string& message) {
err += "thrown message -- ";
err.append(message);
}
catch (const char* message) {
err += "thrown message -- ";
err.append(message);
}
catch (...) {
err.append("thrown but unknown type, cannot serialize into error message");
}
}
if (t == sol::type::string) {
err += ": ";
std::string_view serr = sol::stack::unqualified_get<std::string_view>(lstate, result.stack_index());
err.append(serr.data(), serr.size());
}
std::cerr << err << std::endl;
/* sol::state_view lua(lstate);
std::string traceback = lua.script("debug.traceback()");
std::cout << "Traceback: " << traceback << std::endl; */
return result;
}
LuaTestScriptEvent() : se::Renderable(), lua(sol::c_call<decltype(&LuaTestScriptEvent::lua_panic_handler),
&LuaTestScriptEvent::lua_panic_handler>), registry({})/* , script_res(std::nullopt_t) */ {
register_meta_component<se::ecs::TransformComponent>();
lua.open_libraries(sol::lib::base, sol::lib::package, sol::lib::string, sol::lib::debug);
lua.require("registry", sol::c_call<AUTO_ARG(&open_registry)>, false); // create registry type
// Overwrite the Lua print function to use a logger
lua.globals().set_function("print", [](std::string msg) {
SE_DEBUG("Lua ScriptingEngine", "{}", msg);
});
// Make registry available to lua
lua["registry"] = std::ref(this->registry);
// Registry TransformComponent
lua.new_usertype<se::ecs::TransformComponent>("TransformComponent",
"type_id", &entt::type_hash<se::ecs::TransformComponent>::value,
sol::call_constructor, sol::factories(
[](float px, float py, float pz) {
return se::ecs::TransformComponent(glm::vec3(px, py, pz));
},
[](float px, float py, float pz, float rx, float ry, float rz) {
return se::ecs::TransformComponent(glm::vec3(px, py, pz), glm::vec3(rx, ry, rz));
},
[](float px, float py, float pz, float rx, float ry, float rz, float sx, float sy, float sz) {
return se::ecs::TransformComponent(glm::vec3(px, py, pz), glm::vec3(rx, ry, rz), glm::vec3(sx, sy, sz));
}
),
sol::meta_function::to_string, [](const se::ecs::TransformComponent& self) {
return glm::to_string(self.transform_matrix);
} //&Transform::to_string
);
lua.set_exception_handler(&LuaTestScriptEvent::lua_exception_handler);
try {
script_res = lua.safe_script(R"LUA(
print('start')
local dog = registry:create()
local cat = registry:create()
print('created cat and cat')
print('Dog is ' .. dog .. ', and registry size is ' .. registry:size())
print('Cat is ' .. cat .. ', and cat size is ' .. registry:size())
assert(dog == 0 and cat == 1 and registry:size() == 2)
registry:emplace(dog, TransformComponent(5, 6, 3))
assert(registry:has(dog, TransformComponent))
assert(registry:has(dog, TransformComponent.type_id()))
assert(not registry:any_of(dog, -1, -2, -3))
function update(delta_time)
transform = registry:get(dog, TransformComponent)
print('Dog position = ' .. tostring(transform))
end
print('Lua script loaded!')
)LUA", &LuaTestScriptEvent::lua_protected_function_handler);
} catch (sol::error e) {
std::cerr << "Ran into sol2 error: " << e.what() << std::endl;
} catch (std::exception e) {
std::cerr << "Ran into std::exception: " << e.what() << std::endl;
} catch (...) {
std::cerr << "Ran into something :shrug:" << std::endl;
}
}
virtual void update(const float &delta_time) {
if (script_res.valid()) {
sol::function lua_update = lua["update"];
if (lua_update.valid()) {
lua_update(delta_time);
}
}
}
virtual void input_update(const float &delta_time) {
if (script_res.valid()) {
sol::function lua_input_update = lua["input_update"];
if (lua_input_update.valid()) {
lua_input_update(delta_time);
}
}
}
virtual void render(const float& interpolate_alpha, const float& frame_time) {
if (script_res.valid()) {
sol::function lua_render = lua["render"];
if (lua_render.valid()) {
lua_render(interpolate_alpha, frame_time);
}
}
}
};
class FPSCounterEvent : public se::Renderable { class FPSCounterEvent : public se::Renderable {
public: public:
double last_frame_time_input; double last_frame_time_input;
int frame_count_input; int frame_count_input = 0;
double last_frame_time_tps; double last_frame_time_tps;
int frame_count_tps; int frame_count_tps = 0;
double last_frame_time_render; double last_frame_time_render;
int frame_count_render; int frame_count_render = 0;
FPSCounterEvent() : se::Renderable() { FPSCounterEvent() : se::Renderable(), last_frame_time_input(glfwGetTime()),
last_frame_time_input = glfwGetTime(); last_frame_time_tps(glfwGetTime()), last_frame_time_render(glfwGetTime()) {
frame_count_input = 0;
last_frame_time_tps = glfwGetTime();
frame_count_tps = 0;
last_frame_time_render = glfwGetTime();
frame_count_render = 0;
} }
virtual void update(const float &delta_time) { virtual void update(const float &delta_time) {
@ -364,8 +172,9 @@ int main(int argc, char *argv[]) {
auto fps_counter = std::make_shared<FPSCounterEvent>(); auto fps_counter = std::make_shared<FPSCounterEvent>();
game.add_renderable(fps_counter); game.add_renderable(fps_counter);
auto lua_script = std::make_shared<LuaTestScriptEvent>(); auto script_sys = std::make_shared<simpleengine::scripting::ScriptingSystem>(registry);
game.add_renderable(lua_script); script_sys->add_lua_engine();
game.add_renderable(script_sys);
/* game.set_enable_vsync(false); /* game.set_enable_vsync(false);
game.set_fps_limit(100); */ game.set_fps_limit(100); */

View File

@ -125,10 +125,16 @@ namespace simpleengine::ecs {
return rotation_matrix(degrees, glm::vec3(0, 0, 1)); return rotation_matrix(degrees, glm::vec3(0, 0, 1));
} }
virtual void rotate(float degrees, glm::vec3 rotation_axis) { virtual void rotate_axis(float degrees, glm::vec3 rotation_axis) {
transform_matrix = rotation_matrix(degrees, rotation_axis); transform_matrix = rotation_matrix(degrees, rotation_axis);
} }
virtual void rotate(float x_degrees, float y_degrees, float z_degrees) {
transform_matrix = rotation_x_matrix(x_degrees);
transform_matrix = rotation_y_matrix(y_degrees);
transform_matrix = rotation_z_matrix(z_degrees);
}
virtual void rotate_x(float degrees) { virtual void rotate_x(float degrees) {
transform_matrix = rotation_x_matrix(degrees); transform_matrix = rotation_x_matrix(degrees);
} }

View File

@ -1,151 +0,0 @@
#pragma once
#include "entt_meta_helper.h"
#include <entt/entity/fwd.hpp>
#include <entt/entity/registry.hpp>
#include <entt/entity/runtime_view.hpp>
#include <set>
#include <sol/sol.hpp>
template <typename Component> auto is_valid(const entt::registry *registry, entt::entity entity) {
assert(registry);
return registry->valid(entity);
}
template <typename Component>
auto emplace_component(entt::registry *registry, entt::entity entity, const sol::table& instance, sol::this_state s) {
assert(registry);
auto& comp =
registry->emplace_or_replace<Component>(entity, instance.valid() ? instance.as<Component>() : Component{});
return sol::make_reference(s, std::ref(comp));
}
template <typename Component> auto get_component(entt::registry *registry, entt::entity entity, sol::this_state s) {
assert(registry);
auto& comp = registry->get_or_emplace<Component>(entity);
return sol::make_reference(s, std::ref(comp));
}
template <typename Component> bool has_component(entt::registry *registry, entt::entity entity) {
assert(registry);
return registry->any_of<Component>(entity);
}
template <typename Component> auto remove_component(entt::registry *registry, entt::entity entity) {
assert(registry);
return registry->remove<Component>(entity);
}
template <typename Component> void clear_component(entt::registry *registry) {
assert(registry);
registry->clear<Component>();
}
template <typename Component> void register_meta_component() {
using namespace entt::literals;
entt::meta<Component>()
.type()
.template func<&is_valid<Component>>("valid"_hs)
.template func<&emplace_component<Component>>("emplace"_hs)
.template func<&get_component<Component>>("get"_hs)
.template func<&has_component<Component>>("has"_hs)
.template func<&clear_component<Component>>("clear"_hs)
.template func<&remove_component<Component>>("remove"_hs);
}
auto collect_types(const sol::variadic_args& va) {
std::set<entt::id_type> types;
std::transform(va.cbegin(), va.cend(), std::inserter(types, types.begin()),
[](const auto& obj) { return deduce_type(obj); });
return types;
}
sol::table open_registry(sol::this_state s) {
// To create a registry inside a script: entt.registry.new()
sol::state_view lua{s};
auto entt_module = lua["entt"].get_or_create<sol::table>();
entt_module.new_usertype<entt::runtime_view>("runtime_view", sol::no_constructor,
"size_hint", &entt::runtime_view::size_hint, "contains",
&entt::runtime_view::contains, "each",
[](const entt::runtime_view& self, const sol::function& callback) {
if (!callback.valid()) {
return;
}
for (auto entity : self) {
callback(entity);
}
});
using namespace entt::literals;
entt_module.new_usertype<entt::registry>(
"registry", sol::meta_function::construct, sol::factories([] { return entt::registry{}; }),
"size", &entt::registry::size,
"alive", &entt::registry::alive,
"valid", &entt::registry::valid, "current", &entt::registry::current,
"create", [](entt::registry& self) { return self.create(); },
"destroy", [](entt::registry& self, entt::entity entity) { return self.destroy(entity); },
"emplace",
[](entt::registry& self, entt::entity entity, const sol::table& comp, sol::this_state s) -> sol::object {
if (!comp.valid())
return sol::lua_nil_t{};
const auto maybe_any = invoke_meta_func(get_type_id(comp), "emplace"_hs, &self, entity, comp, s);
return maybe_any ? maybe_any.cast<sol::reference>() : sol::lua_nil_t{};
},
"remove",
[](entt::registry& self, entt::entity entity, const sol::object& type_or_id) {
const auto maybe_any = invoke_meta_func(deduce_type(type_or_id), "remove"_hs, &self, entity);
return maybe_any ? maybe_any.cast<size_t>() : 0;
},
"has",
[](entt::registry& self, entt::entity entity, const sol::object& type_or_id) {
const auto maybe_any = invoke_meta_func(deduce_type(type_or_id), "has"_hs, &self, entity);
return maybe_any ? maybe_any.cast<bool>() : false;
},
"any_of",
[](const sol::table& self, entt::entity entity, const sol::variadic_args& va) {
const auto types = collect_types(va);
const auto has = self["has"].get<sol::function>();
return std::any_of(types.cbegin(), types.cend(),
[&](auto type_id) { return has(self, entity, type_id).template get<bool>(); });
},
"get",
[](entt::registry& self, entt::entity entity, const sol::object& type_or_id, sol::this_state s) {
const auto maybe_any = invoke_meta_func(deduce_type(type_or_id), "get"_hs, &self, entity, s);
return maybe_any ? maybe_any.cast<sol::reference>() : sol::lua_nil_t{};
},
"clear",
sol::overload(&entt::registry::clear<>, [](entt::registry& self, sol::object type_or_id) {
invoke_meta_func(deduce_type(type_or_id), "clear"_hs, &self);
}),
"orphan", &entt::registry::orphan,
"runtime_view",
[](entt::registry& self, const sol::variadic_args& va) {
const std::set<uint32_t> types = collect_types(va);
entt::runtime_view view{};
for (const auto& type : types) {
entt::sparse_set& storage = self.storage(type)->second;
view.iterate(storage);
}
return view;
}
);
return entt_module;
}

View File

@ -5,42 +5,48 @@
#include <sol/sol.hpp> #include <sol/sol.hpp>
[[nodiscard]] entt::id_type get_type_id(const sol::table &obj) { namespace simpleengine::scripting {
const auto f = obj["type_id"].get<sol::function>(); class EnttMetaHelper {
assert(f.valid() && "type_id not exposed to lua!"); public:
return f.valid() ? f().get<entt::id_type>() : -1;
}
template <typename T> [[nodiscard]] entt::id_type deduce_type(T &&obj) { [[nodiscard]] static entt::id_type get_type_id(const sol::table &obj) {
switch (obj.get_type()) { const auto f = obj["type_id"].get<sol::function>();
// in lua: registry:has(e, Transform.type_id()) assert(f.valid() && "type_id not exposed to lua!");
case sol::type::number: return f.valid() ? f().get<entt::id_type>() : -1;
return obj.template as<entt::id_type>(); }
// in lua: registry:has(e, Transform)
case sol::type::table:
return get_type_id(obj);
default:
// TODO: Dont assert here. Maybe an exception could be thrown and caught by the ScriptingSystem?
assert(false);
return -1;
}
}
// @see template <typename T> [[nodiscard]] static entt::id_type deduce_type(T &&obj) {
// https://github.com/skypjack/entt/wiki/Crash-Course:-runtime-reflection-system switch (obj.get_type()) {
// in lua: registry:has(e, Transform.type_id())
case sol::type::number:
return obj.template as<entt::id_type>();
// in lua: registry:has(e, Transform)
case sol::type::table:
return get_type_id(obj);
default:
// TODO: Dont assert here. Maybe an exception could be thrown and caught by the ScriptingSystem?
assert(false);
return -1;
}
}
template <typename... Args> // @see
inline auto invoke_meta_func(entt::meta_type meta_type, entt::id_type function_id, Args &&...args) { // https://github.com/skypjack/entt/wiki/Crash-Course:-runtime-reflection-system
if (!meta_type) {
// TODO: Warning message
} else {
if (auto meta_function = meta_type.func(function_id); meta_function)
return meta_function.invoke({}, std::forward<Args>(args)...);
}
return entt::meta_any{};
}
template <typename... Args> template <typename... Args>
inline auto invoke_meta_func(entt::id_type type_id, entt::id_type function_id, Args &&...args) { inline static auto invoke_meta_func(entt::meta_type meta_type, entt::id_type function_id, Args &&...args) {
return invoke_meta_func(entt::resolve(type_id), function_id, std::forward<Args>(args)...); if (!meta_type) {
// TODO: Warning message
} else {
if (auto meta_function = meta_type.func(function_id); meta_function)
return meta_function.invoke({}, std::forward<Args>(args)...);
}
return entt::meta_any{};
}
template <typename... Args>
inline static auto invoke_meta_func(entt::id_type type_id, entt::id_type function_id, Args &&...args) {
return invoke_meta_func(entt::resolve(type_id), function_id, std::forward<Args>(args)...);
}
};
} }

View File

@ -0,0 +1,82 @@
#pragma once
#include "../entt_meta_helper.h"
#include <entt/entity/fwd.hpp>
#include <entt/entity/registry.hpp>
#include <entt/entity/runtime_view.hpp>
#include <set>
#include <sol/sol.hpp>
#include <sol/state_view.hpp>
#include <sol/types.hpp>
namespace simpleengine::scripting::lua {
class ECSBindings {
private:
template <typename Component> static auto is_valid(const entt::registry *registry, entt::entity entity) {
assert(registry);
return registry->valid(entity);
}
template <typename Component>
static auto emplace_component(entt::registry *registry, entt::entity entity, const sol::table& instance, sol::this_state s) {
assert(registry);
auto& comp =
registry->emplace_or_replace<Component>(entity, instance.valid() ? instance.as<Component>() : Component{});
return sol::make_reference(s, std::ref(comp));
}
template <typename Component> static auto get_component(entt::registry *registry, entt::entity entity, sol::this_state s) {
assert(registry);
auto& comp = registry->get_or_emplace<Component>(entity);
return sol::make_reference(s, std::ref(comp));
}
template <typename Component> static bool has_component(entt::registry *registry, entt::entity entity) {
assert(registry);
return registry->any_of<Component>(entity);
}
template <typename Component> static auto remove_component(entt::registry *registry, entt::entity entity) {
assert(registry);
return registry->remove<Component>(entity);
}
template <typename Component> static void clear_component(entt::registry *registry) {
assert(registry);
registry->clear<Component>();
}
template <typename Component> static void register_meta_component() {
using namespace entt::literals;
entt::meta<Component>()
.type()
.template func<&is_valid<Component>>("valid"_hs)
.template func<&emplace_component<Component>>("emplace"_hs)
.template func<&get_component<Component>>("get"_hs)
.template func<&has_component<Component>>("has"_hs)
.template func<&clear_component<Component>>("clear"_hs)
.template func<&remove_component<Component>>("remove"_hs);
}
static auto collect_types(const sol::variadic_args& va) {
std::set<entt::id_type> types;
std::transform(va.cbegin(), va.cend(), std::inserter(types, types.begin()),
[](const auto& obj) { return EnttMetaHelper::deduce_type(obj); });
return types;
}
public:
/// This binds ALL of the ECS to Lua under the `ecs` table
static sol::table bind_full_ecs(sol::this_state s);
/// This binds an entt registry to lua. It binds the registry type as `ecs.registry`
static sol::table bind_registry(sol::this_state s);
/// Bind all components to Lua. This will put all components under `ecs.component.*`
static sol::table bind_components(sol::this_state s);
};
}

View File

@ -0,0 +1,36 @@
#pragma once
#include "../scripting_engine.h"
#include <string>
#include <sol/sol.hpp>
namespace simpleengine::ecs {
class Registry;
}
namespace simpleengine::scripting::lua {
class LuaScriptingEngine : public ScriptingEngine {
private:
sol::state lua;
std::vector<sol::protected_function_result> script_results;
public:
LuaScriptingEngine(std::shared_ptr<ecs::Registry> entity_registry);
static void lua_panic_handler(sol::optional<std::string> maybe_msg);
static int lua_exception_handler(lua_State* L, sol::optional<const std::exception&> maybe_exception, sol::string_view description);
static sol::protected_function_result lua_protected_function_handler(lua_State* lstate, sol::protected_function_result result);
virtual void setup_language() override;
virtual void expose_simpleengine() override;
// TODO: Add some way for the user to get a reference to a script so they can stop it from running
virtual void run_script(std::string lua_code) override;
virtual void run_script_file(std::string path) override;
virtual void update(const float& delta_time) override;
virtual void input_update(const float& delta_time) override;
virtual void render(const float& interpolate_alpha, const float& frame_time) override;
virtual void destroy() override;
};
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <memory>
namespace simpleengine::ecs {
class Registry;
}
namespace simpleengine::scripting {
class ScriptingEngine {
protected:
std::shared_ptr<ecs::Registry> entity_registry;
public:
ScriptingEngine(std::shared_ptr<ecs::Registry> entity_registry) : entity_registry(entity_registry) {
}
/// Setup the scripting language VM
virtual void setup_language() = 0;
/// Expose simpleengine to the scripting language
virtual void expose_simpleengine() = 0;
virtual void run_script(std::string lua_code) = 0;
virtual void run_script_file(std::string path) = 0;
virtual void update(const float& delta_time) = 0;
virtual void input_update(const float& delta_time) = 0;
virtual void render(const float& interpolate_alpha, const float& frame_time) = 0;
virtual void destroy() = 0;
};
}

View File

@ -0,0 +1,21 @@
#pragma once
#include "../ecs/system/system.h"
#include "scripting_engine.h"
#include <memory>
namespace simpleengine::scripting {
class ScriptingSystem : public simpleengine::ecs::system::System {
private:
std::unordered_map<const char*, std::unique_ptr<simpleengine::scripting::ScriptingEngine>> scripting_engines;
public:
ScriptingSystem(std::shared_ptr<simpleengine::ecs::Registry> entity_registry);
void add_lua_engine();
virtual void update(const float& delta_time) override;
virtual void input_update(const float& delta_time) override;
virtual void render(const float& interpolate_alpha, const float& frame_time) override;
virtual void destroy() override;
};
}

View File

@ -0,0 +1,167 @@
#include "scripting/lua/ecs_bindings.h"
#include "ecs/component/transform_component.h"
#include "scripting/entt_meta_helper.h"
#include <glm/detail/type_quat.hpp>
#include <glm/fwd.hpp>
#include <glm/gtx/string_cast.hpp>
#include <sol/overload.hpp>
#include <tuple>
namespace simpleengine::scripting::lua {
sol::table ECSBindings::bind_full_ecs(sol::this_state s) {
sol::table ecs_table = bind_registry(s);
bind_components(s);
return ecs_table;
}
sol::table ECSBindings::bind_registry(sol::this_state s) {
sol::state_view lua{s};
auto ecs_module = lua["ecs"].get_or_create<sol::table>();
ecs_module.new_usertype<entt::runtime_view>("runtime_view", sol::no_constructor,
"size_hint", &entt::runtime_view::size_hint, "contains",
&entt::runtime_view::contains, "each",
[](const entt::runtime_view& self, const sol::function& callback) {
if (!callback.valid()) {
return;
}
for (auto entity : self) {
callback(entity);
}
});
using namespace entt::literals;
ecs_module.new_usertype<entt::registry>(
"registry", sol::meta_function::construct, sol::factories([] { return entt::registry{}; }),
"size", &entt::registry::size,
"alive", &entt::registry::alive,
"valid", &entt::registry::valid, "current", &entt::registry::current,
"create", [](entt::registry& self) { return self.create(); },
"destroy", [](entt::registry& self, entt::entity entity) { return self.destroy(entity); },
"emplace",
[](entt::registry& self, entt::entity entity, const sol::table& comp, sol::this_state s) -> sol::object {
if (!comp.valid())
return sol::lua_nil_t{};
const auto maybe_any = EnttMetaHelper::invoke_meta_func(EnttMetaHelper::get_type_id(comp), "emplace"_hs, &self, entity, comp, s);
return maybe_any ? maybe_any.cast<sol::reference>() : sol::lua_nil_t{};
},
"remove",
[](entt::registry& self, entt::entity entity, const sol::object& type_or_id) {
const auto maybe_any = EnttMetaHelper::invoke_meta_func(EnttMetaHelper::deduce_type(type_or_id), "remove"_hs, &self, entity);
return maybe_any ? maybe_any.cast<size_t>() : 0;
},
"has",
[](entt::registry& self, entt::entity entity, const sol::object& type_or_id) {
const auto maybe_any = EnttMetaHelper::invoke_meta_func(EnttMetaHelper::deduce_type(type_or_id), "has"_hs, &self, entity);
return maybe_any ? maybe_any.cast<bool>() : false;
},
"any_of",
[](const sol::table& self, entt::entity entity, const sol::variadic_args& va) {
const auto types = collect_types(va);
const auto has = self["has"].get<sol::function>();
return std::any_of(types.cbegin(), types.cend(),
[&](auto type_id) { return has(self, entity, type_id).template get<bool>(); });
},
"get",
[](entt::registry& self, entt::entity entity, const sol::object& type_or_id, sol::this_state s) {
const auto maybe_any = EnttMetaHelper::invoke_meta_func(EnttMetaHelper::deduce_type(type_or_id), "get"_hs, &self, entity, s);
return maybe_any ? maybe_any.cast<sol::reference>() : sol::lua_nil_t{};
},
"clear",
sol::overload(&entt::registry::clear<>, [](entt::registry& self, sol::object type_or_id) {
EnttMetaHelper::invoke_meta_func(EnttMetaHelper::deduce_type(type_or_id), "clear"_hs, &self);
}),
"orphan", &entt::registry::orphan,
"runtime_view",
[](entt::registry& self, const sol::variadic_args& va) {
const std::set<uint32_t> types = collect_types(va);
entt::runtime_view view{};
for (const auto& type : types) {
entt::sparse_set& storage = self.storage(type)->second;
view.iterate(storage);
}
return view;
}
);
return ecs_module;
}
sol::table ECSBindings::bind_components(sol::this_state s) {
sol::state_view lua{s};
auto ecs_module = lua["ecs"].get_or_create<sol::table>();
auto comp_module = ecs_module["component"].get_or_create<sol::table>();
// TransformComponent
register_meta_component<ecs::TransformComponent>();
comp_module.new_usertype<simpleengine::ecs::TransformComponent>("TransformComponent",
"type_id", &entt::type_hash<simpleengine::ecs::TransformComponent>::value,
sol::call_constructor, sol::factories(
[](float px, float py, float pz) {
return simpleengine::ecs::TransformComponent(glm::vec3(px, py, pz));
},
[](float px, float py, float pz, float rx, float ry, float rz) {
return simpleengine::ecs::TransformComponent(glm::vec3(px, py, pz), glm::vec3(rx, ry, rz));
},
[](float px, float py, float pz, float rx, float ry, float rz, float sx, float sy, float sz) {
return simpleengine::ecs::TransformComponent(glm::vec3(px, py, pz), glm::vec3(rx, ry, rz), glm::vec3(sx, sy, sz));
}
),
"decompose_matrix", [](const ecs::TransformComponent& self) {
glm::vec3 pos, scale;
glm::quat rot;
self.decompose_matrix(pos, rot, scale);
return std::tuple(pos, rot, scale);
},
// TODO: Implement glm::vec3
/* "get_pos", &ecs::TransformComponent::get_pos,
"get_scale", &ecs::TransformComponent::get_scale,
"get_rotation_quat", &ecs::TransformComponent::get_rotation_quat, */
// combine_transform(const glm::mat4& transform_matrix)
// combine_transform(const TransformComponent& transformable)
"translate", sol::overload([](ecs::TransformComponent& self, float x, float y, float z) {
self.translate(x, y, z);
}), // TODO: Implement glm::vec3 translate
"rotate", &ecs::TransformComponent::rotate,
"rotate_x", &ecs::TransformComponent::rotate_x,
"rotate_y", &ecs::TransformComponent::rotate_y,
"rotate_z", &ecs::TransformComponent::rotate_z,
"scale", sol::overload([](ecs::TransformComponent& self, float scalar) {
self.scale(scalar);
}, [](ecs::TransformComponent& self, float x_scalar, float y_scalar, float z_scalar) {
self.scale(glm::vec3(x_scalar, y_scalar, z_scalar));
}), // TODO: Implement glm::vec3 scale
sol::meta_function::to_string, [](const ecs::TransformComponent& self) {
return glm::to_string(self.transform_matrix);
}
);
return comp_module;
}
}

View File

@ -0,0 +1,192 @@
#include "scripting/lua/lua_scripting_engine.h"
#include "scripting/lua/ecs_bindings.h"
#include "scripting/entt_meta_helper.h"
#include "ecs/registry.h"
#include "log/logger.h"
#include "ecs/component/transform_component.h"
#include "scripting/lua/ecs_bindings.h"
#include <iostream>
#include <glm/gtx/string_cast.hpp>
#define AUTO_ARG(x) decltype(x), x
namespace simpleengine::scripting::lua {
LuaScriptingEngine::LuaScriptingEngine(std::shared_ptr<ecs::Registry> entity_registry) : ScriptingEngine(entity_registry) {
}
void LuaScriptingEngine::lua_panic_handler(sol::optional<std::string> maybe_msg) {
std::cerr << "Lua panic: ";
if(maybe_msg) {
std::cerr << maybe_msg->c_str();
}
std::cerr << std::endl;
}
int LuaScriptingEngine::lua_exception_handler(lua_State* L, sol::optional<const std::exception&> maybe_exception, sol::string_view description) {
std::cerr << "Lua Exception: ";
if(maybe_exception) {
const std::exception& ex = *maybe_exception;
std::cerr << ex.what();
} else {
std::cerr.write(description.data(), description.size());
}
std::cerr << std::endl;
return sol::stack::push(L, description);
}
sol::protected_function_result LuaScriptingEngine::lua_protected_function_handler(lua_State* lstate, sol::protected_function_result result) {
std::cerr << "Lua protected function error" << std::endl;
sol::type t = sol::type_of(lstate, result.stack_index());
std::string err = "sol: ";
err += to_string(result.status());
err += " error";
std::exception_ptr eptr = std::current_exception();
if (eptr) {
err += " with a ";
try {
std::rethrow_exception(eptr);
}
catch (const std::exception& ex) {
err += "std::exception -- ";
err.append(ex.what());
}
catch (const std::string& message) {
err += "thrown message -- ";
err.append(message);
}
catch (const char* message) {
err += "thrown message -- ";
err.append(message);
}
catch (...) {
err.append("thrown but unknown type, cannot serialize into error message");
}
}
if (t == sol::type::string) {
err += ": ";
std::string_view serr = sol::stack::unqualified_get<std::string_view>(lstate, result.stack_index());
err.append(serr.data(), serr.size());
}
SE_ERROR("Lua ScriptingEngine", "{}", err);
return result;
}
void LuaScriptingEngine::setup_language() {
std::cout << "Setup" << std::endl;
lua = sol::state{sol::c_call<decltype(&LuaScriptingEngine::lua_panic_handler),
&LuaScriptingEngine::lua_panic_handler>};
lua.set_exception_handler(&LuaScriptingEngine::lua_exception_handler);
// Open all libraries built into sol2. There is ffi and jit, but these are only available
// when using LuaJIT.
lua.open_libraries(sol::lib::base, sol::lib::package, sol::lib::coroutine,
sol::lib::string, sol::lib::os, sol::lib::math, sol::lib::table, sol::lib::debug,
sol::lib::bit32, sol::lib::io, sol::lib::utf8);
//lua.require("registry", sol::c_call<AUTO_ARG(&simpleengine::scripting::lua::bindings::bind_registry)>, false); // create registry type
// Overwrite the Lua print function to use a logger
lua.globals().set_function("print", [](std::string msg) {
SE_DEBUG("Lua ScriptingEngine", "[Lua Output] {}", msg);
});
}
void LuaScriptingEngine::expose_simpleengine() {
// Make registry available to lua
lua["registry"] = std::ref(entity_registry->get_inner());
// Registry TransformComponent
//bindings::bind_registry();
lua.require("ecs", sol::c_call<AUTO_ARG(&simpleengine::scripting::lua::ECSBindings::bind_full_ecs)>, false); // create registry type
//lua.require("registry", sol::c_call<AUTO_ARG(&simpleengine::scripting::lua::bindings::bind_registry)>, false); // create registry type
entt::entity en = entity_registry->get_inner().create();
lua["dog"] = en;
run_script(R"LUA(
print('start')
--local dog = registry:create()
local cat = registry:create()
print('created dog and cat')
print('Dog is ' .. dog .. ', and registry size is ' .. registry:size())
print('Cat is ' .. cat .. ', and cat size is ' .. registry:size())
registry:emplace(dog, ecs.component.TransformComponent(5, 6, 3))
assert(registry:has(dog, ecs.component.TransformComponent))
assert(registry:has(dog, ecs.component.TransformComponent.type_id()))
assert(not registry:any_of(dog, -1, -2, -3))
function update(delta_time)
transform = registry:get(dog, ecs.component.TransformComponent)
print('Dog position = ' .. tostring(transform))
transform:translate(0, 0, 1)
end
print('Lua script loaded!')
)LUA");
}
void LuaScriptingEngine::run_script(std::string lua_code) {
script_results.emplace_back(lua.script(lua_code, &LuaScriptingEngine::lua_protected_function_handler));
}
void LuaScriptingEngine::run_script_file(std::string path) {
script_results.emplace_back(lua.script_file(path, &LuaScriptingEngine::lua_protected_function_handler));
}
void LuaScriptingEngine::update(const float& delta_time) {
for (const auto& res : script_results) {
if (res.valid()) {
sol::function lua_update = lua["update"];
if (lua_update.valid()) {
lua_update(delta_time);
}
}
}
}
void LuaScriptingEngine::input_update(const float& delta_time) {
for (const auto& res : script_results) {
if (res.valid()) {
sol::function lua_input_update = lua["input_update"];
if (lua_input_update.valid()) {
lua_input_update(delta_time);
}
}
}
}
void LuaScriptingEngine::render(const float& interpolate_alpha, const float& frame_time) {
for (const auto& res : script_results) {
if (res.valid()) {
sol::function lua_render = lua["render"];
if (lua_render.valid()) {
lua_render(interpolate_alpha, frame_time);
}
}
}
}
void LuaScriptingEngine::destroy() {
std::cout << "Destroy" << std::endl;
}
}

View File

@ -0,0 +1,42 @@
#include "scripting/scripting_system.h"
#include "ecs/system/system.h"
#include "scripting/lua/lua_scripting_engine.h"
namespace simpleengine::scripting {
ScriptingSystem::ScriptingSystem(std::shared_ptr<simpleengine::ecs::Registry> entity_registry) : simpleengine::ecs::system::System(entity_registry) {
}
void ScriptingSystem::add_lua_engine() {
auto lua = std::make_unique<lua::LuaScriptingEngine>(entity_registry);
lua->setup_language();
lua->expose_simpleengine();
scripting_engines.emplace("lua", std::move(lua));
}
void ScriptingSystem::update(const float& delta_time) {
for (const auto& [key, val] : scripting_engines) {
val->update(delta_time);
}
}
void ScriptingSystem::input_update(const float& delta_time) {
for (const auto& [key, val] : scripting_engines) {
val->input_update(delta_time);
}
}
void ScriptingSystem::render(const float& interpolate_alpha, const float& frame_time) {
for (const auto& [key, val] : scripting_engines) {
val->render(interpolate_alpha, frame_time);
}
}
void ScriptingSystem::destroy() {
std::cout << "Scripting system destroyed!" << std::endl;
}
}