Move all Lua code to separate classes, expose most TransformComponent's methods
This commit is contained in:
parent
51206d60d4
commit
db0cdf32a1
|
@ -11,6 +11,7 @@
|
|||
#include "simpleengine/gfx/model.h"
|
||||
#include "simpleengine/gfx/renderer.h"
|
||||
#include "simpleengine/gfx/texture.h"
|
||||
#include "simpleengine/scripting/scripting_system.h"
|
||||
#include "simpleengine/vector.h"
|
||||
#include <glm/gtx/string_cast.hpp>
|
||||
#include <optional>
|
||||
|
@ -29,9 +30,6 @@
|
|||
#include <simpleengine/log/logger.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 <assimp/material.h>
|
||||
|
@ -47,215 +45,25 @@
|
|||
#include <sol/load_result.hpp>
|
||||
#include <spdlog/common.h>
|
||||
#include <sstream>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <BulletCollision/CollisionShapes/btStaticPlaneShape.h>
|
||||
|
||||
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 {
|
||||
public:
|
||||
double last_frame_time_input;
|
||||
int frame_count_input;
|
||||
int frame_count_input = 0;
|
||||
|
||||
double last_frame_time_tps;
|
||||
int frame_count_tps;
|
||||
int frame_count_tps = 0;
|
||||
|
||||
double last_frame_time_render;
|
||||
int frame_count_render;
|
||||
int frame_count_render = 0;
|
||||
|
||||
FPSCounterEvent() : se::Renderable() {
|
||||
last_frame_time_input = glfwGetTime();
|
||||
frame_count_input = 0;
|
||||
|
||||
last_frame_time_tps = glfwGetTime();
|
||||
frame_count_tps = 0;
|
||||
|
||||
last_frame_time_render = glfwGetTime();
|
||||
frame_count_render = 0;
|
||||
FPSCounterEvent() : se::Renderable(), last_frame_time_input(glfwGetTime()),
|
||||
last_frame_time_tps(glfwGetTime()), last_frame_time_render(glfwGetTime()) {
|
||||
|
||||
}
|
||||
|
||||
virtual void update(const float &delta_time) {
|
||||
|
@ -364,8 +172,9 @@ int main(int argc, char *argv[]) {
|
|||
auto fps_counter = std::make_shared<FPSCounterEvent>();
|
||||
game.add_renderable(fps_counter);
|
||||
|
||||
auto lua_script = std::make_shared<LuaTestScriptEvent>();
|
||||
game.add_renderable(lua_script);
|
||||
auto script_sys = std::make_shared<simpleengine::scripting::ScriptingSystem>(registry);
|
||||
script_sys->add_lua_engine();
|
||||
game.add_renderable(script_sys);
|
||||
|
||||
/* game.set_enable_vsync(false);
|
||||
game.set_fps_limit(100); */
|
||||
|
|
|
@ -125,10 +125,16 @@ namespace simpleengine::ecs {
|
|||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
transform_matrix = rotation_x_matrix(degrees);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -5,42 +5,48 @@
|
|||
|
||||
#include <sol/sol.hpp>
|
||||
|
||||
[[nodiscard]] entt::id_type get_type_id(const sol::table &obj) {
|
||||
const auto f = obj["type_id"].get<sol::function>();
|
||||
assert(f.valid() && "type_id not exposed to lua!");
|
||||
return f.valid() ? f().get<entt::id_type>() : -1;
|
||||
}
|
||||
namespace simpleengine::scripting {
|
||||
class EnttMetaHelper {
|
||||
public:
|
||||
|
||||
[[nodiscard]] static entt::id_type get_type_id(const sol::table &obj) {
|
||||
const auto f = obj["type_id"].get<sol::function>();
|
||||
assert(f.valid() && "type_id not exposed to lua!");
|
||||
return f.valid() ? f().get<entt::id_type>() : -1;
|
||||
}
|
||||
|
||||
template <typename T> [[nodiscard]] entt::id_type deduce_type(T &&obj) {
|
||||
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 T> [[nodiscard]] static entt::id_type deduce_type(T &&obj) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// @see
|
||||
// https://github.com/skypjack/entt/wiki/Crash-Course:-runtime-reflection-system
|
||||
// @see
|
||||
// https://github.com/skypjack/entt/wiki/Crash-Course:-runtime-reflection-system
|
||||
|
||||
template <typename... Args>
|
||||
inline auto invoke_meta_func(entt::meta_type meta_type, entt::id_type function_id, 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::meta_type meta_type, entt::id_type function_id, 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 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)...);
|
||||
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)...);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue