diff --git a/ecsact/entt/detail/apply_pending.hh b/ecsact/entt/detail/apply_pending.hh index 72ad6df..2b94620 100644 --- a/ecsact/entt/detail/apply_pending.hh +++ b/ecsact/entt/detail/apply_pending.hh @@ -13,10 +13,11 @@ auto apply_pending_add(::entt::registry& registry) -> void { registry.emplace(entity); }); } else { - registry.view>().each( + registry.view>().each( // [&](auto entity, const pending_add& comp) { registry.emplace(entity, comp.value); - registry.emplace>(entity, comp.value, false); + registry + .emplace>(entity, comp.value, false); } ); } @@ -27,7 +28,7 @@ template auto apply_pending_remove(::entt::registry& registry) -> void { registry.view>().each([&](auto entity) { if constexpr(!std::is_empty_v) { - registry.erase>(entity); + registry.erase>(entity); } registry.erase(entity); }); diff --git a/ecsact/entt/detail/internal_markers.hh b/ecsact/entt/detail/internal_markers.hh index 4728f57..dda2f80 100644 --- a/ecsact/entt/detail/internal_markers.hh +++ b/ecsact/entt/detail/internal_markers.hh @@ -37,11 +37,17 @@ struct beforeremove_storage { template requires(!std::is_empty_v) -struct beforechange_storage { +struct exec_beforechange_storage { C value; bool has_update_occurred = false; }; +template + requires(!std::is_empty_v) +struct exec_itr_beforechange_storage { + C value; +}; + template struct pending_add; @@ -107,4 +113,18 @@ auto remove_system_markers_if_needed( // )"); } +template +auto add_exec_itr_beforechange_if_needed( // + ::entt::registry&, + ecsact::entt::entity_id +) -> void { + static_assert(system_markers_unimplemented_by_codegen, R"( + ----------------------------------------------------------------------------- +| (!) CODEGEN ERROR | +| `add_exec_itr_beforechange_if_needed<>` template specialization cannot be found | +| This is typically generated by ecsact_rt_entt_codegen. | + ----------------------------------------------------------------------------- +)"); +} + } // namespace ecsact::entt::detail diff --git a/ecsact/entt/wrapper/core.hh b/ecsact/entt/wrapper/core.hh index c8c45e3..bdcf1bb 100644 --- a/ecsact/entt/wrapper/core.hh +++ b/ecsact/entt/wrapper/core.hh @@ -59,7 +59,7 @@ inline auto add_component( // if constexpr(std::is_empty_v) { reg.emplace(entity); } else { - reg.emplace>( + reg.emplace>( entity, *static_cast(component_data), false @@ -68,6 +68,7 @@ inline auto add_component( // } ecsact::entt::detail::add_system_markers_if_needed(reg, entity); + ecsact::entt::detail::add_exec_itr_beforechange_if_needed(reg, entity); } return err; @@ -96,7 +97,7 @@ inline auto add_component_exec_options( // if constexpr(std::is_empty_v) { reg.emplace(entity); } else { - reg.emplace>( + reg.emplace>( entity, *static_cast(component_data) ); @@ -104,6 +105,7 @@ inline auto add_component_exec_options( // } reg.template emplace_or_replace>(entity); ecsact::entt::detail::add_system_markers_if_needed(reg, entity); + ecsact::entt::detail::add_exec_itr_beforechange_if_needed(reg, entity); } return err; @@ -172,7 +174,7 @@ auto remove_component( reg.remove(entity); if constexpr(!std::is_empty_v) { - reg.remove>(entity); + reg.remove>(entity); } reg.template remove>(entity); reg.template remove>(entity); @@ -205,7 +207,7 @@ auto remove_component_exec_options( reg.template emplace_or_replace>(entity); if constexpr(!std::is_empty_v) { - reg.template remove>(entity); + reg.template remove>(entity); } ecsact::entt::detail::remove_system_markers_if_needed(reg, entity); @@ -294,7 +296,7 @@ auto _trigger_update_component_event( ecsact::entt::detail::execution_events_collector& events_collector ) -> void { using ecsact::entt::component_updated; - using ecsact::entt::detail::beforechange_storage; + using ecsact::entt::detail::exec_beforechange_storage; if(!events_collector.has_update_callback()) { return; @@ -304,12 +306,13 @@ auto _trigger_update_component_event( if constexpr(!C::transient && !std::is_empty_v) { ::entt::basic_view changed_view{ reg.template storage(), - reg.template storage>(), + reg.template storage>(), reg.template storage>(), }; for(ecsact::entt::entity_id entity : changed_view) { - auto& before = changed_view.template get>(entity); + auto& before = + changed_view.template get>(entity); auto& current = changed_view.template get(entity); before.has_update_occurred = false; @@ -403,9 +406,33 @@ inline auto prepare_component(ecsact_registry_id registry_id) -> void { reg.template storage>(); if constexpr(!std::is_empty_v) { - reg.storage>(); + reg.storage>(); reg.template storage>(); } } +template +auto has_component_changed(entt::entity_id entity, V& view) -> bool { + using detail::exec_itr_beforechange_storage; + + const auto& current_comp = view.template get(entity); + const auto& before_comp = + view.template get>(entity); + + if(before_comp.value != current_comp) { + return true; + } + return false; +} + +template +auto update_exec_itr_beforechange(entt::entity_id entity, ::entt::registry& reg) + -> void { + auto comp = reg.get(entity); + auto& beforechange_comp = + reg.get>(entity); + + beforechange_comp.value = comp; +} + } // namespace ecsact::entt::wrapper::core diff --git a/ecsact/entt/wrapper/dynamic.hh b/ecsact/entt/wrapper/dynamic.hh index e45a245..a77ad93 100644 --- a/ecsact/entt/wrapper/dynamic.hh +++ b/ecsact/entt/wrapper/dynamic.hh @@ -20,8 +20,8 @@ auto context_add( ) -> void { using ecsact::entt::component_added; using ecsact::entt::component_removed; - using ecsact::entt::detail::beforechange_storage; using ecsact::entt::detail::beforeremove_storage; + using ecsact::entt::detail::exec_beforechange_storage; using ecsact::entt::detail::pending_add; assert(ecsact_id_cast(C::id) == component_id); @@ -34,7 +34,6 @@ auto context_add( } else { const C* component = static_cast(component_data); registry.template emplace_or_replace>(entity, *component); - registry.template remove>(entity); } @@ -54,8 +53,6 @@ auto component_add_trivial( ) -> void { using ecsact::entt::component_added; using ecsact::entt::component_removed; - using ecsact::entt::detail::beforechange_storage; - using ecsact::entt::detail::beforeremove_storage; using ecsact::entt::detail::pending_add; registry.template emplace_or_replace>(entity_id); @@ -146,7 +143,7 @@ auto context_update( const void* in_component_data ) -> void { using ecsact::entt::component_updated; - using ecsact::entt::detail::beforechange_storage; + using ecsact::entt::detail::exec_beforechange_storage; // TODO(Kelwan): for remove, beforeremove_storage auto entity = context->entity; @@ -155,7 +152,8 @@ auto context_update( const auto& in_component = *static_cast(in_component_data); auto& current_component = registry.template get(entity); - auto& beforechange = registry.template get>(entity); + auto& beforechange = + registry.template get>(entity); if(!beforechange.has_update_occurred) { beforechange.value = current_component; beforechange.has_update_occurred = true; diff --git a/rt_entt_codegen/core/BUILD.bazel b/rt_entt_codegen/core/BUILD.bazel index 40eea6d..20cc7cd 100644 --- a/rt_entt_codegen/core/BUILD.bazel +++ b/rt_entt_codegen/core/BUILD.bazel @@ -32,8 +32,10 @@ _CORE_CODEGEN_METHODS = { ], "system_markers": [ "//rt_entt_codegen/shared:sorting", + "//rt_entt_codegen/shared:system_util", ], "system_notify": ["//rt_entt_codegen/shared:system_util"], + "update_beforechange": [], } [cc_library( diff --git a/rt_entt_codegen/core/core.hh b/rt_entt_codegen/core/core.hh index 073a5b6..196cefc 100644 --- a/rt_entt_codegen/core/core.hh +++ b/rt_entt_codegen/core/core.hh @@ -66,6 +66,11 @@ auto print_system_marker_remove_fn( const ecsact_entt_details& details ) -> void; +auto print_add_sys_beforestorage_fn( + codegen_plugin_context& ctx, + const ecsact_entt_details& details +) -> void; + auto print_cleanup_system_notifies( codegen_plugin_context& ctx, const ecsact_entt_details& details @@ -76,4 +81,9 @@ auto print_entity_match_fn( const ecsact_entt_details& details ) -> void; +auto print_update_all_beforechange_storage( + codegen_plugin_context& ctx, + const ecsact_entt_details& details +) -> void; + } // namespace ecsact::rt_entt_codegen::core diff --git a/rt_entt_codegen/core/execute_systems.cc b/rt_entt_codegen/core/execute_systems.cc index ecc9e22..8bb2651 100644 --- a/rt_entt_codegen/core/execute_systems.cc +++ b/rt_entt_codegen/core/execute_systems.cc @@ -67,6 +67,9 @@ auto ecsact::rt_entt_codegen::core::print_execute_systems( // } } + ctx.write("\nupdate_all_beforechange_storage(registry_id);\n"); + ctx.write("cleanup_system_notifies(registry_id);\n"); + ctx.indentation -= 1; ctx.write("\n}\n\n"); @@ -88,9 +91,9 @@ auto ecsact::rt_entt_codegen::core::print_execute_systems( // "trigger_component_events_all(registry_id, events_collector);\n\n" ); }); - ctx.write("cleanup_component_events(registry_id);\n"); - ctx.write("cleanup_system_notifies(registry_id);\n"); }); + ctx.write("cleanup_component_events(registry_id);\n"); + ctx.write("return ECSACT_EXEC_SYS_OK;"); } diff --git a/rt_entt_codegen/core/system_markers.cc b/rt_entt_codegen/core/system_markers.cc index 9376cbd..7ee0aab 100644 --- a/rt_entt_codegen/core/system_markers.cc +++ b/rt_entt_codegen/core/system_markers.cc @@ -6,6 +6,7 @@ #include "ecsact/lang-support/lang-cc.hh" #include "ecsact/cpp_codegen_plugin_util.hh" #include "rt_entt_codegen/shared/sorting.hh" +#include "rt_entt_codegen/shared/system_util.hh" using ecsact::cpp_codegen_plugin_util::block; using ecsact::cpp_codegen_plugin_util::method_printer; @@ -93,3 +94,50 @@ auto ecsact::rt_entt_codegen::core::print_system_marker_remove_fn( ctx.write("//TODO\n"); } } + +auto ecsact::rt_entt_codegen::core::print_add_sys_beforestorage_fn( + codegen_plugin_context& ctx, + const ecsact_entt_details& details +) -> void { + using cc_lang_support::cpp_identifier; + using ecsact::meta::decl_full_name; + + auto already_printed_ = std::set{}; + + for(auto comp_id : details.all_components) { + auto comp_name = cpp_identifier(decl_full_name(comp_id)); + + ctx.write("template<>\n"); + auto printer = + method_printer{ + ctx, + std::format( + "ecsact::entt::detail::add_exec_itr_beforechange_if_needed< {}>", + comp_name + ) + } + .parameter("::entt::registry&", "reg") + .parameter("ecsact::entt::entity_id", "entity") + .return_type("void"); + + for(auto system_id : details.all_systems) { + auto notify_settings = ecsact::meta::system_notify_settings(system_id); + + for(auto const [comp_id_to_compare, notify_setting] : notify_settings) { + if(comp_id == static_cast(comp_id_to_compare)) { + if(notify_setting == ECSACT_SYS_NOTIFY_ONCHANGE) { + if(already_printed_.contains(comp_id)) { + break; + } + already_printed_.insert(comp_id); + ctx.write(std::format( // + "reg.emplace>(entity);\n", + comp_name + )); + break; + } + } + } + } + } +} diff --git a/rt_entt_codegen/core/update_beforechange.cc b/rt_entt_codegen/core/update_beforechange.cc new file mode 100644 index 0000000..8dc1f97 --- /dev/null +++ b/rt_entt_codegen/core/update_beforechange.cc @@ -0,0 +1,54 @@ +#include "core.hh" + +#include + +#include "ecsact/lang-support/lang-cc.hh" +#include "rt_entt_codegen/shared/util.hh" +#include "ecsact/runtime/common.h" +#include "ecsact/cpp_codegen_plugin_util.hh" + +auto ecsact::rt_entt_codegen::core::print_update_all_beforechange_storage( + codegen_plugin_context& ctx, + const ecsact_entt_details& details +) -> void { + using ecsact::cc_lang_support::c_identifier; + using ecsact::cc_lang_support::cpp_identifier; + using ecsact::cpp_codegen_plugin_util::block; + using ecsact::cpp_codegen_plugin_util::method_printer; + using ecsact::meta::decl_full_name; + + auto printer = // + method_printer{ctx, "update_all_beforechange_storage"} + .parameter("ecsact_registry_id", "registry_id") + .return_type("void"); + + ctx.write("auto& reg = ecsact::entt::get_registry(registry_id);\n\n"); + + for(auto comp_id : details.all_components) { + if(ecsact::meta::get_field_ids(comp_id).empty()) { + continue; + } + + auto comp_name = c_identifier(decl_full_name((comp_id))); + auto cpp_comp_name = cpp_identifier(decl_full_name(comp_id)); + auto comp_change_name = std::format( // + "ecsact::entt::detail::exec_itr_beforechange_storage<{}>", + cpp_comp_name + ); + + auto view_name = std::format("{}_view", comp_name); + auto comp_list = std::format("{}, {}", cpp_comp_name, comp_change_name); + + ctx.write( // + std::format("auto {} = reg.view<{}>();\n", view_name, comp_list) + ); + + block(ctx, std::format("for(auto entity: {})", view_name), [&]() { + ctx.write(std::format( + "ecsact::entt::wrapper::core::update_exec_itr_beforechange<{}>(entity, " + "reg);\n", + cpp_comp_name + )); + }); + } +} diff --git a/rt_entt_codegen/rt_entt_codegen.cc b/rt_entt_codegen/rt_entt_codegen.cc index 2e1b8e7..8703603 100644 --- a/rt_entt_codegen/rt_entt_codegen.cc +++ b/rt_entt_codegen/rt_entt_codegen.cc @@ -207,6 +207,7 @@ void ecsact_codegen_plugin( core::print_entity_match_fn(ctx, details); core::print_system_marker_add_fn(ctx, details); core::print_system_marker_remove_fn(ctx, details); + core::print_add_sys_beforestorage_fn(ctx, details); core::print_entity_sorting_components(ctx, details); core::print_check_error_template_specializations(ctx, details); core::print_execute_system_like_template_specializations(ctx, details); @@ -217,6 +218,7 @@ void ecsact_codegen_plugin( core::print_cleanup_ecsact_component_events(ctx, details); core::print_execution_options(ctx, details); core::print_cleanup_system_notifies(ctx, details); + core::print_update_all_beforechange_storage(ctx, details); core::print_execute_systems(ctx, details); } } diff --git a/rt_entt_codegen/shared/system_util.cc b/rt_entt_codegen/shared/system_util.cc index d86303c..5667e4c 100644 --- a/rt_entt_codegen/shared/system_util.cc +++ b/rt_entt_codegen/shared/system_util.cc @@ -2,6 +2,9 @@ #include #include +#include + +#include "ecsact/runtime/common.h" #include "rt_entt_codegen/shared/util.hh" auto ecsact::rt_entt_codegen::system_util::detail::is_notify_system( @@ -34,21 +37,38 @@ auto ecsact::rt_entt_codegen::system_util::detail::print_system_notify_views( using ecsact::cc_lang_support::c_identifier; using ecsact::cc_lang_support::cpp_identifier; using ecsact::cpp_codegen_plugin_util::block; - using ecsact::meta::component_name; using ecsact::meta::decl_full_name; auto notify_settings = ecsact::meta::system_notify_settings(system_id); + using notify_settings_pair_t = + std::pair; + + auto notify_settings_vec = std::vector( + notify_settings.begin(), + notify_settings.end() + ); + + std::sort( + notify_settings_vec.begin(), + notify_settings_vec.end(), + [](const notify_settings_pair_t a, notify_settings_pair_t b) -> bool { + if(a.second != ECSACT_SYS_NOTIFY_ONCHANGE) { + return true; + } + return false; + } + ); + auto system_name = cpp_identifier(decl_full_name(system_id)); - for(auto const [comp_id, notify_setting] : notify_settings) { + for(auto const& [comp_id, notify_setting] : notify_settings_vec) { if(notify_setting == ECSACT_SYS_NOTIFY_ALWAYS || notify_setting == ECSACT_SYS_NOTIFY_NONE) { break; } auto cpp_comp_name = cpp_identifier(decl_full_name(comp_id)); - auto comp_name = - c_identifier(component_name(static_cast(comp_id))); + auto comp_name = c_identifier(decl_full_name((comp_id))); auto run_system_comp = std::format("ecsact::entt::detail::run_system<{}>", system_name); @@ -120,8 +140,37 @@ auto ecsact::rt_entt_codegen::system_util::detail::print_system_notify_views( } if(notify_setting == ECSACT_SYS_NOTIFY_ONCHANGE) { - // TODO: Implement a component to be checked and added in another PR - // Added when component fields have changed during execution + auto exec_itr_onchange_str = std::format( + "ecsact::entt::detail::exec_itr_beforechange_storage<{}>", + cpp_comp_name + ); + + auto view_name = std::format("{}_change_view", comp_name); + + ecsact::rt_entt_codegen::util::make_view( + ctx, + view_name, + registry_name, + details, + std::vector{exec_itr_onchange_str}, + std::vector{run_system_comp} + ); + + block(ctx, std::format("for(auto entity: {})", view_name), [&]() { + block( + ctx, + std::format( + "if(!ecsact::entt::wrapper::core::has_component_changed<{}>(entity," + " registry))", + cpp_comp_name + ), + [&] { ctx.write("continue;\n"); } + ); + + ctx.write( + std::format("registry.emplace<{}>(entity);\n", run_system_comp) + ); + }); } } } diff --git a/test/runtime_test.cc b/test/runtime_test.cc index be6b408..aceda8c 100644 --- a/test/runtime_test.cc +++ b/test/runtime_test.cc @@ -212,6 +212,18 @@ void runtime_test::RemoveNotify::impl(context& ctx) { ctx.update(comp); } +void runtime_test::ChangeNotify::impl(context& ctx) { + auto comp = ctx.get(); + comp.val += 1; + ctx.update(comp); +} + +void runtime_test::MixedNotify::impl(context& ctx) { + auto comp = ctx.get(); + comp.val += 1; + ctx.update(comp); +} + TEST(Core, CreateRegistry) { auto reg_id = ecsact_create_registry("CreateRegistry"); EXPECT_NE(reg_id, ecsact_invalid_registry_id); @@ -1409,4 +1421,150 @@ TEST(Core, NotifyOnSystemUpdate) { ASSERT_EQ(notify_comp_a.val, 1); EXPECT_TRUE(event_happened) << "Init event did not happen"; + + ecsact_set_system_execution_impl( + ecsact_id_cast(runtime_test::TriggerUpdateNotify::id + ), + nullptr + ); + + ecsact_set_system_execution_impl( + ecsact_id_cast(runtime_test::UpdateNotify::id), + nullptr + ); +} + +TEST(Core, NotifyOnChange) { + using runtime_test::NotifyComponentA; + + auto reg = ecsact::core::registry("NotifyOnSystemUpdate"); + + auto entity = reg.create_entity(); + + auto notify_comp_a = NotifyComponentA{.val = 0}; + + auto exec_options = ecsact::core::execution_options{}; + auto evc = ecsact::core::execution_events_collector<>{}; + + ecsact_set_system_execution_impl( + ecsact_id_cast(runtime_test::ChangeNotify::id), + runtime_test__ChangeNotify + ); + + exec_options.add_component(entity, ¬ify_comp_a); + + auto error = reg.execute_systems(std::array{exec_options}); + ASSERT_EQ(error, ECSACT_EXEC_SYS_OK); + + notify_comp_a = reg.get_component(entity); + ASSERT_EQ(notify_comp_a.val, 0); + + exec_options.clear(); + exec_options.update_component(entity, ¬ify_comp_a); + + error = reg.execute_systems(std::array{exec_options}); + ASSERT_EQ(error, ECSACT_EXEC_SYS_OK); + + // Should not call on change + notify_comp_a = reg.get_component(entity); + ASSERT_EQ(notify_comp_a.val, 0); + + notify_comp_a.val = 5; + + exec_options.clear(); + exec_options.update_component(entity, ¬ify_comp_a); + + // System should update, increasing value by 1 to 6 + error = reg.execute_systems(std::array{exec_options}); + ASSERT_EQ(error, ECSACT_EXEC_SYS_OK); + + notify_comp_a = reg.get_component(entity); + ASSERT_EQ(notify_comp_a.val, 6); + + exec_options.clear(); + exec_options.update_component(entity, ¬ify_comp_a); + + error = reg.execute_systems(std::array{exec_options}); + ASSERT_EQ(error, ECSACT_EXEC_SYS_OK); + + notify_comp_a = reg.get_component(entity); + ASSERT_EQ(notify_comp_a.val, 6); + + ecsact_set_system_execution_impl( + ecsact_id_cast(runtime_test::ChangeNotify::id), + nullptr + ); +} + +TEST(Core, NotifyMixed) { + using runtime_test::Counter; + using runtime_test::NotifyComponentA; + using runtime_test::NotifyComponentB; + using runtime_test::NotifyComponentC; + + auto reg = ecsact::core::registry("NotifyMixed"); + + auto entity = reg.create_entity(); + + auto notify_comp_a = NotifyComponentA{.val = 0}; + auto notify_comp_b = NotifyComponentB{.val = 0}; + auto notify_comp_c = NotifyComponentC{.val = 0}; + auto counter = Counter{.val = 0}; + + auto exec_options = ecsact::core::execution_options{}; + + exec_options.add_component(entity, ¬ify_comp_a); + exec_options.add_component(entity, ¬ify_comp_b); + exec_options.add_component(entity, ¬ify_comp_c); + exec_options.add_component(entity, &counter); + + auto evc = ecsact::core::execution_events_collector<>{}; + + ecsact_set_system_execution_impl( + ecsact_id_cast(runtime_test::MixedNotify::id), + runtime_test__MixedNotify + ); + + auto error = reg.execute_systems(std::array{exec_options}); + ASSERT_EQ(error, ECSACT_EXEC_SYS_OK); + + counter = reg.get_component(entity); + ASSERT_EQ(counter.val, 1); + + reg.execute_systems(1); + + counter = reg.get_component(entity); + ASSERT_EQ(counter.val, 1); + + exec_options.clear(); + + notify_comp_a.val = 100; + + exec_options.update_component(entity, ¬ify_comp_a); + + error = reg.execute_systems(std::array{exec_options}); + ASSERT_EQ(error, ECSACT_EXEC_SYS_OK); + + counter = reg.get_component(entity); + ASSERT_EQ(counter.val, 2); + + reg.execute_systems(1); + + counter = reg.get_component(entity); + ASSERT_EQ(counter.val, 2); + + exec_options.clear(); + exec_options.remove_component(entity); + + error = reg.execute_systems(std::array{exec_options}); + ASSERT_EQ(error, ECSACT_EXEC_SYS_OK); + + counter = reg.get_component(entity); + ASSERT_EQ(counter.val, 3); + + reg.execute_systems(1); + ASSERT_EQ(error, ECSACT_EXEC_SYS_OK); + + counter = reg.get_component(entity); + ASSERT_EQ(counter.val, 3); } diff --git a/test/runtime_test.ecsact b/test/runtime_test.ecsact index 730a65c..fc7220d 100644 --- a/test/runtime_test.ecsact +++ b/test/runtime_test.ecsact @@ -245,3 +245,24 @@ system RemoveNotify { onremove NotifyComponentB; } } + +system ChangeNotify { + readwrite NotifyComponentA; + + notify onchange; +} + +component Counter { + i32 val; +} + +system MixedNotify { + readwrite Counter; + readwrite NotifyComponentA; + readonly NotifyComponentC; + notify { + onchange NotifyComponentA; + oninit NotifyComponentC; + onremove NotifyComponentB; + } +}