#include #include #include #include #include #include #include template struct tag { using type = Type; }; struct position { double x; double y; }; struct renderable {}; struct duktape_runtime { std::map components; }; template duk_ret_t set(duk_context *ctx, entt::registry ®istry) { const auto entity = duk_require_uint(ctx, 0); if constexpr(std::is_same_v) { const auto x = duk_require_number(ctx, 2); const auto y = duk_require_number(ctx, 3); registry.assign_or_replace(entity, x, y); } else if constexpr(std::is_same_v) { const auto type = duk_require_uint(ctx, 1); duk_dup(ctx, 2); if(!registry.has(entity)) { registry.assign(entity).components[type] = duk_json_encode(ctx, -1); } else { registry.get(entity).components[type] = duk_json_encode(ctx, -1); } duk_pop(ctx); } else { registry.assign_or_replace(entity); } return 0; } template duk_ret_t unset(duk_context *ctx, entt::registry ®istry) { const auto entity = duk_require_uint(ctx, 0); if constexpr(std::is_same_v) { const auto type = duk_require_uint(ctx, 1); auto &components = registry.get(entity).components; assert(components.find(type) != components.cend()); components.erase(type); if(components.empty()) { registry.remove(entity); } } else { registry.remove(entity); } return 0; } template duk_ret_t has(duk_context *ctx, entt::registry ®istry) { const auto entity = duk_require_uint(ctx, 0); if constexpr(std::is_same_v) { duk_push_boolean(ctx, registry.has(entity)); if(registry.has(entity)) { const auto type = duk_require_uint(ctx, 1); const auto &components = registry.get(entity).components; duk_push_boolean(ctx, components.find(type) != components.cend()); } else { duk_push_false(ctx); } } else { duk_push_boolean(ctx, registry.has(entity)); } return 1; } template duk_ret_t get(duk_context *ctx, entt::registry ®istry) { [[maybe_unused]] const auto entity = duk_require_uint(ctx, 0); if constexpr(std::is_same_v) { const auto &pos = registry.get(entity); const auto idx = duk_push_object(ctx); duk_push_string(ctx, "x"); duk_push_number(ctx, pos.x); duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE); duk_push_string(ctx, "y"); duk_push_number(ctx, pos.y); duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE); } if constexpr(std::is_same_v) { const auto type = duk_require_uint(ctx, 1); auto &runtime = registry.get(entity); assert(runtime.components.find(type) != runtime.components.cend()); duk_push_string(ctx, runtime.components[type].c_str()); duk_json_decode(ctx, -1); } else { assert(registry.has(entity)); duk_push_object(ctx); } return 1; } class duktape_registry { // I'm pretty sure I won't have more than 99 components in the example static constexpr entt::registry::component_type udef = 100; struct func_map { using func_type = duk_ret_t(*)(duk_context *, entt::registry &); func_type set; func_type unset; func_type has; func_type get; }; template void reg() { ((func[registry.type()] = { &::set, &::unset, &::has, &::get }), ...); } static duktape_registry & instance(duk_context *ctx) { duk_push_this(ctx); duk_push_string(ctx, DUK_HIDDEN_SYMBOL("dreg")); duk_get_prop(ctx, -2); auto &dreg = *static_cast(duk_require_pointer(ctx, -1)); duk_pop_2(ctx); return dreg; } template static duk_ret_t invoke(duk_context *ctx) { auto &dreg = instance(ctx); auto &func = dreg.func; auto ®istry = dreg.registry; auto type = duk_require_uint(ctx, 1); if(type >= udef) { type = registry.type(); } assert(func.find(type) != func.cend()); return (func[type].*Op)(ctx, registry); } public: duktape_registry(entt::registry &ref) : registry{ref} { reg(); } static duk_ret_t identifier(duk_context *ctx) { static auto next = udef; duk_push_uint(ctx, next++); return 1; } static duk_ret_t create(duk_context *ctx) { auto &dreg = instance(ctx); duk_push_uint(ctx, dreg.registry.create()); return 1; } static duk_ret_t set(duk_context *ctx) { return invoke<&func_map::set>(ctx); } static duk_ret_t unset(duk_context *ctx) { return invoke<&func_map::unset>(ctx); } static duk_ret_t has(duk_context *ctx) { return invoke<&func_map::has>(ctx); } static duk_ret_t get(duk_context *ctx) { return invoke<&func_map::get>(ctx); } static duk_ret_t entities(duk_context *ctx) { const duk_idx_t nargs = duk_get_top(ctx); auto &dreg = instance(ctx); duk_uarridx_t pos = 0; duk_push_array(ctx); std::vector components; std::vector runtime; for(duk_idx_t arg = 0; arg < nargs; arg++) { auto type = duk_require_uint(ctx, arg); if(type < udef) { components.push_back(type); } else { if(runtime.empty()) { components.push_back(dreg.registry.type()); } runtime.push_back(type); } } auto view = dreg.registry.runtime_view(components.cbegin(), components.cend()); for(const auto entity: view) { if(runtime.empty()) { duk_push_uint(ctx, entity); duk_put_prop_index(ctx, -2, pos++); } else { const auto &others = dreg.registry.get(entity).components; const auto match = std::all_of(runtime.cbegin(), runtime.cend(), [&others](const auto type) { return others.find(type) != others.cend(); }); if(match) { duk_push_uint(ctx, entity); duk_put_prop_index(ctx, -2, pos++); } } } return 1; } private: std::map func; entt::registry ®istry; }; const duk_function_list_entry js_duktape_registry_methods[] = { { "identifier", &duktape_registry::identifier, 0 }, { "create", &duktape_registry::create, 0 }, { "set", &duktape_registry::set, DUK_VARARGS }, { "unset", &duktape_registry::unset, 2 }, { "has", &duktape_registry::has, 2 }, { "get", &duktape_registry::get, 2 }, { "entities", &duktape_registry::entities, DUK_VARARGS }, { nullptr, nullptr, 0 } }; void export_types(duk_context *context, entt::registry ®istry) { auto export_type = [](auto *ctx, auto ®, auto idx, auto type, const auto *name) { duk_push_string(ctx, name); duk_push_uint(ctx, reg.template type()); duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_CLEAR_WRITABLE); }; auto idx = duk_push_object(context); export_type(context, registry, idx, tag{}, "position"); export_type(context, registry, idx, tag{}, "renderable"); duk_put_global_string(context, "Types"); } void export_duktape_registry(duk_context *ctx, duktape_registry &dreg) { auto idx = duk_push_object(ctx); duk_push_string(ctx, DUK_HIDDEN_SYMBOL("dreg")); duk_push_pointer(ctx, &dreg); duk_put_prop(ctx, idx); duk_put_function_list(ctx, idx, js_duktape_registry_methods); duk_put_global_string(ctx, "Registry"); } TEST(Mod, Duktape) { entt::registry registry; duktape_registry dreg{registry}; duk_context *ctx = duk_create_heap_default(); if(!ctx) { FAIL(); } export_types(ctx, registry); export_duktape_registry(ctx, dreg); const char *s0 = "" "Types[\"PLAYING_CHARACTER\"] = Registry.identifier();" "Types[\"VELOCITY\"] = Registry.identifier();" ""; if(duk_peval_string(ctx, s0)) { FAIL(); } const auto e0 = registry.create(); registry.assign(e0, 0., 0.); registry.assign(e0); const auto e1 = registry.create(); registry.assign(e1, 0., 0.); const char *s1 = "" "Registry.entities(Types.position, Types.renderable).forEach(function(entity) {" "Registry.set(entity, Types.position, 100., 100.);" "});" "var entity = Registry.create();" "Registry.set(entity, Types.position, 100., 100.);" "Registry.set(entity, Types.renderable);" ""; if(duk_peval_string(ctx, s1)) { FAIL(); } ASSERT_EQ(registry.view().size(), 0u); ASSERT_EQ(registry.view().size(), 3u); ASSERT_EQ(registry.view().size(), 2u); registry.view().each([®istry](auto entity, const auto &position) { ASSERT_FALSE(registry.has(entity)); if(registry.has(entity)) { ASSERT_EQ(position.x, 100.); ASSERT_EQ(position.y, 100.); } else { ASSERT_EQ(position.x, 0.); ASSERT_EQ(position.y, 0.); } }); const char *s2 = "" "Registry.entities(Types.position).forEach(function(entity) {" "if(!Registry.has(entity, Types.renderable)) {" "Registry.set(entity, Types.VELOCITY, { \"dx\": -100., \"dy\": -100. });" "Registry.set(entity, Types.PLAYING_CHARACTER, {});" "}" "});" ""; if(duk_peval_string(ctx, s2)) { FAIL(); } ASSERT_EQ(registry.view().size(), 1u); ASSERT_EQ(registry.view().size(), 3u); ASSERT_EQ(registry.view().size(), 2u); registry.view().each([](const duktape_runtime &runtime) { ASSERT_EQ(runtime.components.size(), 2u); }); const char *s3 = "" "Registry.entities(Types.position, Types.renderable, Types.VELOCITY, Types.PLAYING_CHARACTER).forEach(function(entity) {" "var velocity = Registry.get(entity, Types.VELOCITY);" "Registry.set(entity, Types.position, velocity.dx, velocity.dy)" "});" ""; if(duk_peval_string(ctx, s3)) { FAIL(); } ASSERT_EQ(registry.view().size(), 1u); ASSERT_EQ(registry.view().size(), 3u); ASSERT_EQ(registry.view().size(), 2u); registry.view().each([](const position &position, auto &&...) { ASSERT_EQ(position.x, -100.); ASSERT_EQ(position.y, -100.); }); const char *s4 = "" "Registry.entities(Types.VELOCITY, Types.PLAYING_CHARACTER).forEach(function(entity) {" "Registry.unset(entity, Types.VELOCITY);" "Registry.unset(entity, Types.PLAYING_CHARACTER);" "});" "Registry.entities(Types.position).forEach(function(entity) {" "Registry.unset(entity, Types.position);" "});" ""; if(duk_peval_string(ctx, s4)) { FAIL(); } ASSERT_EQ(registry.view().size(), 0u); ASSERT_EQ(registry.view().size(), 0u); ASSERT_EQ(registry.view().size(), 2u); duk_destroy_heap(ctx); }