From ef9fa96878fe182297d47ddab2afe1d23db55716 Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Fri, 29 Sep 2023 00:33:43 -0700 Subject: [PATCH] feat: system notify settings (#196) --- MODULE.bazel | 4 +- bazel/copts.bzl | 2 +- ecsact/interpret/eval.cc | 195 ++++++++++++++++++ ecsact/interpret/eval.h | 2 +- ecsact/interpret/eval_error.h | 18 +- .../parse-resolver-runtime.cc | 60 +++++- test/MODULE.bazel | 3 + test/errors/BUILD.bazel | 4 + test/errors/duplicate_notice_components.cc | 11 + .../errors/duplicate_notice_components.ecsact | 14 ++ test/errors/invalid_notify_settings.cc | 11 + test/errors/invalid_notify_settings.ecsact | 11 + .../unknown_component_notify_settings.cc | 12 ++ .../unknown_component_notify_settings.ecsact | 13 ++ test/test_lib.hh | 5 +- 15 files changed, 356 insertions(+), 9 deletions(-) create mode 100644 test/errors/duplicate_notice_components.cc create mode 100644 test/errors/duplicate_notice_components.ecsact create mode 100644 test/errors/invalid_notify_settings.cc create mode 100644 test/errors/invalid_notify_settings.ecsact create mode 100644 test/errors/unknown_component_notify_settings.cc create mode 100644 test/errors/unknown_component_notify_settings.ecsact diff --git a/MODULE.bazel b/MODULE.bazel index 2185e86..00a5354 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -7,5 +7,5 @@ module( bazel_dep(name = "rules_cc", version = "0.0.8") bazel_dep(name = "bazel_skylib", version = "1.4.2") bazel_dep(name = "magic_enum", version = "0.9.3") -bazel_dep(name = "ecsact_runtime", version = "0.5.1") -bazel_dep(name = "ecsact_parse", version = "0.3.0") +bazel_dep(name = "ecsact_runtime", version = "0.5.3") +bazel_dep(name = "ecsact_parse", version = "0.3.3") diff --git a/bazel/copts.bzl b/bazel/copts.bzl index 2a49358..51b6c1a 100644 --- a/bazel/copts.bzl +++ b/bazel/copts.bzl @@ -10,7 +10,7 @@ copts = selects.with_or({ "-fexperimental-library", ], ("@rules_cc//cc/compiler:msvc-cl", "@rules_cc//cc/compiler:clang-cl"): [ - "/std:c++latest", + "/std:c++20", "/permissive-", "/Zc:preprocessor", ], diff --git a/ecsact/interpret/eval.cc b/ecsact/interpret/eval.cc index 5e4d6f7..1aa04d7 100644 --- a/ecsact/interpret/eval.cc +++ b/ecsact/interpret/eval.cc @@ -15,6 +15,7 @@ #include "ecsact/parse.h" #include "ecsact/runtime/dynamic.h" #include "ecsact/runtime/meta.hh" +#include "ecsact/runtime/meta.h" #include "./detail/file_eval_error.hh" @@ -1100,6 +1101,13 @@ static ecsact_eval_error eval_system_component_statement( }; } + if(ecsact::meta::system_notify_settings_count(*sys_like_id) > 0) { + return ecsact_eval_error{ + .code = ECSACT_EVAL_ERR_NOTIFY_BEFORE_SYSTEM_COMPONENT, + .relevant_content = {}, + }; + } + auto& data = statement.data.system_component_statement; std::string comp_like_name( @@ -1308,6 +1316,181 @@ static ecsact_eval_error eval_system_with_entity_statement( return {}; } +static auto get_notify_setting_from_string( // + std::string_view setting_name +) -> std::optional { + static const auto notify_setting_name_map = + std::unordered_map{ + {"always"sv, ECSACT_SYS_NOTIFY_ALWAYS}, + {"oninit"sv, ECSACT_SYS_NOTIFY_ONINIT}, + {"onupdate"sv, ECSACT_SYS_NOTIFY_ONUPDATE}, + {"onchange"sv, ECSACT_SYS_NOTIFY_ONCHANGE}, + {"onremove"sv, ECSACT_SYS_NOTIFY_ONREMOVE}, + }; + + auto itr = notify_setting_name_map.find(setting_name); + if(itr != notify_setting_name_map.end()) { + return itr->second; + } + + return std::nullopt; +} + +static auto eval_system_notify_statement( + ecsact_package_id package_id, + std::span& context_stack, + const ecsact_statement& statement +) -> ecsact_eval_error { + auto [context, err] = expect_context( + context_stack, + { + ECSACT_STATEMENT_SYSTEM, + ECSACT_STATEMENT_ACTION, + } + ); + + if(err.code != ECSACT_EVAL_OK) { + return err; + } + + if(auto err = disallow_statement_params(statement, context)) { + return *err; + } + + auto sys_like_id = find_by_statement( // + package_id, + *context + ); + + if(ecsact::meta::system_notify_settings_count(*sys_like_id) > 0) { + return ecsact_eval_error{ + .code = ECSACT_EVAL_ERR_MULTIPLE_NOTIFY_STATEMENTS, + .relevant_content = {}, + }; + } + + auto data = statement.data.system_notify_statement; + + auto setting_name = std::string_view( // + data.setting_name.data, + data.setting_name.length + ); + + if(!setting_name.empty()) { + auto notify_setting = get_notify_setting_from_string(setting_name); + + if(!notify_setting) { + return ecsact_eval_error{ + .code = ECSACT_EVAL_ERR_INVALID_NOTIFY_SETTING, + .relevant_content = data.setting_name, + }; + } + + for(auto&& [comp_id, _] : ecsact::meta::system_capabilities(*sys_like_id)) { + ecsact_set_system_notify_component_setting( + *sys_like_id, + comp_id, + *notify_setting + ); + } + } + + return {}; +} + +static auto eval_system_notify_component_statement( + ecsact_package_id package_id, + std::span& context_stack, + const ecsact_statement& statement +) -> ecsact_eval_error { + if(context_stack.size() < 2) { + return ecsact_eval_error{ + .code = ECSACT_EVAL_ERR_INVALID_CONTEXT, + .relevant_content = {}, + }; + } + + auto [context, err] = + expect_context(context_stack, {ECSACT_STATEMENT_SYSTEM_NOTIFY}); + + if(err.code != ECSACT_EVAL_OK) { + return err; + } + + if(auto err = disallow_statement_params(statement, context)) { + return *err; + } + + auto block_setting_name = std::string_view( // + context->data.system_notify_statement.setting_name.data, + context->data.system_notify_statement.setting_name.length + ); + + auto data = statement.data.system_notify_component_statement; + + if(!block_setting_name.empty()) { + return ecsact_eval_error{ + .code = ECSACT_EVAL_ERR_NOTIFY_BLOCK_AND_COMPONENTS, + .relevant_content = data.setting_name, + .context_type = context->type, + }; + } + + auto sys_like_id = find_by_statement( // + package_id, + context_stack[context_stack.size() - 2] + ); + + auto comp_like_name = std::string( // + data.component_name.data, + data.component_name.length + ); + + auto comp_like_id = find_by_name( // + package_id, + comp_like_name + ); + + if(!comp_like_id) { + return ecsact_eval_error{ + .code = ECSACT_EVAL_ERR_UNKNOWN_COMPONENT_LIKE_TYPE, + .relevant_content = data.component_name, + }; + } + + auto setting_name = std::string_view( // + data.setting_name.data, + data.setting_name.length + ); + + auto notify_setting = get_notify_setting_from_string(setting_name); + + if(!notify_setting) { + return ecsact_eval_error{ + .code = ECSACT_EVAL_ERR_INVALID_NOTIFY_SETTING, + .relevant_content = data.setting_name, + }; + } + + for(auto&& [existing_comp_id, _] : + ecsact::meta::system_notify_settings(*sys_like_id)) { + if(existing_comp_id == *comp_like_id) { + return ecsact_eval_error{ + .code = ECSACT_EVAL_ERR_DUPLICATE_NOTIFY_COMPONENT, + .relevant_content = {}, + }; + } + } + + ecsact_set_system_notify_component_setting( + *sys_like_id, + *comp_like_id, + *notify_setting + ); + + return {}; +} + static ecsact_eval_error eval_entity_constraint_statement( ecsact_package_id package_id, std::span& context_stack, @@ -1484,6 +1667,18 @@ ecsact_eval_error ecsact_eval_statement( context_statements, statement ); + case ECSACT_STATEMENT_SYSTEM_NOTIFY: + return eval_system_notify_statement( + package_id, + context_statements, + statement + ); + case ECSACT_STATEMENT_SYSTEM_NOTIFY_COMPONENT: + return eval_system_notify_component_statement( + package_id, + context_statements, + statement + ); } return std::nullopt; diff --git a/ecsact/interpret/eval.h b/ecsact/interpret/eval.h index 976646c..51589c7 100644 --- a/ecsact/interpret/eval.h +++ b/ecsact/interpret/eval.h @@ -32,7 +32,7 @@ ecsact_eval_error ecsact_eval_statement( ); /** - * Clears memory of all previous successfully evaluated statements. + * @deprecated */ void ecsact_eval_reset(); diff --git a/ecsact/interpret/eval_error.h b/ecsact/interpret/eval_error.h index 1e0e2d9..7e14ff5 100644 --- a/ecsact/interpret/eval_error.h +++ b/ecsact/interpret/eval_error.h @@ -59,8 +59,24 @@ typedef enum ecsact_eval_error_code { /// Statement does not support any parameters yet parameters were given ECSACT_EVAL_ERR_PARAMETERS_NOT_ALLOWED, + /// Notify setting provided is invalid + ECSACT_EVAL_ERR_INVALID_NOTIFY_SETTING, + + /// More than 1 notify statements were found. Only 1 is permitted per system. + ECSACT_EVAL_ERR_MULTIPLE_NOTIFY_STATEMENTS, + + /// Notify block setting and component settings were found. Only one or the + /// other is permitted. + ECSACT_EVAL_ERR_NOTIFY_BLOCK_AND_COMPONENTS, + + /// The notify statement must be after all capability statements. + ECSACT_EVAL_ERR_NOTIFY_BEFORE_SYSTEM_COMPONENT, + + // More than one system notify component statement for the same component. + ECSACT_EVAL_ERR_DUPLICATE_NOTIFY_COMPONENT, + /// Internal error. Should not happen and is an indiciation of a bug. - ECSACT_EVAL_ERR_INTERNAL, + ECSACT_EVAL_ERR_INTERNAL = 999, /// Not an error code. Start of file only errors. /// File error codes only applies when parsing complete Ecsact files or diff --git a/parse-resolver-runtime/parse-resolver-runtime.cc b/parse-resolver-runtime/parse-resolver-runtime.cc index 4b4d3cf..939d3f6 100644 --- a/parse-resolver-runtime/parse-resolver-runtime.cc +++ b/parse-resolver-runtime/parse-resolver-runtime.cc @@ -53,11 +53,21 @@ struct system_like { ecsact_system_generate flag; }; - std::unordered_map caps; + using caps_t = std::unordered_map< // + ecsact_component_like_id, + cap_entry>; + caps_t caps; + using generates_t = std::unordered_map< ecsact_system_generates_id, std::unordered_map>; - generates_t generates; + generates_t generates; + + using notify_settings_t = std::unordered_map< // + ecsact_component_like_id, + ecsact_system_notify_setting>; + notify_settings_t notify_settings; + ecsact_system_like_id parent_system_id = (ecsact_system_like_id)-1; /** in execution order */ @@ -1173,3 +1183,49 @@ bool ecsact_meta_get_system_parallel_execution( // auto& def = get_system_like(system_like_id); return def.parallel_execution; } + +auto ecsact_meta_system_notify_settings_count( + ecsact_system_like_id system_like_id +) -> int32_t { + auto& def = get_system_like(system_like_id); + return static_cast(def.notify_settings.size()); +} + +auto ecsact_meta_system_notify_settings( + ecsact_system_like_id system_like_id, + int32_t max_notifies_count, + ecsact_component_like_id* out_notify_component_ids, + ecsact_system_notify_setting* out_notify_settings, + int32_t* out_notifies_count +) -> void { + auto& def = get_system_like(system_like_id); + + auto itr = def.notify_settings.begin(); + for(int i = 0; max_notifies_count > i; ++i) { + if(i >= def.notify_settings.size() || itr == def.notify_settings.end()) { + break; + } + + out_notify_component_ids[i] = itr->first; + out_notify_settings[i] = itr->second; + + ++itr; + } + + if(out_notifies_count != nullptr) { + *out_notifies_count = static_cast(def.notify_settings.size()); + } +} + +auto ecsact_set_system_notify_component_setting( + ecsact_system_like_id system_like_id, + ecsact_component_like_id component_like_id, + ecsact_system_notify_setting setting +) -> void { + auto& def = get_system_like(system_like_id); + if(setting == ECSACT_SYS_NOTIFY_NONE) { + def.notify_settings.erase(component_like_id); + } else { + def.notify_settings[component_like_id] = setting; + } +} diff --git a/test/MODULE.bazel b/test/MODULE.bazel index 6b30675..7b067b2 100644 --- a/test/MODULE.bazel +++ b/test/MODULE.bazel @@ -3,6 +3,9 @@ module(name = "ecsact_interpret_test") bazel_dep(name = "rules_cc", version = "0.0.8") bazel_dep(name = "bazel_skylib", version = "1.4.2") bazel_dep(name = "googletest", version = "1.14.0") +bazel_dep(name = "ecsact_parse", version = "0.3.3") +bazel_dep(name = "ecsact_runtime", version = "0.5.3") + bazel_dep(name = "bazel_sundry") bazel_dep(name = "ecsact_interpret") diff --git a/test/errors/BUILD.bazel b/test/errors/BUILD.bazel index ec3452a..52c1f59 100644 --- a/test/errors/BUILD.bazel +++ b/test/errors/BUILD.bazel @@ -1,10 +1,14 @@ load("@rules_cc//cc:defs.bzl", "cc_test") load("@ecsact_interpret//bazel:copts.bzl", "copts") +# buildifier: keep sorted _TESTS = [ + "duplicate_notice_components", + "invalid_notify_settings", "no_capabilities", "no_package_statement_first", "unknown_association_field", + "unknown_component_notify_settings", ] [cc_test( diff --git a/test/errors/duplicate_notice_components.cc b/test/errors/duplicate_notice_components.cc new file mode 100644 index 0000000..1921500 --- /dev/null +++ b/test/errors/duplicate_notice_components.cc @@ -0,0 +1,11 @@ +#include "gtest/gtest.h" +#include "ecsact/interpret/eval.h" + +#include "test_lib.hh" + +TEST(DuplicateNoticeComponents, DuplicateNoticeComponents) { + auto errs = + ecsact_interpret_test_files({"errors/duplicate_notice_components.ecsact"}); + ASSERT_EQ(errs.size(), 1); + ASSERT_EQ(errs[0].eval_error, ECSACT_EVAL_ERR_DUPLICATE_NOTIFY_COMPONENT); +} diff --git a/test/errors/duplicate_notice_components.ecsact b/test/errors/duplicate_notice_components.ecsact new file mode 100644 index 0000000..5a56619 --- /dev/null +++ b/test/errors/duplicate_notice_components.ecsact @@ -0,0 +1,14 @@ +package error.duplicate_notice_components; + +component ExampleComponent { + i32 num; +} + +system DuplicateNoticeComponents { + readwrite ExampleComponent; + + notify { + always ExampleComponent; + oninit ExampleComponent; + } +} diff --git a/test/errors/invalid_notify_settings.cc b/test/errors/invalid_notify_settings.cc new file mode 100644 index 0000000..48acbfe --- /dev/null +++ b/test/errors/invalid_notify_settings.cc @@ -0,0 +1,11 @@ +#include "gtest/gtest.h" +#include "ecsact/interpret/eval.h" + +#include "test_lib.hh" + +TEST(InvalidNotifySettings, InvalidNotifySettings) { + auto errs = + ecsact_interpret_test_files({"errors/invalid_notify_settings.ecsact"}); + ASSERT_EQ(errs.size(), 1); + ASSERT_EQ(errs[0].eval_error, ECSACT_EVAL_ERR_INVALID_NOTIFY_SETTING); +} diff --git a/test/errors/invalid_notify_settings.ecsact b/test/errors/invalid_notify_settings.ecsact new file mode 100644 index 0000000..6c714e0 --- /dev/null +++ b/test/errors/invalid_notify_settings.ecsact @@ -0,0 +1,11 @@ +package error.invalid_notify_settings; + +component ExampleComponent { + i32 num; +} + +system InvalidNotifySetting { + readwrite ExampleComponent; + + notify blah; +} diff --git a/test/errors/unknown_component_notify_settings.cc b/test/errors/unknown_component_notify_settings.cc new file mode 100644 index 0000000..53dfee2 --- /dev/null +++ b/test/errors/unknown_component_notify_settings.cc @@ -0,0 +1,12 @@ +#include "gtest/gtest.h" +#include "ecsact/interpret/eval.h" + +#include "test_lib.hh" + +TEST(UnknownComponentNotifySettings, UnknownComponentNotifySettings) { + auto errs = + ecsact_interpret_test_files({"errors/" + "unknown_component_notify_settings.ecsact"}); + ASSERT_EQ(errs.size(), 1); + ASSERT_EQ(errs[0].eval_error, ECSACT_EVAL_ERR_UNKNOWN_COMPONENT_LIKE_TYPE); +} diff --git a/test/errors/unknown_component_notify_settings.ecsact b/test/errors/unknown_component_notify_settings.ecsact new file mode 100644 index 0000000..e938028 --- /dev/null +++ b/test/errors/unknown_component_notify_settings.ecsact @@ -0,0 +1,13 @@ +package error.unknown_notify_settings; + +component ExampleComponent { + i32 num; +} + +system UnknownComponentNotifySettings { + readwrite ExampleComponent; + + notify { + always NonExistentComponent; + } +} diff --git a/test/test_lib.hh b/test/test_lib.hh index 3f4fdd2..1891f5b 100644 --- a/test/test_lib.hh +++ b/test/test_lib.hh @@ -7,8 +7,9 @@ #include "ecsact/interpret/eval.hh" #include "bazel_sundry/runfiles.hh" -auto ecsact_interpret_test_files(std::vector relative_file_paths) - -> std::vector { +inline auto ecsact_interpret_test_files( + std::vector relative_file_paths +) -> std::vector { auto runfiles = bazel_sundry::CreateDefaultRunfiles(); [&] { ASSERT_TRUE(runfiles); }();