#include <gtest/gtest.h>
|
|
#include <type_traits>
|
|
#include <cassert>
|
|
#include <map>
|
|
#include <string>
|
|
#include <duktape.h>
|
|
#include <entt/entity/registry.hpp>
|
|
|
|
template<typename Type>
|
|
struct tag { using type = Type; };
|
|
|
|
struct position {
|
|
double x;
|
|
double y;
|
|
};
|
|
|
|
struct renderable {};
|
|
|
|
struct duktape_runtime {
|
|
std::map<duk_uint_t, std::string> components;
|
|
};
|
|
|
|
template<typename Comp>
|
|
duk_ret_t set(duk_context *ctx, entt::registry ®istry) {
|
|
const auto entity = duk_require_uint(ctx, 0);
|
|
|
|
if constexpr(std::is_same_v<Comp, position>) {
|
|
const auto x = duk_require_number(ctx, 2);
|
|
const auto y = duk_require_number(ctx, 3);
|
|
registry.assign_or_replace<position>(entity, x, y);
|
|
} else if constexpr(std::is_same_v<Comp, duktape_runtime>) {
|
|
const auto type = duk_require_uint(ctx, 1);
|
|
|
|
duk_dup(ctx, 2);
|
|
|
|
if(!registry.has<duktape_runtime>(entity)) {
|
|
registry.assign<duktape_runtime>(entity).components[type] = duk_json_encode(ctx, -1);
|
|
} else {
|
|
registry.get<duktape_runtime>(entity).components[type] = duk_json_encode(ctx, -1);
|
|
}
|
|
|
|
duk_pop(ctx);
|
|
} else {
|
|
registry.assign_or_replace<Comp>(entity);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
template<typename Comp>
|
|
duk_ret_t unset(duk_context *ctx, entt::registry ®istry) {
|
|
const auto entity = duk_require_uint(ctx, 0);
|
|
|
|
if constexpr(std::is_same_v<Comp, duktape_runtime>) {
|
|
const auto type = duk_require_uint(ctx, 1);
|
|
|
|
auto &components = registry.get<duktape_runtime>(entity).components;
|
|
assert(components.find(type) != components.cend());
|
|
components.erase(type);
|
|
|
|
if(components.empty()) {
|
|
registry.remove<duktape_runtime>(entity);
|
|
}
|
|
} else {
|
|
registry.remove<Comp>(entity);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
template<typename Comp>
|
|
duk_ret_t has(duk_context *ctx, entt::registry ®istry) {
|
|
const auto entity = duk_require_uint(ctx, 0);
|
|
|
|
if constexpr(std::is_same_v<Comp, duktape_runtime>) {
|
|
duk_push_boolean(ctx, registry.has<duktape_runtime>(entity));
|
|
|
|
if(registry.has<duktape_runtime>(entity)) {
|
|
const auto type = duk_require_uint(ctx, 1);
|
|
const auto &components = registry.get<duktape_runtime>(entity).components;
|
|
duk_push_boolean(ctx, components.find(type) != components.cend());
|
|
} else {
|
|
duk_push_false(ctx);
|
|
}
|
|
} else {
|
|
duk_push_boolean(ctx, registry.has<Comp>(entity));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
template<typename Comp>
|
|
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<Comp, position>) {
|
|
const auto &pos = registry.get<position>(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<Comp, duktape_runtime>) {
|
|
const auto type = duk_require_uint(ctx, 1);
|
|
|
|
auto &runtime = registry.get<duktape_runtime>(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<Comp>(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<typename... Comp>
|
|
void reg() {
|
|
((func[registry.type<Comp>()] = {
|
|
&::set<Comp>,
|
|
&::unset<Comp>,
|
|
&::has<Comp>,
|
|
&::get<Comp>
|
|
}), ...);
|
|
}
|
|
|
|
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<duktape_registry *>(duk_require_pointer(ctx, -1));
|
|
duk_pop_2(ctx);
|
|
|
|
return dreg;
|
|
}
|
|
|
|
template<func_map::func_type func_map::*Op>
|
|
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<duktape_runtime>();
|
|
}
|
|
|
|
assert(func.find(type) != func.cend());
|
|
|
|
return (func[type].*Op)(ctx, registry);
|
|
}
|
|
|
|
public:
|
|
duktape_registry(entt::registry &ref)
|
|
: registry{ref}
|
|
{
|
|
reg<position, renderable, duktape_runtime>();
|
|
}
|
|
|
|
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<typename entt::registry::component_type> components;
|
|
std::vector<typename entt::registry::component_type> 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<duktape_runtime>());
|
|
}
|
|
|
|
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<duktape_runtime>(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<duk_uint_t, func_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<typename decltype(type)::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>{}, "position");
|
|
export_type(context, registry, idx, tag<renderable>{}, "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<position>(e0, 0., 0.);
|
|
registry.assign<renderable>(e0);
|
|
|
|
const auto e1 = registry.create();
|
|
registry.assign<position>(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<duktape_runtime>().size(), 0u);
|
|
ASSERT_EQ(registry.view<position>().size(), 3u);
|
|
ASSERT_EQ(registry.view<renderable>().size(), 2u);
|
|
|
|
registry.view<position>().each([®istry](auto entity, const auto &position) {
|
|
ASSERT_FALSE(registry.has<duktape_runtime>(entity));
|
|
|
|
if(registry.has<renderable>(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<duktape_runtime>().size(), 1u);
|
|
ASSERT_EQ(registry.view<position>().size(), 3u);
|
|
ASSERT_EQ(registry.view<renderable>().size(), 2u);
|
|
|
|
registry.view<duktape_runtime>().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<duktape_runtime>().size(), 1u);
|
|
ASSERT_EQ(registry.view<position>().size(), 3u);
|
|
ASSERT_EQ(registry.view<renderable>().size(), 2u);
|
|
|
|
registry.view<position, renderable, duktape_runtime>().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<duktape_runtime>().size(), 0u);
|
|
ASSERT_EQ(registry.view<position>().size(), 0u);
|
|
ASSERT_EQ(registry.view<renderable>().size(), 2u);
|
|
|
|
duk_destroy_heap(ctx);
|
|
}
|