🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

414 lines
12 KiB

  1. #include <gtest/gtest.h>
  2. #include <type_traits>
  3. #include <cassert>
  4. #include <map>
  5. #include <string>
  6. #include <duktape.h>
  7. #include <entt/entity/registry.hpp>
  8. template<typename Type>
  9. struct tag { using type = Type; };
  10. struct position {
  11. double x;
  12. double y;
  13. };
  14. struct renderable {};
  15. struct duktape_runtime {
  16. std::map<duk_uint_t, std::string> components;
  17. };
  18. template<typename Comp>
  19. duk_ret_t set(duk_context *ctx, entt::registry &registry) {
  20. const auto entity = duk_require_uint(ctx, 0);
  21. if constexpr(std::is_same_v<Comp, position>) {
  22. const auto x = duk_require_number(ctx, 2);
  23. const auto y = duk_require_number(ctx, 3);
  24. registry.assign_or_replace<position>(entity, x, y);
  25. } else if constexpr(std::is_same_v<Comp, duktape_runtime>) {
  26. const auto type = duk_require_uint(ctx, 1);
  27. duk_dup(ctx, 2);
  28. if(!registry.has<duktape_runtime>(entity)) {
  29. registry.assign<duktape_runtime>(entity).components[type] = duk_json_encode(ctx, -1);
  30. } else {
  31. registry.get<duktape_runtime>(entity).components[type] = duk_json_encode(ctx, -1);
  32. }
  33. duk_pop(ctx);
  34. } else {
  35. registry.assign_or_replace<Comp>(entity);
  36. }
  37. return 0;
  38. }
  39. template<typename Comp>
  40. duk_ret_t unset(duk_context *ctx, entt::registry &registry) {
  41. const auto entity = duk_require_uint(ctx, 0);
  42. if constexpr(std::is_same_v<Comp, duktape_runtime>) {
  43. const auto type = duk_require_uint(ctx, 1);
  44. auto &components = registry.get<duktape_runtime>(entity).components;
  45. assert(components.find(type) != components.cend());
  46. components.erase(type);
  47. if(components.empty()) {
  48. registry.remove<duktape_runtime>(entity);
  49. }
  50. } else {
  51. registry.remove<Comp>(entity);
  52. }
  53. return 0;
  54. }
  55. template<typename Comp>
  56. duk_ret_t has(duk_context *ctx, entt::registry &registry) {
  57. const auto entity = duk_require_uint(ctx, 0);
  58. if constexpr(std::is_same_v<Comp, duktape_runtime>) {
  59. duk_push_boolean(ctx, registry.has<duktape_runtime>(entity));
  60. if(registry.has<duktape_runtime>(entity)) {
  61. const auto type = duk_require_uint(ctx, 1);
  62. const auto &components = registry.get<duktape_runtime>(entity).components;
  63. duk_push_boolean(ctx, components.find(type) != components.cend());
  64. } else {
  65. duk_push_false(ctx);
  66. }
  67. } else {
  68. duk_push_boolean(ctx, registry.has<Comp>(entity));
  69. }
  70. return 1;
  71. }
  72. template<typename Comp>
  73. duk_ret_t get(duk_context *ctx, entt::registry &registry) {
  74. [[maybe_unused]] const auto entity = duk_require_uint(ctx, 0);
  75. if constexpr(std::is_same_v<Comp, position>) {
  76. const auto &pos = registry.get<position>(entity);
  77. const auto idx = duk_push_object(ctx);
  78. duk_push_string(ctx, "x");
  79. duk_push_number(ctx, pos.x);
  80. duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE);
  81. duk_push_string(ctx, "y");
  82. duk_push_number(ctx, pos.y);
  83. duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE);
  84. } if constexpr(std::is_same_v<Comp, duktape_runtime>) {
  85. const auto type = duk_require_uint(ctx, 1);
  86. auto &runtime = registry.get<duktape_runtime>(entity);
  87. assert(runtime.components.find(type) != runtime.components.cend());
  88. duk_push_string(ctx, runtime.components[type].c_str());
  89. duk_json_decode(ctx, -1);
  90. } else {
  91. assert(registry.has<Comp>(entity));
  92. duk_push_object(ctx);
  93. }
  94. return 1;
  95. }
  96. class duktape_registry {
  97. // I'm pretty sure I won't have more than 99 components in the example
  98. static constexpr entt::registry::component_type udef = 100;
  99. struct func_map {
  100. using func_type = duk_ret_t(*)(duk_context *, entt::registry &);
  101. func_type set;
  102. func_type unset;
  103. func_type has;
  104. func_type get;
  105. };
  106. template<typename... Comp>
  107. void reg() {
  108. ((func[registry.type<Comp>()] = {
  109. &::set<Comp>,
  110. &::unset<Comp>,
  111. &::has<Comp>,
  112. &::get<Comp>
  113. }), ...);
  114. }
  115. static duktape_registry & instance(duk_context *ctx) {
  116. duk_push_this(ctx);
  117. duk_push_string(ctx, DUK_HIDDEN_SYMBOL("dreg"));
  118. duk_get_prop(ctx, -2);
  119. auto &dreg = *static_cast<duktape_registry *>(duk_require_pointer(ctx, -1));
  120. duk_pop_2(ctx);
  121. return dreg;
  122. }
  123. template<func_map::func_type func_map::*Op>
  124. static duk_ret_t invoke(duk_context *ctx) {
  125. auto &dreg = instance(ctx);
  126. auto &func = dreg.func;
  127. auto &registry = dreg.registry;
  128. auto type = duk_require_uint(ctx, 1);
  129. if(type >= udef) {
  130. type = registry.type<duktape_runtime>();
  131. }
  132. assert(func.find(type) != func.cend());
  133. return (func[type].*Op)(ctx, registry);
  134. }
  135. public:
  136. duktape_registry(entt::registry &ref)
  137. : registry{ref}
  138. {
  139. reg<position, renderable, duktape_runtime>();
  140. }
  141. static duk_ret_t identifier(duk_context *ctx) {
  142. static auto next = udef;
  143. duk_push_uint(ctx, next++);
  144. return 1;
  145. }
  146. static duk_ret_t create(duk_context *ctx) {
  147. auto &dreg = instance(ctx);
  148. duk_push_uint(ctx, dreg.registry.create());
  149. return 1;
  150. }
  151. static duk_ret_t set(duk_context *ctx) {
  152. return invoke<&func_map::set>(ctx);
  153. }
  154. static duk_ret_t unset(duk_context *ctx) {
  155. return invoke<&func_map::unset>(ctx);
  156. }
  157. static duk_ret_t has(duk_context *ctx) {
  158. return invoke<&func_map::has>(ctx);
  159. }
  160. static duk_ret_t get(duk_context *ctx) {
  161. return invoke<&func_map::get>(ctx);
  162. }
  163. static duk_ret_t entities(duk_context *ctx) {
  164. const duk_idx_t nargs = duk_get_top(ctx);
  165. auto &dreg = instance(ctx);
  166. duk_uarridx_t pos = 0;
  167. duk_push_array(ctx);
  168. std::vector<typename entt::registry::component_type> components;
  169. std::vector<typename entt::registry::component_type> runtime;
  170. for(duk_idx_t arg = 0; arg < nargs; arg++) {
  171. auto type = duk_require_uint(ctx, arg);
  172. if(type < udef) {
  173. components.push_back(type);
  174. } else {
  175. if(runtime.empty()) {
  176. components.push_back(dreg.registry.type<duktape_runtime>());
  177. }
  178. runtime.push_back(type);
  179. }
  180. }
  181. auto view = dreg.registry.runtime_view(components.cbegin(), components.cend());
  182. for(const auto entity: view) {
  183. if(runtime.empty()) {
  184. duk_push_uint(ctx, entity);
  185. duk_put_prop_index(ctx, -2, pos++);
  186. } else {
  187. const auto &others = dreg.registry.get<duktape_runtime>(entity).components;
  188. const auto match = std::all_of(runtime.cbegin(), runtime.cend(), [&others](const auto type) {
  189. return others.find(type) != others.cend();
  190. });
  191. if(match) {
  192. duk_push_uint(ctx, entity);
  193. duk_put_prop_index(ctx, -2, pos++);
  194. }
  195. }
  196. }
  197. return 1;
  198. }
  199. private:
  200. std::map<duk_uint_t, func_map> func;
  201. entt::registry &registry;
  202. };
  203. const duk_function_list_entry js_duktape_registry_methods[] = {
  204. { "identifier", &duktape_registry::identifier, 0 },
  205. { "create", &duktape_registry::create, 0 },
  206. { "set", &duktape_registry::set, DUK_VARARGS },
  207. { "unset", &duktape_registry::unset, 2 },
  208. { "has", &duktape_registry::has, 2 },
  209. { "get", &duktape_registry::get, 2 },
  210. { "entities", &duktape_registry::entities, DUK_VARARGS },
  211. { nullptr, nullptr, 0 }
  212. };
  213. void export_types(duk_context *context, entt::registry &registry) {
  214. auto export_type = [](auto *ctx, auto &reg, auto idx, auto type, const auto *name) {
  215. duk_push_string(ctx, name);
  216. duk_push_uint(ctx, reg.template type<typename decltype(type)::type>());
  217. duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_CLEAR_WRITABLE);
  218. };
  219. auto idx = duk_push_object(context);
  220. export_type(context, registry, idx, tag<position>{}, "position");
  221. export_type(context, registry, idx, tag<renderable>{}, "renderable");
  222. duk_put_global_string(context, "Types");
  223. }
  224. void export_duktape_registry(duk_context *ctx, duktape_registry &dreg) {
  225. auto idx = duk_push_object(ctx);
  226. duk_push_string(ctx, DUK_HIDDEN_SYMBOL("dreg"));
  227. duk_push_pointer(ctx, &dreg);
  228. duk_put_prop(ctx, idx);
  229. duk_put_function_list(ctx, idx, js_duktape_registry_methods);
  230. duk_put_global_string(ctx, "Registry");
  231. }
  232. TEST(Mod, Duktape) {
  233. entt::registry registry;
  234. duktape_registry dreg{registry};
  235. duk_context *ctx = duk_create_heap_default();
  236. if(!ctx) {
  237. FAIL();
  238. }
  239. export_types(ctx, registry);
  240. export_duktape_registry(ctx, dreg);
  241. const char *s0 = ""
  242. "Types[\"PLAYING_CHARACTER\"] = Registry.identifier();"
  243. "Types[\"VELOCITY\"] = Registry.identifier();"
  244. "";
  245. if(duk_peval_string(ctx, s0)) {
  246. FAIL();
  247. }
  248. const auto e0 = registry.create();
  249. registry.assign<position>(e0, 0., 0.);
  250. registry.assign<renderable>(e0);
  251. const auto e1 = registry.create();
  252. registry.assign<position>(e1, 0., 0.);
  253. const char *s1 = ""
  254. "Registry.entities(Types.position, Types.renderable).forEach(function(entity) {"
  255. "Registry.set(entity, Types.position, 100., 100.);"
  256. "});"
  257. "var entity = Registry.create();"
  258. "Registry.set(entity, Types.position, 100., 100.);"
  259. "Registry.set(entity, Types.renderable);"
  260. "";
  261. if(duk_peval_string(ctx, s1)) {
  262. FAIL();
  263. }
  264. ASSERT_EQ(registry.view<duktape_runtime>().size(), 0u);
  265. ASSERT_EQ(registry.view<position>().size(), 3u);
  266. ASSERT_EQ(registry.view<renderable>().size(), 2u);
  267. registry.view<position>().each([&registry](auto entity, const auto &position) {
  268. ASSERT_FALSE(registry.has<duktape_runtime>(entity));
  269. if(registry.has<renderable>(entity)) {
  270. ASSERT_EQ(position.x, 100.);
  271. ASSERT_EQ(position.y, 100.);
  272. } else {
  273. ASSERT_EQ(position.x, 0.);
  274. ASSERT_EQ(position.y, 0.);
  275. }
  276. });
  277. const char *s2 = ""
  278. "Registry.entities(Types.position).forEach(function(entity) {"
  279. "if(!Registry.has(entity, Types.renderable)) {"
  280. "Registry.set(entity, Types.VELOCITY, { \"dx\": -100., \"dy\": -100. });"
  281. "Registry.set(entity, Types.PLAYING_CHARACTER, {});"
  282. "}"
  283. "});"
  284. "";
  285. if(duk_peval_string(ctx, s2)) {
  286. FAIL();
  287. }
  288. ASSERT_EQ(registry.view<duktape_runtime>().size(), 1u);
  289. ASSERT_EQ(registry.view<position>().size(), 3u);
  290. ASSERT_EQ(registry.view<renderable>().size(), 2u);
  291. registry.view<duktape_runtime>().each([](const duktape_runtime &runtime) {
  292. ASSERT_EQ(runtime.components.size(), 2u);
  293. });
  294. const char *s3 = ""
  295. "Registry.entities(Types.position, Types.renderable, Types.VELOCITY, Types.PLAYING_CHARACTER).forEach(function(entity) {"
  296. "var velocity = Registry.get(entity, Types.VELOCITY);"
  297. "Registry.set(entity, Types.position, velocity.dx, velocity.dy)"
  298. "});"
  299. "";
  300. if(duk_peval_string(ctx, s3)) {
  301. FAIL();
  302. }
  303. ASSERT_EQ(registry.view<duktape_runtime>().size(), 1u);
  304. ASSERT_EQ(registry.view<position>().size(), 3u);
  305. ASSERT_EQ(registry.view<renderable>().size(), 2u);
  306. registry.view<position, renderable, duktape_runtime>().each([](const position &position, auto &&...) {
  307. ASSERT_EQ(position.x, -100.);
  308. ASSERT_EQ(position.y, -100.);
  309. });
  310. const char *s4 = ""
  311. "Registry.entities(Types.VELOCITY, Types.PLAYING_CHARACTER).forEach(function(entity) {"
  312. "Registry.unset(entity, Types.VELOCITY);"
  313. "Registry.unset(entity, Types.PLAYING_CHARACTER);"
  314. "});"
  315. "Registry.entities(Types.position).forEach(function(entity) {"
  316. "Registry.unset(entity, Types.position);"
  317. "});"
  318. "";
  319. if(duk_peval_string(ctx, s4)) {
  320. FAIL();
  321. }
  322. ASSERT_EQ(registry.view<duktape_runtime>().size(), 0u);
  323. ASSERT_EQ(registry.view<position>().size(), 0u);
  324. ASSERT_EQ(registry.view<renderable>().size(), 2u);
  325. duk_destroy_heap(ctx);
  326. }