Create a PoC version of the scripting engine

This commit is contained in:
SeanOMik 2022-12-04 01:32:16 -05:00
parent 50c668c467
commit 51206d60d4
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
12 changed files with 540 additions and 7 deletions

14
CMake/Getsol2.cmake Normal file
View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -12,6 +12,8 @@
#include "simpleengine/gfx/renderer.h"
#include "simpleengine/gfx/texture.h"
#include "simpleengine/vector.h"
#include <glm/gtx/string_cast.hpp>
#include <optional>
#include <semaphore.h>
#include <simpleengine/ecs/component/model_component.h>
#include <simpleengine/ecs/component/rotating_component.h>
@ -27,6 +29,9 @@
#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>
@ -38,6 +43,8 @@
#include <chrono>
#include <iostream>
#include <memory>
#include <sol/error.hpp>
#include <sol/load_result.hpp>
#include <spdlog/common.h>
#include <sstream>
#include <stdint.h>
@ -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<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;
@ -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<se::ecs::ModelComponent>("examples/dev_testing/resources/bricks/bricks.fbx");
//brick_e.add_component<se::ecs::ModelComponent>("examples/dev_testing/resources/bricks/bricks.fbx");
registry->get_inner().emplace<se::ecs::ModelComponent>(brick_e.get_inner(), "examples/dev_testing/resources/bricks/bricks.fbx");
brick_e.add_component<se::ecs::TransformComponent>(glm::vec3(6.f, 6.f, 0.f));
brick_e.add_component<se::ecs::BoxColliderComponent>(1.f, 1.f, 1.f);
brick_e.add_component<se::ecs::RigidBodyComponent>(1.f, se::Vectorf(6.f, 6.f, 0.1f));
@ -162,13 +353,20 @@ int main(int argc, char *argv[]) {
floor.add_component<se::ecs::BoxColliderComponent>(1.f, 1.f, 1.f);
floor.add_component<se::ecs::RigidBodyComponent>(0.f, se::Vectorf(6.f, -6.f, 0.f));
// Add TransformComponent using Lua
//register_met
auto light = std::make_shared<se::gfx::Light>(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<FPSCounterEvent>();
game.add_renderable(fps_counter);
auto lua_script = std::make_shared<LuaTestScriptEvent>();
game.add_renderable(lua_script);
/* game.set_enable_vsync(false);
game.set_fps_limit(100); */
int res = game.run();

View File

@ -1,12 +1,16 @@
#pragma once
#include "entt/entity/fwd.hpp"
#include <entt/entity/fwd.hpp>
#include <cstddef>
#include <entt/entt.hpp>
#include <rttr/registration.h>
#include <utility>
#include <rttr/registration>
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<typename Component, typename... Args>
decltype(auto) add_component(Args&&... args) {
return registry.emplace<Component>(inner, std::forward<Args>(args)...);
return registry.emplace<Component>(inner, std::forward<Args&&>(args)...);
}
template<typename Component>
Component& add_component_copy(Component comp) {
return registry.emplace<Component>(inner, comp);
}
/**
@ -135,7 +148,7 @@ namespace simpleengine::ecs {
*/
template<typename Component, typename... Args>
decltype(auto) add_or_replace_component(Args&&... args) {
return registry.emplace_or_replace<Component>(inner, std::forward<Args>(args)...);
return registry.emplace_or_replace<Component>(inner, std::forward<Args&&>(args)...);
}
/**
@ -156,7 +169,7 @@ namespace simpleengine::ecs {
*/
template<typename Component, typename... Args>
decltype(auto) replace_component(Args&&... args) {
return registry.replace<Component>(inner, std::forward<Args>(args)...);
return registry.replace<Component>(inner, std::forward<Args&&>(args)...);
}
/**
@ -263,4 +276,40 @@ namespace simpleengine::ecs {
return registry.orphan(inner);
}
};
}
RTTR_REGISTRATION {
using simpleengine::ecs::Entity;
rttr::registration::class_<Entity>("Entity")
.constructor<entt::registry&, entt::entity>()
.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<entt::registry::version_type (Entity::*)()>(&Entity::release))
.method("release", static_cast<entt::registry::version_type (Entity::*)(entt::registry::version_type version)>(&Entity::release))
.method("destroy", static_cast<entt::registry::version_type (Entity::*)()>(&Entity::destroy))
.method("destroy", static_cast<entt::registry::version_type (Entity::*)(entt::registry::version_type version)>(&Entity::destroy))
// TODO: implement
//.method("add_component", &Entity::add_component<simpleengine::ecs::BoxColliderComponent>)
//.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>("Sprite")
.constructor()
.method("Move", &Sprite::move)
.method("Draw", &Sprite::draw)
.property("x", &Sprite::x)
.property("y", &Sprite::y); */
}

View File

@ -1,12 +1,16 @@
#pragma once
#include "entt/signal/fwd.hpp"
#include <entt/signal/fwd.hpp>
#include <entt/entt.hpp>
#include <rttr/rttr_enable.h>
namespace simpleengine::ecs {
class Entity;
class Registry {
RTTR_ENABLE()
entt::registry inner;
//entt::dispatcher dispatcher;
public:

View File

@ -2,6 +2,8 @@
#include "system.h"
#include <rttr/rttr_enable.h>
namespace simpleengine {
// fwd decl
class Camera;
@ -15,6 +17,7 @@ namespace simpleengine {
namespace system {
class SceneSystem : public System {
RTTR_ENABLE()
protected:
std::shared_ptr<gfx::Renderer> renderer;
std::shared_ptr<Camera> camera;

View File

@ -2,12 +2,15 @@
#include "../../renderable.h"
#include <rttr/rttr_enable.h>
namespace simpleengine::ecs {
class Entity;
class Registry;
namespace system {
class System : public simpleengine::Renderable {
RTTR_ENABLE()
protected:
std::shared_ptr<Registry> entity_registry;
public:

View File

@ -0,0 +1,151 @@
#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

@ -0,0 +1,46 @@
#pragma once
#include <entt/meta/factory.hpp>
#include <entt/meta/resolve.hpp>
#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;
}
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;
}
}
// @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 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

@ -14,5 +14,8 @@ pkgs.mkShell {
assimp
spdlog
bullet
rttr
lua5_4
(callPackage ./sol2.nix { })
];
}

45
sol2.nix Normal file
View File

@ -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 ];
};
}