From 7ff9e21b4db767c2bfa38b80db60d60fdf271a93 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Fri, 7 Jun 2019 15:52:57 +0200 Subject: [PATCH] Added codegen to register components, and started reworking lua --- .flintlock | 2 +- .gitignore | 6 ++ .vimlocal | 3 + ecs-lua/include/ecs-lua.h | 12 ++- ecs-lua/src/ecs-lua.cpp | 13 ++- ecs-serial/include/ecs-serial.h | 13 +++ ecs-serial/src/ecs-serial.cpp | 24 ++++- ecs/include/ecs.h | 12 +-- flint.lua | 12 +++ template.mako | 46 ++++++++ test.py | 83 +++++++++++++++ test/include/components.h | 25 +++++ test/src/main.cpp | 179 ++++++++++++++++++++------------ test/test.lua | 61 +++++++---- 14 files changed, 381 insertions(+), 110 deletions(-) create mode 100644 .vimlocal create mode 100644 template.mako create mode 100755 test.py create mode 100644 test/include/components.h diff --git a/.flintlock b/.flintlock index e560e56..0f1c6ce 100644 --- a/.flintlock +++ b/.flintlock @@ -1,5 +1,5 @@ [flint.py] -version = nightly +version = feature_git base_url = https://downloads.mtgames.nl/release/flint [flint.py-plugins] diff --git a/.gitignore b/.gitignore index cbaba0e..e963a64 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,9 @@ tags # flint .flint + +# Custom +.clangd/ +compile_commands.json +test/ids +test/entities diff --git a/.vimlocal b/.vimlocal new file mode 100644 index 0000000..87574ad --- /dev/null +++ b/.vimlocal @@ -0,0 +1,3 @@ +let &makeprg="./flint.py --version ../flint/.flint/build/linux/debug/bin" +map :Make +map :Start ./flint.py --version ../flint/.flint/build/linux/debug/bin -rs && read diff --git a/ecs-lua/include/ecs-lua.h b/ecs-lua/include/ecs-lua.h index 6751aed..62fe37e 100644 --- a/ecs-lua/include/ecs-lua.h +++ b/ecs-lua/include/ecs-lua.h @@ -4,20 +4,22 @@ #include "ecs.h" #include +#include namespace ecs::lua { - struct LuaWrapper : Component { - LuaWrapper(sol::object _object) : object(_object) {} - LuaWrapper() {} + struct Wrapper : Component { + Wrapper(std::string _name, sol::table _table) : name(_name), table(_table) {} + Wrapper() {} - sol::object object; + std::string name; + sol::table table; }; template void register_component(sol::state& lua, sol::table& table, Args... args) { table.new_usertype(get_typename(), "new", sol::factories([](Constructor... constructor) { - return new T(constructor...); + return std::make_pair(new T(constructor...), get_typename()); }), args... ); lua.set_function("_internal_to_" + get_typename(), [] (Component* component) { diff --git a/ecs-lua/src/ecs-lua.cpp b/ecs-lua/src/ecs-lua.cpp index 53e1d8a..7c4e002 100644 --- a/ecs-lua/src/ecs-lua.cpp +++ b/ecs-lua/src/ecs-lua.cpp @@ -1,5 +1,6 @@ #include "ecs-lua.h" +#include "ecs.h" namespace ecs::lua { void init(sol::state& lua) { // Add a preloader that loads all the ecs stuff @@ -11,7 +12,7 @@ namespace ecs::lua { "__tostring", [] (Entity* thiz) { return uuids::to_string(thiz->uuid); }, - "add_component", [] (Entity* thiz, std::string name, Component* component) { + "add_component", [] (Entity* thiz, Component* component, std::string name) { return thiz->add_component(ComponentID::get_id({name})[0], component); }, "has_components", [] (Entity* thiz, sol::variadic_args args) { @@ -27,10 +28,7 @@ namespace ecs::lua { if (f1.valid()) { return f1(thiz->get_component(ComponentID::get_id({name})[0])); } - - // If the type of the component unknown we assume it a lua object and convert it to the wrapper - auto f2 = lua["_internal_to_LuaWrapper"]; - return f2(thiz->get_component(ComponentID::get_id({name})[0])); + throw std::runtime_error("Unknown component"); } ); @@ -52,8 +50,9 @@ namespace ecs::lua { } ); - register_component(lua, ecs, - "object", &LuaWrapper::object + register_component(lua, ecs, + "name", &Wrapper::name, + "table", &Wrapper::table ); return ecs; diff --git a/ecs-serial/include/ecs-serial.h b/ecs-serial/include/ecs-serial.h index fb2ffb2..1fe51d0 100644 --- a/ecs-serial/include/ecs-serial.h +++ b/ecs-serial/include/ecs-serial.h @@ -1,3 +1,5 @@ +#include +#include #pragma once #include "ecs.h" @@ -57,6 +59,17 @@ namespace ecs::serial { internal::functions.insert({ComponentID::id, {func1, func2, func3}}); } + template + void register_component_custom(std::function func1, std::function func2) { + auto func3 = [] () -> Component* { + return new T(); + }; + + internal::functions.insert({ComponentID::id, {func1, func2, func3}}); + } + void serialize(std::ostream& os, Entity* entity); void deserialize(std::istream& is, Manager& manager); + + void serialize_ids(std::ostream& os); } diff --git a/ecs-serial/src/ecs-serial.cpp b/ecs-serial/src/ecs-serial.cpp index 728b351..d865e1b 100644 --- a/ecs-serial/src/ecs-serial.cpp +++ b/ecs-serial/src/ecs-serial.cpp @@ -1,6 +1,14 @@ #include "ecs-serial.h" +#include "/home/tim/Projects/cpp/iohelper/iohelper/include/iohelper/read.h" +#include "/home/tim/Projects/cpp/iohelper/iohelper/include/iohelper/write.h" +#include "ecs.h" +#include +#include + +// @todo We need to make sure that the id list stays the same between versions +// Maybe store that in the stream as well? namespace ecs::serial { std::unordered_map, std::function, std::function>> internal::functions; @@ -11,8 +19,12 @@ namespace ecs::serial { auto components = entity->get_components(); iohelper::write_length(os, components.size()); for (auto [id, component] : components) { + auto functions = internal::functions.find(id); + if (functions == internal::functions.end()) { + throw std::runtime_error("Unknown id"); + } + iohelper::write_length(os, id); - // @todo What if the function does not exist? std::get<0>(internal::functions[id])(os, component); } } @@ -60,4 +72,14 @@ namespace ecs::serial { } } } + + void serialize_ids(std::ostream& os) { + auto& ids = ComponentID::_ids(); + + iohelper::write_length(os, ids.size()); + for (auto& [name, id] : ids) { + iohelper::write(os, name); + iohelper::write_length(os, id); + } + } } diff --git a/ecs/include/ecs.h b/ecs/include/ecs.h index 69a91da..83b54ae 100644 --- a/ecs/include/ecs.h +++ b/ecs/include/ecs.h @@ -34,10 +34,10 @@ namespace ecs { class ComponentID { private: static size_t _id; - // This needs to be a function because otherwise it might not be initialized on time - static std::unordered_map& _ids(); public: + // This needs to be a function because otherwise it might not be initialized on time + static std::unordered_map& _ids(); static std::vector get_id(std::vector names); // This looks kind of ugly @@ -122,11 +122,11 @@ namespace ecs { return _entities.begin(); } - const auto begin() const { + auto begin() const { return _entities.begin(); } - const auto cbegin() const { + auto cbegin() const { return _entities.cbegin(); } @@ -134,11 +134,11 @@ namespace ecs { return _entities.end(); } - const auto end() const { + auto end() const { return _entities.end(); } - const auto cend() const { + auto cend() const { return _entities.cend(); } diff --git a/flint.lua b/flint.lua index c40767e..f0f201e 100644 --- a/flint.lua +++ b/flint.lua @@ -31,11 +31,23 @@ lib "ecs-serial" dependency("ecs", "iohelper") +function codegen(file) + local handle = io.popen("mkdir " .. config.paths.build .. "/generated") + handle:close() + local command = "python test.py test/include/" .. file .. " > " .. config.paths.build .. "/generated/ecs_" .. file + handle = io.popen(command) + handle:close() +end + executable "test" path "test" dependency "ecs-lua" dependency "ecs-serial" + hook(step.PRE_BUILD, codegen, "components.h") + + include(config.paths.build .. "/generated/") + run_dir "test" run_target "test" diff --git a/template.mako b/template.mako new file mode 100644 index 0000000..6c81266 --- /dev/null +++ b/template.mako @@ -0,0 +1,46 @@ +#include "${include_file}" + +#if __has_include("ecs-lua.h") +#include "sol.hpp" + +namespace generated { + void init(sol::state& lua) { + sol::table preload = lua["package"]["preload"]; + % for c in components: + + preload["ecs.${c.name}"] = [&lua] { + sol::table component = lua.create_table(); + component.new_usertype<${c.name}>("${c.name}", + ${', '.join("\"{}\", &{}::{}".format(v.name, c.name, v.name) for v in c.variables if not "hidden" in v.annotations)} + ); + + % for con in c.constructors: + % if len(con.parameters) > 0: + component.set_function("new", sol::factories([](${', '.join("{} {}".format(p.type, p.name) for p in con.parameters)}) { + return std::make_pair(new ${c.name}(${', '.join(p.name for p in con.parameters)}), "${c.name}"); + })); + + % endif + % endfor + lua.set_function("_internal_to_${c.name}", [] (ecs::Component* component) { + return (${c.name}*)component; + }); + + return component; + }; + % endfor + } +} +#endif + +#if __has_include("ecs-serial.h") +namespace generated { + void init() { + % for c in components: + ecs::serial::register_component<${c.name}>( + ${', '.join("&{}::{}".format(c.name, v.name) for v in c.variables if not "unserial" in v.annotations)} + ); + % endfor + } +} +#endif diff --git a/test.py b/test.py new file mode 100755 index 0000000..e479209 --- /dev/null +++ b/test.py @@ -0,0 +1,83 @@ +#! /usr/bin/env python3 +import sys +import clang.cindex +from mako.template import Template + +def node_children(node): + return (c for c in node.get_children() if c.location.file.name == filename) + +def get_annotations(node): + return [c.displayname for c in node.get_children() if c.kind == clang.cindex.CursorKind.ANNOTATE_ATTR] + +class Variable(object): + def __init__(self, cursor): + self.name = cursor.spelling + self.annotations = get_annotations(cursor) + +class Parameter(object): + def __init__(self, cursor): + self.name = cursor.spelling + self.type = cursor.type.spelling + +class Constructor(object): + def __init__(self, cursor): + + self.parameters = [] + + for c in cursor.get_children(): + if c.kind == clang.cindex.CursorKind.PARM_DECL: + p = Parameter(c) + self.parameters.append(p) + +class Component(object): + def __init__(self, cursor): + self.name = cursor.spelling + self.variables = [] + + self.constructors = [] + + for c in cursor.get_children(): + if (c.kind == clang.cindex.CursorKind.CONSTRUCTOR and c.access_specifier == clang.cindex.AccessSpecifier.PUBLIC): + con = Constructor(c) + # if len(con.parameters) > 0: + self.constructors.append(con) + + if (c.kind == clang.cindex.CursorKind.FIELD_DECL and c.access_specifier == clang.cindex.AccessSpecifier.PUBLIC): + v = Variable(c) + self.variables.append(v) + +def build_components(cursor, filename): + result = [] + for c in cursor.get_children(): + if (c.kind == clang.cindex.CursorKind.STRUCT_DECL and c.location.file.name == filename): + for d in c.get_children(): + if (d.kind == clang.cindex.CursorKind.CXX_BASE_SPECIFIER and d.spelling == "ecs::Component"): + a_class = Component(c) + result.append(a_class) + break + + elif c.kind == clang.cindex.CursorKind.NAMESPACE: + child_components = build_components(c, filename) + result.extend(child_components) + + return result + +def main(argv): + if len(sys.argv) != 2: + print("Usage: {} [header filename]".format(argv[0])) + sys.exit() + + filename = argv[1] + + index = clang.cindex.Index.create() + tu = index.parse(filename, ['-x', 'c++', '-std=c++17', '-Iecs/include']) + + components = build_components(tu.cursor, filename) + + tpl = Template(filename='template.mako') + + include_file = filename.split('/')[-1] + print(tpl.render(components=components, include_file=include_file)) + +if __name__ == "__main__": + main(sys.argv) diff --git a/test/include/components.h b/test/include/components.h new file mode 100644 index 0000000..c98abb3 --- /dev/null +++ b/test/include/components.h @@ -0,0 +1,25 @@ +#pragma once +#include "ecs.h" + +struct Position : ecs::Component { + Position(float _x, float _y) : x(_x), y(_y) {} + Position() {} + + float x = 0; + float y = 0; +}; + +struct Velocity : ecs::Component { + Velocity(float _x, float _y) : x(_x), y(_y) {} + Velocity() {} + + float x = 0; + float y = 0; +}; + +struct Meta : ecs::Component { + Meta(std::string _name) : name(_name) {} + Meta() {} + + std::string name; +}; diff --git a/test/src/main.cpp b/test/src/main.cpp index d55bb42..25c4e7c 100644 --- a/test/src/main.cpp +++ b/test/src/main.cpp @@ -1,24 +1,20 @@ +#include "/home/tim/Projects/cpp/iohelper/iohelper/include/iohelper/read.h" +#include "/home/tim/Projects/cpp/iohelper/iohelper/include/iohelper/write.h" +#include "components.h" #include "ecs-lua.h" #include "ecs-serial.h" +#include +#include +#include #include #include -struct Position : ecs::Component { - Position(float _x, float _y) : x(_x), y(_y) {} - Position() {} - - float x; - float y; -}; - -struct Velocity : ecs::Component { - Velocity(float _x, float _y) : x(_x), y(_y) {} - Velocity() {} - - float x; - float y; -}; +#include +#include +#include +#include "ecs.h" +#include "ecs_components.h" int main() { sol::state lua; @@ -26,42 +22,99 @@ int main() { ecs::lua::init(lua); - // @todo We should probably make this simpler to do... - sol::table preload = lua["package"]["preload"]; - preload["ecs_test"] = [&lua] { - sol::table test = lua.create_table(); + generated::init(lua); + generated::init(); - ecs::lua::register_component(lua, test, - "x", &Position::x, - "y", &Position::y - ); + ecs::Manager manager; - ecs::lua::register_component(lua, test, - "x", &Velocity::x, - "y", &Velocity::y - ); + ecs::serial::register_component_custom( + [](std::ostream& os, ecs::Component* component) { + ecs::lua::Wrapper* wrapper = (ecs::lua::Wrapper*)component; - return test; - }; + iohelper::write(os, wrapper->name); - ecs::serial::register_component( - &Position::x, - &Position::y - ); - - ecs::serial::register_component( - &Velocity::x, - &Velocity::y - ); - - ecs::serial::register_component( - // @todo We need to create a read and write function for sol::object - // &ecs::lua::LuaWrapper::object + // #todo .size() does not work + size_t size = 0; + for (auto [a, b] : wrapper->table) { + size++; + } + iohelper::write_length(os, size); + + for (auto [a, b] : wrapper->table) { + iohelper::write(os, a.as()); + + sol::type type = b.get_type(); + iohelper::write(os, (uint8_t)type); + switch (type) { + case sol::type::none: + case sol::type::nil: + break; + + case sol::type::string: + iohelper::write(os, b.as()); + break; + + case sol::type::number: + iohelper::write(os, b.as()); + break; + + case sol::type::boolean: + iohelper::write(os, b.as()); + break; + + case sol::type::table: + // @todo Make this happen + break; + + // All other types are not supported + default: + throw std::runtime_error("Unsupported type in wrapped lua table"); + break; + } + } + }, + [](std::istream& is, ecs::Component* component) { + ecs::lua::Wrapper* wrapper = (ecs::lua::Wrapper*)component; + + wrapper->name = iohelper::read(is); + std::cout << wrapper->name << '\n'; + + size_t size = iohelper::read_length(is); + std::cout << "Size: " << size << '\n'; + for (size_t i = 0; i < size; ++i) { + std::string name = iohelper::read(is); + + std::cout << "Name: " << name << '\n'; + + sol::type type = (sol::type)iohelper::read(is); + switch (type) { + case sol::type::none: + break; + + case sol::type::nil: + break; + + case sol::type::string: + std::cout << "Value: " << iohelper::read(is) << '\n'; + break; + + case sol::type::number: + std::cout << "Value: " << std::dec << iohelper::read(is) << '\n'; + break; + + case sol::type::boolean: + std::cout << "Value: " << std::dec << iohelper::read(is) << '\n'; + break; + + default: + break; + } + } + } ); + // Testing interop with lua { - // This manages all our entities - ecs::Manager manager; lua.set_function("get_manager", [&manager] () -> ecs::Manager& { return manager; @@ -69,23 +122,6 @@ int main() { lua.script_file("test.lua"); - // for (int i = 0; i < 10; ++i) { - // // We can create entities - // Entity* entity = manager.create_entity(); - // // Then we can add components to them - // entity->add_component(0.0f, 0.0f); - // if (i % 2 == 0) { - // entity->add_component(0.1f, 0.2f); - // } - // - // // We can check for components by name - // if (entity->has_components()) { - // std::cout << "YES\n"; - // } else { - // std::cout << "NO\n"; - // } - // } - std::cout << "Update position\n"; manager.view().for_each([](ecs::Entity*, Position* pos, Velocity* vel) { pos->x += vel->x; @@ -100,22 +136,22 @@ int main() { // These are really just an internal api that should not be used for (auto [uuid, entity] : manager.view(ecs::ComponentID::get_id({"Random"}))) { - sol::table random = ((ecs::lua::LuaWrapper*)entity->get_component(ecs::ComponentID::get_id({"Random"})[0]))->object; + sol::table random = ((ecs::lua::Wrapper*)entity->get_component(ecs::ComponentID::get_id({"Random"})[0]))->table; random["a"] = 21; std::cout << random["a"].get() << '\n'; }; for (auto [uuid, entity] : manager.view(ecs::ComponentID::get_id({"Random"}))) { - sol::table random = ((ecs::lua::LuaWrapper*)entity->get_component(ecs::ComponentID::get_id({"Random"})[0]))->object; + sol::table random = ((ecs::lua::Wrapper*)entity->get_component(ecs::ComponentID::get_id({"Random"})[0]))->table; std::cout << random["a"].get() << '\n'; }; } + // Test serialization { std::cout << "STORE\n"; - ecs::Manager manager; // Create entities and store them for (int i = 0; i < 10; ++i) { @@ -145,26 +181,31 @@ int main() { std::cout << "x: " << pos->x << '\n'; std::cout << "y: " << pos->y << '\n'; }); + + // @todo This should be used to constuct a conversion table + // This is needed to avoid id conflicts between clients + std::ofstream ids2("ids", std::ios::out | std::ios::trunc); + ecs::serial::serialize_ids(ids2); } { std::cout << "LOAD\n"; - ecs::Manager manager; + ecs::Manager manager2; // Load entities from disk as a test std::ifstream file("entities", std::ios::in); size_t count = iohelper::read_length(file); for (size_t i = 0; i < count; ++i) { - ecs::serial::deserialize(file, manager); + ecs::serial::deserialize(file, manager2); } file.seekg(0, std::ios::beg); iohelper::read_length(file); - ecs::serial::deserialize(file, manager); + ecs::serial::deserialize(file, manager2); if (false) { - auto ent = manager.get_entity(uuids::uuid::from_string("6d58fdb5-6d8c-4e6f-89d4-f7d7b184f463").value()); + auto ent = manager2.get_entity(uuids::uuid::from_string("6d58fdb5-6d8c-4e6f-89d4-f7d7b184f463").value()); if (ent->has_components()) { auto pos = ent->get_component(); pos->x = 1.2f; @@ -172,7 +213,7 @@ int main() { } } - manager.view().for_each([] (ecs::Entity* entity, Position* pos) { + manager2.view().for_each([] (ecs::Entity* entity, Position* pos) { std::cout << "uuid: " << entity->uuid << '\n'; std::cout << "x: " << pos->x << '\n'; std::cout << "y: " << pos->y << '\n'; diff --git a/test/test.lua b/test/test.lua index 18bcb09..ce61f55 100644 --- a/test/test.lua +++ b/test/test.lua @@ -1,18 +1,29 @@ local ecs = require "ecs" -local test = require "ecs_test" +-- local test = require "ecs_test" +local Position = require "ecs.Position" +local Velocity = require "ecs.Velocity" +local Meta = require "ecs.Meta" manager = get_manager() ent = manager:create_entity() -ent:add_component("Position", test.Position.new(1.9, 9.7)) -ent:add_component("Velocity", test.Velocity.new(0.2, 0.3)) +ent:add_component(Position.new(1.9, 9.7)) +ent:add_component(Velocity.new(0.2, 0.3)) +ent:add_component(Meta.new("Soldier")) + random = { - a = 10, - b = 11, - c = function(s) - print("Hello " .. s .. "!") - end + speed = 10, + something = "Hello, World!", + alive = true } -ent:add_component("Random", ecs.LuaWrapper.new(random)) +ent:add_component(ecs.Wrapper.new("Random", random)) + +-- @todo Make this happen... +-- stats = { +-- hp = 100, +-- magic = 30 +-- } +-- +-- ent:add_component(ecs.Wrapper.new("Stats", stats)) print(ent:has_components("Position", "Velocity")) pos = ent:get_component("Position") @@ -23,8 +34,7 @@ print("v_x: " .. vel.x) print("v_y: " .. vel.y) print("View test") -view = manager:view("Position", "Velocity") -view:for_each(function(ent) +manager:view("Position", "Velocity"):for_each(function(ent) pos = ent:get_component("Position") vel = ent:get_component("Velocity") @@ -38,6 +48,11 @@ view:for_each(function(ent) print("v_x: " .. vel.x) print("v_y: " .. vel.y) + + if ent:has_components("Meta") then + meta = ent:get_component("Meta") + print("name: " .. meta.name) + end end) -- @todo Implement this @@ -45,16 +60,20 @@ end) -- print "TEST" -- end -manager:view("Random"):for_each(function(ent) - wrapped = ent:get_component("Random").object +manager:view("Wrapper"):for_each(function(ent) + wrapped = ent:get_component("Wrapper").table - print(wrapped.a) - wrapped.a = 11 - print(wrapped.a) - print(random.a) - random.a = 20 - print(wrapped.a) - print(random.a) + print(wrapped.speed) + wrapped.speed = 11 + print(wrapped.speed) + print(random.speed) + random.speed = 20 + print(wrapped.speed) + print(random.speed) - random.c("you") + print(wrapped.alive) + wrapped.alive = false + print(wrapped.alive) + + print(wrapped.something) end)