diff --git a/CMake/Getsol2.cmake b/CMake/Getsol2.cmake new file mode 100644 index 0000000..0a27127 --- /dev/null +++ b/CMake/Getsol2.cmake @@ -0,0 +1,14 @@ +# Get the sol2 header libraries. +include(FetchContent) +message("Downloading sol2 library...") +set(SOL2_ENABLE_INSTALL OFF) +set(SOL2_BUILD_LUA FALSE) +set(SOL2_LUA_VERSION "5.2") +FetchContent_Declare( + sol2 + GIT_REPOSITORY https://github.com/ThePhD/sol2.git + GIT_TAG v3.3.0 +) +FetchContent_MakeAvailable(sol2) +message("Downloaded sol2 library to: ${sol2_SOURCE_DIR}") +set(sol2_INCLUDE_DIR ${sol2_SOURCE_DIR}/src) \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index cfe1c96..3c18372 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ include(CMake/GetEnTT.cmake) # Add some CMake options: option(SIMPLE_ENGINE_BUILD_EXAMPLES "Build example projects" ON) +option(SIMPLE_ENGINE_DOWNLOAD_SOL2 "Download sol2 from github. I fits off, SimpleEngine will try to find it in the system." ON) # By default use OpenGL GLVND option(SIMPLE_ENGINE_USE_GL_LEGACY "Use OpenGL legacy, or use BLVND" OFF) @@ -17,6 +18,16 @@ else() set(OpenGL_GL_PREFERENCE "GLVND") endif() +# Download sol2 or search the system. We also search for lua since we have to include it for the linker +find_package(Lua 5.4 REQUIRED) +if (SIMPLE_ENGINE_DOWNLOAD_SOL2) + set(SOL2_BUILD_LUA FALSE) + set(SOL2_LUA_VERSION 5.4.4) + include(CMake/Getsol2.cmake) +else() + find_package(sol2 REQUIRED) +endif() + find_package(GLEW REQUIRED) find_package(glfw3 CONFIG REQUIRED) find_package(glm CONFIG REQUIRED) @@ -25,6 +36,7 @@ find_package(fmt CONFIG REQUIRED) find_package(spdlog CONFIG REQUIRED) find_package(assimp CONFIG REQUIRED) find_package(Bullet REQUIRED) +find_package(RTTR CONFIG REQUIRED Core) # Link sources file(GLOB_RECURSE source_list src/*.cpp) @@ -51,6 +63,9 @@ target_link_libraries(simpleengine PUBLIC glfw) target_link_libraries(simpleengine PUBLIC ${GLM_LIBRARIES}) target_link_libraries(simpleengine PUBLIC ${OPENGL_LIBRARIES}) target_link_libraries(simpleengine PUBLIC ${BULLET_LIBRARIES}) +target_link_libraries(simpleengine PUBLIC ${LUA_LIBRARIES}) +target_link_libraries(simpleengine PUBLIC RTTR::Core) +target_link_libraries(simpleengine PUBLIC sol2::sol2) if(WIN32) target_link_libraries(simpleengine PUBLIC assimp::assimp) target_link_libraries(simpleengine PUBLIC fmt::fmt) diff --git a/examples/dev_testing/CMakeLists.txt b/examples/dev_testing/CMakeLists.txt index 6253a89..bc5c403 100644 --- a/examples/dev_testing/CMakeLists.txt +++ b/examples/dev_testing/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required (VERSION 3.6) project(DevTesting DESCRIPTION "A testing project for engine developers.") +set(CMAKE_CXX_FLAGS "-Og") + add_executable(dev_testing src/main.cpp) # Link headers and source files. diff --git a/examples/dev_testing/src/main.cpp b/examples/dev_testing/src/main.cpp index 0baf8a0..aea3b40 100644 --- a/examples/dev_testing/src/main.cpp +++ b/examples/dev_testing/src/main.cpp @@ -12,6 +12,8 @@ #include "simpleengine/gfx/renderer.h" #include "simpleengine/gfx/texture.h" #include "simpleengine/vector.h" +#include +#include #include #include #include @@ -27,6 +29,9 @@ #include #include +#include +#include + #include "entt/entity/fwd.hpp" #include @@ -38,6 +43,8 @@ #include #include #include +#include +#include #include #include #include @@ -46,6 +53,189 @@ 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 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 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(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), registry({})/* , script_res(std::nullopt_t) */ { + register_meta_component(); + + lua.open_libraries(sol::lib::base, sol::lib::package, sol::lib::string, sol::lib::debug); + lua.require("registry", sol::c_call, 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("TransformComponent", + "type_id", &entt::type_hash::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; @@ -151,7 +341,8 @@ int main(int argc, char *argv[]) { transform_comp.translate(4.f, 0.f, 0.f); */ se::ecs::Entity brick_e = scene->create_entity(); - brick_e.add_component("examples/dev_testing/resources/bricks/bricks.fbx"); + //brick_e.add_component("examples/dev_testing/resources/bricks/bricks.fbx"); + registry->get_inner().emplace(brick_e.get_inner(), "examples/dev_testing/resources/bricks/bricks.fbx"); brick_e.add_component(glm::vec3(6.f, 6.f, 0.f)); brick_e.add_component(1.f, 1.f, 1.f); brick_e.add_component(1.f, se::Vectorf(6.f, 6.f, 0.1f)); @@ -162,13 +353,20 @@ int main(int argc, char *argv[]) { floor.add_component(1.f, 1.f, 1.f); floor.add_component(0.f, se::Vectorf(6.f, -6.f, 0.f)); + + // Add TransformComponent using Lua + //register_met + + auto light = std::make_shared(core_shader, glm::vec3(0.f, 0.f, 0.f), glm::vec3(1.f, 1.f, 1.f)); game.add_event(light); - // TODO: Fix, for some reason it crashes auto fps_counter = std::make_shared(); game.add_renderable(fps_counter); + auto lua_script = std::make_shared(); + game.add_renderable(lua_script); + /* game.set_enable_vsync(false); game.set_fps_limit(100); */ int res = game.run(); diff --git a/include/simpleengine/ecs/entity.h b/include/simpleengine/ecs/entity.h index 8cc2675..d15554c 100644 --- a/include/simpleengine/ecs/entity.h +++ b/include/simpleengine/ecs/entity.h @@ -1,12 +1,16 @@ #pragma once -#include "entt/entity/fwd.hpp" +#include #include #include +#include #include +#include + namespace simpleengine::ecs { class Entity { + RTTR_ENABLE() private: entt::registry& registry; entt::entity inner; @@ -19,6 +23,10 @@ namespace simpleengine::ecs { } + entt::entity& get_inner() { + return inner; + } + /** * @brief Checks if an identifier refers to a valid entity. * @@ -114,7 +122,12 @@ namespace simpleengine::ecs { */ template decltype(auto) add_component(Args&&... args) { - return registry.emplace(inner, std::forward(args)...); + return registry.emplace(inner, std::forward(args)...); + } + + template + Component& add_component_copy(Component comp) { + return registry.emplace(inner, comp); } /** @@ -135,7 +148,7 @@ namespace simpleengine::ecs { */ template decltype(auto) add_or_replace_component(Args&&... args) { - return registry.emplace_or_replace(inner, std::forward(args)...); + return registry.emplace_or_replace(inner, std::forward(args)...); } /** @@ -156,7 +169,7 @@ namespace simpleengine::ecs { */ template decltype(auto) replace_component(Args&&... args) { - return registry.replace(inner, std::forward(args)...); + return registry.replace(inner, std::forward(args)...); } /** @@ -263,4 +276,40 @@ namespace simpleengine::ecs { return registry.orphan(inner); } }; +} + +RTTR_REGISTRATION { + using simpleengine::ecs::Entity; + rttr::registration::class_("Entity") + .constructor() + .method("is_valid", &Entity::is_valid) + .method("current_version", &Entity::current_version) + + // TODO: This version_type needs to be changed to something the scripting system could access + .method("release", static_cast(&Entity::release)) + .method("release", static_cast(&Entity::release)) + .method("destroy", static_cast(&Entity::destroy)) + .method("destroy", static_cast(&Entity::destroy)) + + // TODO: implement + //.method("add_component", &Entity::add_component) + //.method("add_component", &Entity::add_component_copy) + //.method("add_or_replace_component", &Entity::add_or_replace_component) + //.method("replace_component", &Entity::replace_component) + //.method("patch", &Entity::patch) + //.method("remove_components", &Entity::remove_components) + //.method("erase", &Entity::erase) + //.method("has_all_of", &Entity::has_all_of) + //.method("has_any_of", &Entity::has_any_of) + + .method("has_any_components", &Entity::has_any_components) + + ; + + /* rttr::registration::class_("Sprite") + .constructor() + .method("Move", &Sprite::move) + .method("Draw", &Sprite::draw) + .property("x", &Sprite::x) + .property("y", &Sprite::y); */ } \ No newline at end of file diff --git a/include/simpleengine/ecs/registry.h b/include/simpleengine/ecs/registry.h index 0399d63..2a65146 100644 --- a/include/simpleengine/ecs/registry.h +++ b/include/simpleengine/ecs/registry.h @@ -1,12 +1,16 @@ #pragma once -#include "entt/signal/fwd.hpp" +#include #include +#include + namespace simpleengine::ecs { class Entity; class Registry { + RTTR_ENABLE() + entt::registry inner; //entt::dispatcher dispatcher; public: diff --git a/include/simpleengine/ecs/system/scene_system.h b/include/simpleengine/ecs/system/scene_system.h index a49279f..9b0cd4f 100644 --- a/include/simpleengine/ecs/system/scene_system.h +++ b/include/simpleengine/ecs/system/scene_system.h @@ -2,6 +2,8 @@ #include "system.h" +#include + namespace simpleengine { // fwd decl class Camera; @@ -15,6 +17,7 @@ namespace simpleengine { namespace system { class SceneSystem : public System { + RTTR_ENABLE() protected: std::shared_ptr renderer; std::shared_ptr camera; diff --git a/include/simpleengine/ecs/system/system.h b/include/simpleengine/ecs/system/system.h index 5f0e392..7897e26 100644 --- a/include/simpleengine/ecs/system/system.h +++ b/include/simpleengine/ecs/system/system.h @@ -2,12 +2,15 @@ #include "../../renderable.h" +#include + namespace simpleengine::ecs { class Entity; class Registry; namespace system { class System : public simpleengine::Renderable { + RTTR_ENABLE() protected: std::shared_ptr entity_registry; public: diff --git a/include/simpleengine/scripting/ecs_bindings.h b/include/simpleengine/scripting/ecs_bindings.h new file mode 100644 index 0000000..a85886d --- /dev/null +++ b/include/simpleengine/scripting/ecs_bindings.h @@ -0,0 +1,151 @@ +#pragma once + +#include "entt_meta_helper.h" + +#include +#include +#include + +#include + +#include + +template auto is_valid(const entt::registry *registry, entt::entity entity) { + assert(registry); + return registry->valid(entity); +} +template +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(entity, instance.valid() ? instance.as() : Component{}); + return sol::make_reference(s, std::ref(comp)); +} +template auto get_component(entt::registry *registry, entt::entity entity, sol::this_state s) { + assert(registry); + auto& comp = registry->get_or_emplace(entity); + return sol::make_reference(s, std::ref(comp)); +} +template bool has_component(entt::registry *registry, entt::entity entity) { + assert(registry); + return registry->any_of(entity); +} +template auto remove_component(entt::registry *registry, entt::entity entity) { + assert(registry); + return registry->remove(entity); +} +template void clear_component(entt::registry *registry) { + assert(registry); + registry->clear(); +} + +template void register_meta_component() { + using namespace entt::literals; + + entt::meta() + .type() + .template func<&is_valid>("valid"_hs) + .template func<&emplace_component>("emplace"_hs) + .template func<&get_component>("get"_hs) + .template func<&has_component>("has"_hs) + .template func<&clear_component>("clear"_hs) + .template func<&remove_component>("remove"_hs); +} + +auto collect_types(const sol::variadic_args& va) { + std::set 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(); + + entt_module.new_usertype("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( + "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::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() : 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() : 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(); + return std::any_of(types.cbegin(), types.cend(), + [&](auto type_id) { return has(self, entity, type_id).template get(); }); + }, + + "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::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 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; +} \ No newline at end of file diff --git a/include/simpleengine/scripting/entt_meta_helper.h b/include/simpleengine/scripting/entt_meta_helper.h new file mode 100644 index 0000000..92b2e67 --- /dev/null +++ b/include/simpleengine/scripting/entt_meta_helper.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#include + +[[nodiscard]] entt::id_type get_type_id(const sol::table &obj) { + const auto f = obj["type_id"].get(); + assert(f.valid() && "type_id not exposed to lua!"); + return f.valid() ? f().get() : -1; +} + +template [[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(); + // 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 + +template +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)...); + } + return entt::meta_any{}; +} + +template +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)...); +} \ No newline at end of file diff --git a/shell.nix b/shell.nix index 0246176..815252c 100644 --- a/shell.nix +++ b/shell.nix @@ -14,5 +14,8 @@ pkgs.mkShell { assimp spdlog bullet + rttr + lua5_4 + (callPackage ./sol2.nix { }) ]; } \ No newline at end of file diff --git a/sol2.nix b/sol2.nix new file mode 100644 index 0000000..ac80071 --- /dev/null +++ b/sol2.nix @@ -0,0 +1,45 @@ +{ lib +, stdenv +, fetchFromGitHub +, cmake +, clang +, llvm +, pkgs +}: + +let + stdenv = pkgs.clangStdenv; +in stdenv.mkDerivation rec { + version = "v3.3.0"; + pname = "sol2"; + + src = fetchFromGitHub { + owner = "ThePhD"; + repo = pname; + rev = version; + sha256 = "sha256-NACIWXy1GHuYNf7e3Go+S4PSQqitzgErOvqkmXQVuLw="; + }; + + nativeBuildInputs = [ cmake clang pkgs.lua5_4 ]; + + depsBuildBuild = [ pkgs.lua5_4 ]; + +# stdenv = pkgs.clangStdenv; + cmakeFlags = [ "-DSOL2_BUILD_LUA=FALSE" "-DSOL2_LUA_VERSION='5.4.4'" ]; + + installPhase = '' + runHook preInstall + + cmake --build . --target install --config Release + + runHook postInstall + ''; + + meta = with lib; { + description = "sol2 is a C++ library binding to Lua."; + homepage = "https://github.com/ThePhD/sol2"; + license = licenses.mit; + platforms = platforms.unix; + maintainers = with maintainers; [ seanomik ]; + }; +}