From d6e9d453072682cc5050e653bfec613ef64c61b4 Mon Sep 17 00:00:00 2001 From: Borislav Stanimirov Date: Tue, 4 Apr 2023 17:26:17 +0300 Subject: [PATCH 01/10] type mutation: simplify, no more relative info --- code/dnmx/type_mutation.h | 49 ++++----- code/dnmx/type_mutation_handle.h | 1 - code/dynamix/common_mutation_rules.hpp | 2 +- code/dynamix/domain.cpp | 12 +-- code/dynamix/mutate.hpp | 1 + code/dynamix/type_mutation-c.cpp | 68 +++--------- code/dynamix/type_mutation.cpp | 79 +++----------- code/dynamix/type_mutation.hpp | 138 +++++++------------------ test/t-mutation_rule.cpp | 17 ++- test/t-type.c | 51 ++++----- test/t-type_mutation.cpp | 132 ++++++++--------------- test/v1compat/tv1-core.cpp | 3 - 12 files changed, 167 insertions(+), 386 deletions(-) diff --git a/code/dnmx/type_mutation.h b/code/dnmx/type_mutation.h index 1de0459..efbdc95 100644 --- a/code/dnmx/type_mutation.h +++ b/code/dnmx/type_mutation.h @@ -24,37 +24,24 @@ DYNAMIX_API dnmx_type_mutation_handle dnmx_create_type_mutation_from_type(dnmx_t DYNAMIX_API void dnmx_destroy_unused_type_mutation(dnmx_type_mutation_handle hmut); DYNAMIX_API dnmx_domain_handle dnmx_type_mutation_get_domain(dnmx_type_mutation_handle hmut); -DYNAMIX_API dnmx_type_handle dnmx_type_mutation_get_base(dnmx_type_mutation_handle hmut); -DYNAMIX_API dnmx_type_template_handle dnmx_type_mutation_get_new_type(dnmx_type_mutation_handle hmut); - -DYNAMIX_API void dnmx_type_mutation_reset(dnmx_type_mutation_handle hmut); -DYNAMIX_API bool dnmx_type_mutation_is_noop(dnmx_type_mutation_handle hmut); -DYNAMIX_API bool dnmx_type_mutation_is_adding(dnmx_type_mutation_handle hmut, const dnmx_mixin_info* info); -DYNAMIX_API const dnmx_mixin_info* dnmx_type_mutation_is_adding_by_name(dnmx_type_mutation_handle hmut, dnmx_sv name); -DYNAMIX_API bool dnmx_type_mutation_is_adding_mixins(dnmx_type_mutation_handle hmut); -DYNAMIX_API bool dnmx_type_mutation_is_removing(dnmx_type_mutation_handle hmut, const dnmx_mixin_info* info); -DYNAMIX_API const dnmx_mixin_info* dnmx_type_mutation_is_removing_by_name(dnmx_type_mutation_handle hmut, dnmx_sv name); -DYNAMIX_API bool dnmx_type_mutation_is_removing_mixins(dnmx_type_mutation_handle hmut); - -// type template functions - -DYNAMIX_API bool dnmx_type_template_has(dnmx_type_template_handle ht, const dnmx_mixin_info* info); -DYNAMIX_API const dnmx_mixin_info* dnmx_type_template_has_by_name(dnmx_type_template_handle ht, dnmx_sv name); -DYNAMIX_API bool dnmx_type_template_implements_strong(dnmx_type_template_handle ht, const dnmx_feature_info* info); -DYNAMIX_API const dnmx_feature_info* dnmx_type_template_implements_strong_by_name(dnmx_type_template_handle ht, dnmx_sv name); -DYNAMIX_API bool dnmx_type_template_implements(dnmx_type_template_handle ht, const dnmx_feature_info* info); - -DYNAMIX_API bool dnmx_type_template_add(dnmx_type_template_handle ht, const dnmx_mixin_info* info); -DYNAMIX_API bool dnmx_type_template_add_if_lacking(dnmx_type_template_handle ht, const dnmx_mixin_info* info); - -DYNAMIX_API bool dnmx_type_template_remove(dnmx_type_template_handle ht, const dnmx_mixin_info* info); -DYNAMIX_API const dnmx_mixin_info* dnmx_type_template_remove_by_name(dnmx_type_template_handle ht, dnmx_sv name); -DYNAMIX_API bool dnmx_type_template_to_back(dnmx_type_template_handle ht, const dnmx_mixin_info* info); -DYNAMIX_API const dnmx_mixin_info* dnmx_type_template_to_back_by_name(dnmx_type_template_handle ht, dnmx_sv name); -DYNAMIX_API void dnmx_type_template_dedup(dnmx_type_template_handle ht); - -DYNAMIX_API const dnmx_mixin_info* const* dnmx_type_template_get_mixins(dnmx_type_template_handle ht, dnmx_mixin_index_t* out_num_mixins); -DYNAMIX_API bool dnmx_type_template_set_mixins(dnmx_type_template_handle ht, const dnmx_mixin_info* const* mixins, dnmx_mixin_index_t num_mixins); + +DYNAMIX_API bool dnmx_type_mutation_has(dnmx_type_mutation_handle hmut, const dnmx_mixin_info* info); +DYNAMIX_API const dnmx_mixin_info* dnmx_type_mutation_has_by_name(dnmx_type_mutation_handle hmut, dnmx_sv name); +DYNAMIX_API bool dnmx_type_mutation_implements_strong(dnmx_type_mutation_handle hmut, const dnmx_feature_info* info); +DYNAMIX_API const dnmx_feature_info* dnmx_type_mutation_implements_strong_by_name(dnmx_type_mutation_handle hmut, dnmx_sv name); +DYNAMIX_API bool dnmx_type_mutation_implements(dnmx_type_mutation_handle hmut, const dnmx_feature_info* info); + +DYNAMIX_API bool dnmx_type_mutation_add(dnmx_type_mutation_handle hmut, const dnmx_mixin_info* info); +DYNAMIX_API bool dnmx_type_mutation_add_if_lacking(dnmx_type_mutation_handle hmut, const dnmx_mixin_info* info); + +DYNAMIX_API bool dnmx_type_mutation_remove(dnmx_type_mutation_handle hmut, const dnmx_mixin_info* info); +DYNAMIX_API const dnmx_mixin_info* dnmx_type_mutation_remove_by_name(dnmx_type_mutation_handle hmut, dnmx_sv name); +DYNAMIX_API bool dnmx_type_mutation_to_back(dnmx_type_mutation_handle hmut, const dnmx_mixin_info* info); +DYNAMIX_API const dnmx_mixin_info* dnmx_type_mutation_to_back_by_name(dnmx_type_mutation_handle hmut, dnmx_sv name); +DYNAMIX_API void dnmx_type_mutation_dedup(dnmx_type_mutation_handle hmut); + +DYNAMIX_API const dnmx_mixin_info* const* dnmx_type_mutation_get_mixins(dnmx_type_mutation_handle hmut, dnmx_mixin_index_t* out_num_mixins); +DYNAMIX_API bool dnmx_type_mutation_set_mixins(dnmx_type_mutation_handle hmut, const dnmx_mixin_info* const* mixins, dnmx_mixin_index_t num_mixins); #if defined(__cplusplus) } diff --git a/code/dnmx/type_mutation_handle.h b/code/dnmx/type_mutation_handle.h index 8ce5743..c5e8147 100644 --- a/code/dnmx/type_mutation_handle.h +++ b/code/dnmx/type_mutation_handle.h @@ -4,4 +4,3 @@ #pragma once typedef struct dnmx_tag_type_mutation* dnmx_type_mutation_handle; -typedef struct dnmx_tag_type_template* dnmx_type_template_handle; diff --git a/code/dynamix/common_mutation_rules.hpp b/code/dynamix/common_mutation_rules.hpp index 010957b..0df491b 100644 --- a/code/dynamix/common_mutation_rules.hpp +++ b/code/dynamix/common_mutation_rules.hpp @@ -14,7 +14,7 @@ namespace impl { template error_return_t _apply_basic_mixin_dep(dnmx_type_mutation_handle mutation, uintptr_t) { auto mut = type_mutation::from_c_handle(mutation); - if (mut->new_type().has()) { + if (mut->has()) { mut->add_if_lacking(); } return result_success; diff --git a/code/dynamix/domain.cpp b/code/dynamix/domain.cpp index c54b311..c2a1f99 100644 --- a/code/dynamix/domain.cpp +++ b/code/dynamix/domain.cpp @@ -29,7 +29,7 @@ namespace dynamix { namespace { error_return_t sort_by_canonical_order(dnmx_type_mutation_handle mutation, uintptr_t) { - auto& mixins = type_mutation::from_c_handle(mutation)->mod_new_type().mixins; + auto& mixins = type_mutation::from_c_handle(mutation)->mixins; std::sort(mixins.begin(), mixins.end(), canonical_mixin_order{}); return result_success; } @@ -401,7 +401,7 @@ class domain::impl { type_query original_query(m_allocator); // store original query here to return type_query last_result(m_allocator); // store last rule application result here - auto& nt_mixins = mutation.mod_new_type().mixins; + auto& nt_mixins = mutation.mixins; last_result = nt_mixins; // first erase all deps from mixins @@ -439,7 +439,7 @@ class domain::impl { { // search for stored query for this combo auto reg = m_registry.shared_lock(); - auto f = reg->type_queries.find(mutation.new_type().mixins); + auto f = reg->type_queries.find(mutation.mixins); if (f != reg->type_queries.end()) return *f->second; // query is not available, so we need to apply mutation rules @@ -447,7 +447,7 @@ class domain::impl { original_query = apply_mutation_rules_l(mutation, reg->mutation_rules); // now look for exact type - found = find_exact_type_l(mutation.new_type().mixins, reg->types); + found = find_exact_type_l(mutation.mixins, reg->types); } if (found) { @@ -478,7 +478,7 @@ class domain::impl { // creating a mutation will run more or less the exact same as above again // TODO: optimize type_mutation mut(m_empty_type, m_allocator); - mut.mod_new_type().mixins.assign(mixins.begin(), mixins.end()); + mut.mixins.assign(mixins.begin(), mixins.end()); return get_type(mut); } @@ -623,7 +623,7 @@ class domain::impl { // create type for a given mutation requested by a given query const type& create_type(type_mutation& mutation, type_query&& query) { - itlib::span mixins(mutation.new_type().mixins); + itlib::span mixins(mutation.mixins); // first check validity for (size_t i = 0; i < mixins.size(); ++i) { diff --git a/code/dynamix/mutate.hpp b/code/dynamix/mutate.hpp index f3e4b53..536d130 100644 --- a/code/dynamix/mutate.hpp +++ b/code/dynamix/mutate.hpp @@ -4,6 +4,7 @@ #pragma once #include "mutate_ops.hpp" #include "mutate_to.hpp" +#include "domain.hpp" namespace dynamix { // regular mutate with ops as arguments diff --git a/code/dynamix/type_mutation-c.cpp b/code/dynamix/type_mutation-c.cpp index b42f77e..588bdd0 100644 --- a/code/dynamix/type_mutation-c.cpp +++ b/code/dynamix/type_mutation-c.cpp @@ -21,6 +21,7 @@ dnmx_type_mutation_handle dnmx_create_type_mutation_empty(dnmx_domain_handle hd) return nullptr; } } + dnmx_type_mutation_handle dnmx_create_type_mutation_from_type(dnmx_type_handle ht) { try { auto mut = new type_mutation(*type::from_c_handle(ht)); @@ -30,67 +31,32 @@ dnmx_type_mutation_handle dnmx_create_type_mutation_from_type(dnmx_type_handle h return nullptr; } } + void dnmx_destroy_unused_type_mutation(dnmx_type_mutation_handle hmut) { delete self; } dnmx_domain_handle dnmx_type_mutation_get_domain(dnmx_type_mutation_handle hmut) { - return self->get_domain().to_c_hanlde(); -} -dnmx_type_handle dnmx_type_mutation_get_base(dnmx_type_mutation_handle hmut) { - return &self->base(); -} -dnmx_type_template_handle dnmx_type_mutation_get_new_type(dnmx_type_mutation_handle hmut) { - return self->mod_new_type().to_c_hanlde(); -} - -void dnmx_type_mutation_reset(dnmx_type_mutation_handle hmut) { - self->reset(); -} -bool dnmx_type_mutation_is_noop(dnmx_type_mutation_handle hmut) { - return self->noop(); -} -bool dnmx_type_mutation_is_adding(dnmx_type_mutation_handle hmut, const dnmx_mixin_info* info) { - return self->adding(*info); + return self->dom.to_c_hanlde(); } -const dnmx_mixin_info* dnmx_type_mutation_is_adding_by_name(dnmx_type_mutation_handle hmut, dnmx_sv name) { - return self->adding(name.to_std()); -} -bool dnmx_type_mutation_is_adding_mixins(dnmx_type_mutation_handle hmut) { - return self->adding_mixins(); -} -bool dnmx_type_mutation_is_removing(dnmx_type_mutation_handle hmut, const dnmx_mixin_info* info) { - return self->removing(*info); -} -const dnmx_mixin_info* dnmx_type_mutation_is_removing_by_name(dnmx_type_mutation_handle hmut, dnmx_sv name) { - return self->removing(name.to_std()); -} -bool dnmx_type_mutation_is_removing_mixins(dnmx_type_mutation_handle hmut) { - return self->removing_mixins(); -} - -#undef self -#define self type_mutation::type_template::from_c_handle(ht) - -// type template functions -bool dnmx_type_template_has(dnmx_type_template_handle ht, const dnmx_mixin_info* info) { +bool dnmx_type_mutation_has(dnmx_type_mutation_handle hmut, const dnmx_mixin_info* info) { return self->has(*info); } -const dnmx_mixin_info* dnmx_type_template_has_by_name(dnmx_type_template_handle ht, dnmx_sv name) { +const dnmx_mixin_info* dnmx_type_mutation_has_by_name(dnmx_type_mutation_handle hmut, dnmx_sv name) { return self->has(name.to_std()); } -bool dnmx_type_template_implements_strong(dnmx_type_template_handle ht, const dnmx_feature_info* info) { +bool dnmx_type_mutation_implements_strong(dnmx_type_mutation_handle hmut, const dnmx_feature_info* info) { return self->implements_strong(*info); } -const dnmx_feature_info* dnmx_type_template_implements_strong_by_name(dnmx_type_template_handle ht, dnmx_sv name) { +const dnmx_feature_info* dnmx_type_mutation_implements_strong_by_name(dnmx_type_mutation_handle hmut, dnmx_sv name) { return self->implements_strong(name.to_std()); } -bool dnmx_type_template_implements(dnmx_type_template_handle ht, const dnmx_feature_info* info) { +bool dnmx_type_mutation_implements(dnmx_type_mutation_handle hmut, const dnmx_feature_info* info) { return self->implements(*info); } -bool dnmx_type_template_add(dnmx_type_template_handle ht, const dnmx_mixin_info* info) { +bool dnmx_type_mutation_add(dnmx_type_mutation_handle hmut, const dnmx_mixin_info* info) { try { self->add(*info); return true; @@ -99,7 +65,7 @@ bool dnmx_type_template_add(dnmx_type_template_handle ht, const dnmx_mixin_info* return false; } } -bool dnmx_type_template_add_if_lacking(dnmx_type_template_handle ht, const dnmx_mixin_info* info) { +bool dnmx_type_mutation_add_if_lacking(dnmx_type_mutation_handle hmut, const dnmx_mixin_info* info) { try { return self->add_if_lacking(*info); } @@ -107,13 +73,13 @@ bool dnmx_type_template_add_if_lacking(dnmx_type_template_handle ht, const dnmx_ return false; } } -bool dnmx_type_template_remove(dnmx_type_template_handle ht, const dnmx_mixin_info* info) { +bool dnmx_type_mutation_remove(dnmx_type_mutation_handle hmut, const dnmx_mixin_info* info) { return self->remove(*info); } -const dnmx_mixin_info* dnmx_type_template_remove_by_name(dnmx_type_template_handle ht, dnmx_sv name) { +const dnmx_mixin_info* dnmx_type_mutation_remove_by_name(dnmx_type_mutation_handle hmut, dnmx_sv name) { return self->remove(name.to_std()); } -bool dnmx_type_template_to_back(dnmx_type_template_handle ht, const dnmx_mixin_info* info) { +bool dnmx_type_mutation_to_back(dnmx_type_mutation_handle hmut, const dnmx_mixin_info* info) { try { self->to_back(*info); return true; @@ -122,7 +88,7 @@ bool dnmx_type_template_to_back(dnmx_type_template_handle ht, const dnmx_mixin_i return false; } } -const dnmx_mixin_info* dnmx_type_template_to_back_by_name(dnmx_type_template_handle ht, dnmx_sv name) { +const dnmx_mixin_info* dnmx_type_mutation_to_back_by_name(dnmx_type_mutation_handle hmut, dnmx_sv name) { try { return &self->to_back(name.to_std()); } @@ -130,17 +96,17 @@ const dnmx_mixin_info* dnmx_type_template_to_back_by_name(dnmx_type_template_han return nullptr; } } -void dnmx_type_template_dedup(dnmx_type_template_handle ht) { +void dnmx_type_mutation_dedup(dnmx_type_mutation_handle hmut) { self->dedup(); } -const dnmx_mixin_info* const* dnmx_type_template_get_mixins(dnmx_type_template_handle ht, dnmx_mixin_index_t* out_num_mixins) { +const dnmx_mixin_info* const* dnmx_type_mutation_get_mixins(dnmx_type_mutation_handle hmut, dnmx_mixin_index_t* out_num_mixins) { const auto& vec = self->mixins; *out_num_mixins = dnmx_mixin_index_t(vec.size()); return vec.data(); } -bool dnmx_type_template_set_mixins(dnmx_type_template_handle ht, const dnmx_mixin_info* const* mixins, dnmx_mixin_index_t num_mixins) { +bool dnmx_type_mutation_set_mixins(dnmx_type_mutation_handle hmut, const dnmx_mixin_info* const* mixins, dnmx_mixin_index_t num_mixins) { try { self->mixins.assign(mixins, mixins + num_mixins); return true; diff --git a/code/dynamix/type_mutation.cpp b/code/dynamix/type_mutation.cpp index 85c1fdf..9f552cb 100644 --- a/code/dynamix/type_mutation.cpp +++ b/code/dynamix/type_mutation.cpp @@ -18,20 +18,14 @@ namespace dynamix { type_mutation::type_mutation(domain& d, const allocator& alloc) noexcept - : type_mutation(d.get_empty_type(), alloc) + : dom(d) + , mixins(alloc) {} type_mutation::type_mutation(const type& base, const allocator& alloc) noexcept - : m_base(base) - , m_new_type(alloc) + : type_mutation(base.dom, alloc) { - reset(); -} - -domain& type_mutation::get_domain() const noexcept { return m_base.dom; } - -void type_mutation::reset() { - m_new_type.mixins.assign(m_base.mixins.begin(), m_base.mixins.end()); + mixins.assign(base.mixins.begin(), base.mixins.end()); } #define by_name [&](const auto* info) { return info->name == name; } @@ -43,30 +37,30 @@ static void do_to_back(compat::pmr::vector::iterator i, compa vec.push_back(val); } -void type_mutation::type_template::to_back(const mixin_info& info) { +void type_mutation::to_back(const mixin_info& info) { do_to_back(itlib::qfind(mixins, &info), mixins); } -const mixin_info& type_mutation::type_template::to_back(std::string_view name) { +const mixin_info& type_mutation::to_back(std::string_view name) { auto f = itlib::qfind_if(mixins, by_name); do_to_back(f, mixins); return *mixins.back(); } -bool type_mutation::type_template::remove(const mixin_info& info) noexcept { +bool type_mutation::remove(const mixin_info& info) noexcept { return itlib::erase_first(mixins, &info); } -const mixin_info* type_mutation::type_template::remove(std::string_view name) noexcept { +const mixin_info* type_mutation::remove(std::string_view name) noexcept { auto f = itlib::qfind_if(mixins, by_name); if (f == mixins.end()) return nullptr; auto ret = *f; mixins.erase(f); return ret; } -bool type_mutation::type_template::has(const mixin_info& info) const noexcept { +bool type_mutation::has(const mixin_info& info) const noexcept { return !!itlib::pfind(mixins, &info); } -const mixin_info* type_mutation::type_template::has(std::string_view name) const noexcept { +const mixin_info* type_mutation::has(std::string_view name) const noexcept { auto f = itlib::pfind_if(mixins, by_name); if (f) return *f; return nullptr; @@ -74,7 +68,7 @@ const mixin_info* type_mutation::type_template::has(std::string_view name) const #define fspan(features) itlib::make_stride_span_member_view(features.data(), features.size(), &feature_for_mixin::info) -bool type_mutation::type_template::implements_strong(const feature_info& info) const noexcept { +bool type_mutation::implements_strong(const feature_info& info) const noexcept { for (auto m : mixins) { auto features = m->features_span(); auto span = fspan(features); @@ -82,7 +76,7 @@ bool type_mutation::type_template::implements_strong(const feature_info& info) c } return false; } -const feature_info* type_mutation::type_template::implements_strong(std::string_view name) const noexcept { +const feature_info* type_mutation::implements_strong(std::string_view name) const noexcept { for (auto m : mixins) { auto features = m->features_span(); auto span = fspan(features); @@ -91,13 +85,13 @@ const feature_info* type_mutation::type_template::implements_strong(std::string_ } return nullptr; } -bool type_mutation::type_template::implements(const feature_info& info) const noexcept { +bool type_mutation::implements(const feature_info& info) const noexcept { if (info.default_payload) return true; return implements_strong(info); } const mixin_info* type_mutation::safe_add(std::string_view name) { - auto info = get_domain().get_mixin_info(name); + auto info = dom.get_mixin_info(name); if (!info) return nullptr; add(*info); return info; @@ -110,57 +104,18 @@ const mixin_info& type_mutation::add(std::string_view name) { } const mixin_info* type_mutation::safe_add_if_lacking(std::string_view name) { - auto f = itlib::pfind_if(m_new_type.mixins, by_name); + auto f = itlib::pfind_if(mixins, by_name); if (f) return nullptr; return safe_add(name); } const mixin_info* type_mutation::add_if_lacking(std::string_view name) { - auto f = itlib::pfind_if(m_new_type.mixins, by_name); + auto f = itlib::pfind_if(mixins, by_name); if (f) return nullptr; return &add(name); } -bool type_mutation::noop() const noexcept { - return std::equal(m_base.mixins.begin(), m_base.mixins.end(), m_new_type.mixins.begin(), m_new_type.mixins.end()); -} - -bool type_mutation::adding_mixins() const noexcept { - for (auto new_info : m_new_type.mixins) { - if (!m_base.has(new_info->id)) return true; - } - return false; -} - -bool type_mutation::removing_mixins() const noexcept { - for (auto new_info : m_base.mixins) { - if (!m_new_type.has(*new_info)) return true; - } - return false; -} - -bool type_mutation::adding(const mixin_info& info) const noexcept { - if (m_base.has(info)) return false; // already in base - return m_new_type.has(info); -} -const mixin_info* type_mutation::adding(std::string_view name) const noexcept { - if (m_base.has(name)) return nullptr; // already in base - return m_new_type.has(name); -} - -bool type_mutation::removing(const mixin_info& info) const noexcept { - if (!m_base.has(info)) return false; // not in base to remove - return !m_new_type.has(info); -} -const mixin_info* type_mutation::removing(std::string_view name) const noexcept { - auto bi = m_base.index_of(name); - if (bi == invalid_mixin_index) return nullptr; // not in base to remove - auto info = m_base.mixins[bi]; - if (m_new_type.has(*info)) return nullptr; // not removing - return info; -} - -void type_mutation::type_template::dedup() noexcept { +void type_mutation::dedup() noexcept { // first mark as null for (auto i = mixins.begin(); i != mixins.end(); ++i) { auto& info = *i; diff --git a/code/dynamix/type_mutation.hpp b/code/dynamix/type_mutation.hpp index 77eba2b..c82d647 100644 --- a/code/dynamix/type_mutation.hpp +++ b/code/dynamix/type_mutation.hpp @@ -8,7 +8,6 @@ #include "feature_info_fwd.hpp" #include "mixin_info_fwd.hpp" #include "globals.hpp" -#include "domain.from_info.hpp" #include "compat/pmr/vector.hpp" @@ -22,6 +21,9 @@ class type; // a class which represents a type mutation in progress class DYNAMIX_API type_mutation { public: + domain& dom; + compat::pmr::vector mixins; // mixins of the not yet materialized type + // start a mutation from the empty type // optionally provide an allocator for the type template explicit type_mutation(domain& d, const allocator& alloc = {}) noexcept; @@ -30,80 +32,9 @@ class DYNAMIX_API type_mutation { // optionally provide an allocator for the type template explicit type_mutation(const type& base, const allocator& alloc = {}) noexcept; - domain& get_domain() const noexcept; - - // base type with which the mutation was started - [[nodiscard]] const type& base() const noexcept { return m_base; } - - // resets the mutation to a noop - void reset(); - - // a not-yet materialized type - struct DYNAMIX_API type_template { - type_template(const allocator& alloc = allocator{}) noexcept - : mixins(alloc) - {} - compat::pmr::vector mixins; - - void add(const mixin_info& info) { mixins.push_back(&info); } - template - void add() { add(g::get_mixin_info()); } - - bool add_if_lacking(const mixin_info& info) { - if (has(info)) return false; - add(info); - return true; - } - template - void add_if_lacking() { add_if_lacking(g::get_mixin_info()); } - - void to_back(const mixin_info& info); - const mixin_info& to_back(std::string_view name); - template - void to_back() { to_back(g::get_mixin_info()); } - - // return false if missing - bool remove(const mixin_info& info) noexcept; - const mixin_info* remove(std::string_view name) noexcept; - template - bool remove() noexcept { return remove(g::get_mixin_info()); } - - // deduplicate mixins - // will leave the latter entry if a duplicate exists - void dedup() noexcept; - - // these are linear searches so they may be slow - // we don't have the indexes of a complete type here - [[nodiscard]] bool has(const mixin_info& info) const noexcept; - [[nodiscard]] const mixin_info* has(std::string_view name) const noexcept; - [[nodiscard]] bool lacks(const mixin_info& info) const noexcept { return !has(info); } - [[nodiscard]] bool lacks(std::string_view name) const noexcept { return !has(name); } - [[nodiscard]] bool implements_strong(const feature_info& info) const noexcept; - [[nodiscard]] const feature_info* implements_strong(std::string_view name) const noexcept; - [[nodiscard]] bool implements(const feature_info& info) const noexcept; - - template - [[nodiscard]] bool has() const noexcept { return has(g::get_mixin_info()); } - template - [[nodiscard]] bool lacks() const noexcept { return !has(); } - template - [[nodiscard]] bool implements_strong() const noexcept { return implements_strong(g::get_feature_info_fast()); } - template - [[nodiscard]] bool implements() const noexcept { return implements(g::get_feature_info_fast()); } - - dnmx_type_template_handle to_c_hanlde() noexcept { return reinterpret_cast(this); } - static type_template* from_c_handle(dnmx_type_template_handle ha) noexcept { return reinterpret_cast(ha); } - }; - - // deduced mixins for the new type to be produced by this mutation - // not a new type yet - [[nodiscard]] const type_template& new_type() const noexcept { return m_new_type; } - [[nodiscard]] type_template& mod_new_type() noexcept { return m_new_type; } - void dedup_new_type() noexcept { m_new_type.dedup(); } - // add mixin - void add(const mixin_info& info) { m_new_type.add(info); } - const mixin_info& add(std::string_view name); + void add(const mixin_info& info) { mixins.push_back(&info); } + const mixin_info& add(std::string_view name); // can throw bad_mutation const mixin_info* safe_add(std::string_view name); // no bad_mutation if name is not a registered mixin template const mixin_info& add() { @@ -113,8 +44,12 @@ class DYNAMIX_API type_mutation { } // add if not already there - bool add_if_lacking(const mixin_info& info) { return m_new_type.add_if_lacking(info); } - const mixin_info* add_if_lacking(std::string_view name); + bool add_if_lacking(const mixin_info& info) { + if (has(info)) return false; + add(info); + return true; + } + const mixin_info* add_if_lacking(std::string_view name); // can throw bad_mutation const mixin_info* safe_add_if_lacking(std::string_view name); // no bad_mutation if name is not a registered mixin template bool add_if_lacking() { @@ -122,41 +57,46 @@ class DYNAMIX_API type_mutation { return add_if_lacking(info); } - void to_back(const mixin_info& info) { m_new_type.to_back(info); } - const mixin_info& to_back(std::string_view name) { return m_new_type.to_back(name); } + // move mixin to back + void to_back(const mixin_info& info); + const mixin_info& to_back(std::string_view name); template void to_back() { to_back(g::get_mixin_info()); } - // remove mixin, silently ignore missing - bool remove(const mixin_info& info) noexcept { return m_new_type.remove(info); } - const mixin_info* remove(std::string_view name) noexcept { return m_new_type.remove(name); } + // return false nothing was removed + bool remove(const mixin_info& info) noexcept; + const mixin_info* remove(std::string_view name) noexcept; template bool remove() noexcept { return remove(g::get_mixin_info()); } - // check if the mutation is adding a mixin to the base type - [[nodiscard]] bool adding(const mixin_info& info) const noexcept; - [[nodiscard]] const mixin_info* adding(std::string_view name) const noexcept; - template - [[nodiscard]] bool adding() const noexcept { return adding(g::get_mixin_info()); } - [[nodiscard]] bool adding_mixins() const noexcept; // adding anything at all? + // deduplicate mixins + // will leave the latter entry if a duplicate exists + void dedup() noexcept; + + // queries for the not yet materialized type - // check if the mutation is removing a mixin from the base type - [[nodiscard]] bool removing(const mixin_info& info) const noexcept; - [[nodiscard]] const mixin_info* removing(std::string_view name) const noexcept; + // these are linear searches so they may be slow + // we don't have the indexes of a complete type here + [[nodiscard]] bool has(const mixin_info& info) const noexcept; + [[nodiscard]] const mixin_info* has(std::string_view name) const noexcept; + [[nodiscard]] bool lacks(const mixin_info& info) const noexcept { return !has(info); } + [[nodiscard]] bool lacks(std::string_view name) const noexcept { return !has(name); } + [[nodiscard]] bool implements_strong(const feature_info& info) const noexcept; + [[nodiscard]] const feature_info* implements_strong(std::string_view name) const noexcept; + [[nodiscard]] bool implements(const feature_info& info) const noexcept; + + template + [[nodiscard]] bool has() const noexcept { return has(g::get_mixin_info()); } template - [[nodiscard]] bool removing() const noexcept { return removing(g::get_mixin_info()); } - [[nodiscard]] bool removing_mixins() const noexcept; // removing anything at all? + [[nodiscard]] bool lacks() const noexcept { return !has(); } + template + [[nodiscard]] bool implements_strong() const noexcept { return implements_strong(g::get_feature_info_fast()); } + template + [[nodiscard]] bool implements() const noexcept { return implements(g::get_feature_info_fast()); } - // check if mutation is a noop - changes nothing - // note that the mutation may bot be adding or removing anything but only reordering - // in this case it it not a noop even though adding() and removing() will return false - [[nodiscard]] bool noop() const noexcept; dnmx_type_mutation_handle to_c_hanlde() noexcept { return reinterpret_cast(this); } static type_mutation* from_c_handle(dnmx_type_mutation_handle ha) noexcept { return reinterpret_cast(ha); } -private: - const type& m_base; - type_template m_new_type; }; // create a type mutation from list of mixins diff --git a/test/t-mutation_rule.cpp b/test/t-mutation_rule.cpp index 9d15e9c..dd67bb3 100644 --- a/test/t-mutation_rule.cpp +++ b/test/t-mutation_rule.cpp @@ -100,8 +100,7 @@ TEST_CASE("apply rules") { auto& td = mrtd->t; if (td.mesh->user_data == 111) return -1; auto mut = type_mutation::from_c_handle(mutation); - auto& nt = mut->new_type(); - if (nt.has(*td.actor) || nt.has(*td.mesh)) { + if (mut->has(*td.actor) || mut->has(*td.mesh)) { mut->add_if_lacking("tam"); } return result_success; @@ -121,10 +120,10 @@ TEST_CASE("apply rules") { if (td.movable->user_data == 666) throw std::logic_error("too many wheels"); mrtd->log.push_back(2); auto mut = type_mutation::from_c_handle(mutation); - if (mut->adding("wheeled") && mut->new_type().lacks(*td.movable)) { + if (mut->has("wheeled") && mut->lacks(*td.movable)) { mut->add(*td.movable); } - else if (mut->removing("wheeled")) { + else if (mut->lacks("wheeled")) { mut->remove(*td.movable); } return result_success; @@ -141,9 +140,9 @@ TEST_CASE("apply rules") { auto mrtd = reinterpret_cast(ud); mrtd->log.push_back(0); auto mut = type_mutation::from_c_handle(mutation); - itlib::erase_all_if(mut->mod_new_type().mixins, [](const mixin_info* info) { + itlib::erase_all_if(mut->mixins, [](const mixin_info* info) { return itlib::starts_with(info->name.to_std(), "empty"); - }); + }); return result_success; }; mutation_rule_info mri; @@ -276,10 +275,10 @@ TEST_CASE("rule interdependency") { mrtd->log.push_back(1); auto& td = mrtd->t; auto mut = type_mutation::from_c_handle(mutation); - if (mut->adding("wheeled")) { + if (mut->has("wheeled")) { mut->add_if_lacking(*td.movable); } - else if (mut->removing("wheeled")) { + else { mut->remove(*td.movable); } return result_success; @@ -298,7 +297,7 @@ TEST_CASE("rule interdependency") { mrtd->log.push_back(2); auto& td = mrtd->t; auto mut = type_mutation::from_c_handle(mutation); - if (mut->new_type().has(*td.movable)) { + if (mut->has(*td.movable)) { mut->add_if_lacking("tracker"); } return result_success; diff --git a/test/t-type.c b/test/t-type.c index 6d992d7..d4ed4de 100644 --- a/test/t-type.c +++ b/test/t-type.c @@ -204,38 +204,32 @@ void mutations(void) { { dnmx_type_mutation_handle mut = dnmx_create_type_mutation_empty(dom); - CHECK(dnmx_type_mutation_is_noop(mut)); - CHECK(dnmx_type_mutation_get_base(mut) == dnmx_get_empty_type(dom)); + dnmx_mixin_index_t num; + dnmx_type_mutation_get_mixins(mut, &num); + CHECK(num == 0); dnmx_destroy_unused_type_mutation(mut); } dnmx_type_handle t_aw; { dnmx_type_mutation_handle mut = dnmx_create_type_mutation_empty(dom); - dnmx_type_template_handle new_type = dnmx_type_mutation_get_new_type(mut); - CHECK(dnmx_type_template_add_if_lacking(new_type, &warrior)); - CHECK(dnmx_type_template_add(new_type, &athlete)); - CHECK_FALSE(dnmx_type_template_add_if_lacking(new_type, &warrior)); - CHECK(&warrior == dnmx_type_template_to_back_by_name(new_type, dnmx_make_sv_lit("warrior"))); + CHECK(dnmx_type_mutation_add_if_lacking(mut, &warrior)); + CHECK(dnmx_type_mutation_add(mut, &athlete)); + CHECK_FALSE(dnmx_type_mutation_add_if_lacking(mut, &warrior)); + CHECK(&warrior == dnmx_type_mutation_to_back_by_name(mut, dnmx_make_sv_lit("warrior"))); - CHECK(dnmx_type_template_has(new_type, &warrior)); - CHECK(&athlete == dnmx_type_template_has_by_name(new_type, dnmx_make_sv_lit("athlete"))); - CHECK(dnmx_type_template_implements_strong(new_type, &shoot)); - CHECK(&run == dnmx_type_template_implements_strong_by_name(new_type, dnmx_make_sv_lit("run"))); + CHECK(dnmx_type_mutation_has(mut, &warrior)); + CHECK(&athlete == dnmx_type_mutation_has_by_name(mut, dnmx_make_sv_lit("athlete"))); + CHECK(dnmx_type_mutation_implements_strong(mut, &shoot)); + CHECK(&run == dnmx_type_mutation_implements_strong_by_name(mut, dnmx_make_sv_lit("run"))); dnmx_mixin_index_t num; - const dnmx_mixin_info* const* infos = dnmx_type_template_get_mixins(new_type, &num); + const dnmx_mixin_info* const* infos = dnmx_type_mutation_get_mixins(mut, &num); CHECK(num == 2); CHECK(infos[0] == &athlete); CHECK(infos[1] == &warrior); - CHECK(dnmx_type_mutation_is_adding_mixins(mut)); - CHECK_FALSE(dnmx_type_mutation_is_removing_mixins(mut)); - CHECK(&athlete == dnmx_type_mutation_is_adding_by_name(mut, dnmx_make_sv_lit("athlete"))); - CHECK(dnmx_type_mutation_is_adding(mut, &warrior)); - CHECK_FALSE(dnmx_type_mutation_is_adding(mut, &shooter)); - t_aw = dnmx_get_type(dom, &mut); CHECK_FALSE(mut); CHECK(t_aw); @@ -244,23 +238,17 @@ void mutations(void) { dnmx_type_handle t_as; { dnmx_type_mutation_handle mut = dnmx_create_type_mutation_from_type(t_aw); - dnmx_type_template_handle new_type = dnmx_type_mutation_get_new_type(mut); - CHECK_FALSE(dnmx_type_template_remove_by_name(new_type, dnmx_make_sv_lit("shooter"))); - CHECK_FALSE(dnmx_type_template_remove(new_type, &shooter)); - CHECK(&warrior == dnmx_type_template_remove_by_name(new_type, dnmx_make_sv_lit("warrior"))); - CHECK(dnmx_type_template_add(new_type, &shooter)); - CHECK(dnmx_type_mutation_is_removing_mixins(mut)); - CHECK(dnmx_type_mutation_is_removing(mut, &warrior)); - CHECK(&warrior == dnmx_type_mutation_is_removing_by_name(mut, dnmx_make_sv_lit("warrior"))); - CHECK(dnmx_type_mutation_get_base(mut) == t_aw); + CHECK_FALSE(dnmx_type_mutation_remove_by_name(mut, dnmx_make_sv_lit("shooter"))); + CHECK_FALSE(dnmx_type_mutation_remove(mut, &shooter)); + CHECK(&warrior == dnmx_type_mutation_remove_by_name(mut, dnmx_make_sv_lit("warrior"))); + CHECK(dnmx_type_mutation_add(mut, &shooter)); t_as = dnmx_get_type(dom, &mut); } { dnmx_type_mutation_handle mut = dnmx_create_type_mutation_empty(dom); const dnmx_mixin_info* ar_aw[] = {&athlete, &warrior}; - dnmx_type_template_handle new_type = dnmx_type_mutation_get_new_type(mut); - dnmx_type_template_set_mixins(new_type, ar_aw, 2); + dnmx_type_mutation_set_mixins(mut, ar_aw, 2); CHECK(t_aw == dnmx_get_type(dom, &mut)); } @@ -279,9 +267,8 @@ typedef struct mr_dep { } mr_dep; dnmx_error_return_t apply_dep(dnmx_type_mutation_handle mut, uintptr_t user_data) { mr_dep* dep = (mr_dep*)(user_data); - dnmx_type_template_handle new_type = dnmx_type_mutation_get_new_type(mut); - if (dnmx_type_template_has(new_type, dep->primary)) { - dnmx_type_template_add_if_lacking(new_type, dep->dependent); + if (dnmx_type_mutation_has(mut, dep->primary)) { + dnmx_type_mutation_add_if_lacking(mut, dep->dependent); } return dnmx_result_success; } diff --git a/test/t-type_mutation.cpp b/test/t-type_mutation.cpp index c27702b..beeaa04 100644 --- a/test/t-type_mutation.cpp +++ b/test/t-type_mutation.cpp @@ -18,15 +18,10 @@ TEST_CASE("type_mutation from empty") { domain dom; type_mutation mut(dom); - CHECK(&dom == &mut.get_domain()); - CHECK(&dom.get_empty_type() == &mut.base()); - CHECK(mut.noop()); - CHECK_FALSE(mut.adding_mixins()); - CHECK_FALSE(mut.removing_mixins()); - auto& nt = mut.new_type(); - CHECK(nt.mixins.empty()); - CHECK(nt.implements(*t.serialize)); // default - CHECK_FALSE(nt.implements_strong(*t.serialize)); + CHECK(&dom == &mut.dom); + CHECK(mut.mixins.empty()); + CHECK(mut.implements(*t.serialize)); // default + CHECK_FALSE(mut.implements_strong(*t.serialize)); CHECK_THROWS_WITH_AS(mut.add("actor"), "missing mixin name", mutation_error); CHECK_THROWS_WITH_AS(mut.to_back("actor"), "missing mixin", mutation_error); @@ -37,31 +32,22 @@ TEST_CASE("type_mutation from empty") { mut.add(*t.ai); mut.add("actor"); - CHECK_FALSE(mut.noop()); - CHECK(mut.adding_mixins()); - CHECK_FALSE(mut.removing_mixins()); - - CHECK(mut.adding("ai")); - CHECK(mut.adding(*t.actor)); - CHECK(mut.adding(*t.mesh)); - - CHECK(nt.has(*t.ai)); - CHECK_FALSE(nt.has(*t.empty)); - CHECK(nt.lacks(*t.empty)); - CHECK(nt.implements_strong(*t.serialize)); - CHECK(nt.implements_strong("render")); + CHECK(mut.has(*t.ai)); + CHECK_FALSE(mut.has(*t.empty)); + CHECK(mut.lacks(*t.empty)); + CHECK(mut.implements_strong(*t.serialize)); + CHECK(mut.implements_strong("render")); // normalization mut.add("mesh"); - mut.dedup_new_type(); - CHECK(mut.new_type().mixins.size() == 3); - CHECK(mut.new_type().mixins.back() == t.mesh); - - mut.mod_new_type().add(*t.mesh); - CHECK(nt.mixins.size() == 4); - mut.dedup_new_type(); - CHECK(mut.new_type().mixins.size() == 3); - CHECK(nt.mixins.size() == 3); + mut.dedup(); + CHECK(mut.mixins.size() == 3); + CHECK(mut.mixins.back() == t.mesh); + + mut.add(*t.mesh); + CHECK(mut.mixins.size() == 4); + mut.dedup(); + CHECK(mut.mixins.size() == 3); } TEST_CASE("type_mutation from type") { @@ -72,45 +58,20 @@ TEST_CASE("type_mutation from type") { { type_mutation mut(*t.t_asim); - CHECK(mut.noop()); - CHECK_FALSE(mut.adding_mixins()); - CHECK_FALSE(mut.removing_mixins()); - CHECK(mut.new_type().implements_strong(*t.render)); - CHECK_FALSE(mut.new_type().implements_strong(*t.get_name)); + CHECK(mut.implements_strong(*t.render)); + CHECK_FALSE(mut.implements_strong(*t.get_name)); CHECK_FALSE(mut.add_if_lacking(*t.mesh)); - CHECK(mut.noop()); mut.remove("mesh"); - CHECK(mut.removing(*t.mesh)); - CHECK_FALSE(mut.removing(*t.stats)); + CHECK(mut.lacks(*t.mesh)); + CHECK_FALSE(mut.lacks(*t.stats)); mut.remove(*t.stats); - CHECK(mut.removing(*t.stats)); - CHECK_FALSE(mut.noop()); - CHECK_FALSE(mut.adding_mixins()); - CHECK(mut.removing_mixins()); + CHECK(mut.lacks(*t.stats)); CHECK_THROWS_WITH_AS(mut.to_back(*t.stats), "missing mixin", mutation_error); - CHECK_FALSE(mut.adding("actor")); + CHECK_FALSE(mut.has("actor")); CHECK(mut.add_if_lacking(*t.actor)); - CHECK(mut.adding("actor")); - CHECK_FALSE(mut.noop()); - CHECK(mut.adding_mixins()); - CHECK(mut.removing_mixins()); - CHECK(mut.new_type().implements_strong(*t.get_name)); - CHECK_FALSE(mut.new_type().implements_strong(*t.render)); - - mut.reset(); - CHECK(mut.noop()); - } - - { - // reorder - type_mutation mut(*t.t_asim); - mut.add(*t.ai); - CHECK_FALSE(mut.noop()); - CHECK_FALSE(mut.adding_mixins()); - CHECK_FALSE(mut.removing_mixins()); - - mut.reset(); - CHECK(mut.noop()); + CHECK(mut.has("actor")); + CHECK(mut.implements_strong(*t.get_name)); + CHECK_FALSE(mut.implements_strong(*t.render)); } } @@ -125,11 +86,6 @@ TEST_CASE("type_mutation misc") { mut.add(*t.actor); mut.add(*t.mesh); mut.to_back(*t.actor); - - CHECK_FALSE(mut.noop()); - CHECK(mut.adding_mixins()); - CHECK_FALSE(mut.removing_mixins()); - auto& ma = dom.get_type(std::move(mut)); CHECK(ma.has(*t.mesh)); @@ -151,10 +107,9 @@ TEST_CASE("type_mutation misc") { type_mutation mut(*t.t_afmi); mut.add(*t.actor); mut.add(*t.stats); - CHECK_FALSE(mut.removing_mixins()); mut.remove(*t.ai); - CHECK(mut.new_type().has("mesh")); + CHECK(mut.has("mesh")); mut.to_back(*t.mesh); mut.remove("flyer"); @@ -171,10 +126,7 @@ TEST_CASE("type_mutation misc") { { type_mutation mut(*t.t_asmpi); - mut.mod_new_type().mixins = {t.invisible, t.mesh, t.procedural_geometry, t.stats, t.actor}; - CHECK_FALSE(mut.adding_mixins()); - CHECK_FALSE(mut.removing_mixins()); - CHECK_FALSE(mut.noop()); + mut.mixins = {t.invisible, t.mesh, t.procedural_geometry, t.stats, t.actor}; auto& timpsa = dom.get_type(std::move(mut)); t.create_reordered_types(dom); @@ -194,18 +146,17 @@ TEST_CASE("type_mutation canon") { { type_mutation mut(*t.t_asim); mut.add(*t.ai); - mut.dedup_new_type(); - CHECK_FALSE(mut.noop()); - CHECK(&dom.get_type(mut.new_type().mixins) == t.t_asim); + mut.dedup(); + CHECK(&dom.get_type(mut.mixins) == t.t_asim); mut.remove(*t.stats); mut.remove(*t.ai); mut.add(*t.actor); mut.remove(*t.actor); mut.remove(*t.immaterial); - CHECK_FALSE(mut.adding(*t.actor)); - CHECK(mut.removing(*t.stats)); - CHECK_FALSE(mut.removing(*t.mesh)); + CHECK_FALSE(mut.has(*t.actor)); + CHECK(mut.lacks(*t.stats)); + CHECK_FALSE(mut.lacks(*t.mesh)); CHECK(&dom.get_type(std::move(mut)) == t.t_m); } @@ -216,24 +167,24 @@ TEST_CASE("type_mutation canon") { mut.add(*t.stats); mut.add(*t.immaterial); mut.add(*t.ai); - CHECK(&dom.get_type(mut.new_type().mixins) == t.t_asim); + CHECK(&dom.get_type(mut.mixins) == t.t_asim); mut.remove(*t.stats); mut.remove(*t.ai); mut.add(*t.actor); mut.remove(*t.actor); mut.remove(*t.immaterial); - CHECK_FALSE(mut.removing(*t.actor)); - CHECK_FALSE(mut.removing(*t.immaterial)); - CHECK(mut.adding("mesh")); - CHECK(&dom.get_type(mut.new_type().mixins) == t.t_m); + CHECK(mut.lacks(*t.actor)); + CHECK(mut.lacks(*t.immaterial)); + CHECK(mut.has("mesh")); + CHECK(&dom.get_type(mut.mixins) == t.t_m); mut.add(*t.ai); mut.add(*t.invisible); mut.add(*t.flyer); - CHECK(mut.adding("mesh")); - CHECK(mut.adding("flyer")); + CHECK(mut.has("mesh")); + CHECK(mut.has("flyer")); CHECK(&dom.get_type(std::move(mut)) == t.t_afmi); } @@ -241,8 +192,7 @@ TEST_CASE("type_mutation canon") { t.create_more_types(dom); type_mutation mut(*t.t_asmpi); - mut.mod_new_type().mixins = {t.invisible, t.mesh, t.procedural_geometry, t.stats, t.actor}; - CHECK_FALSE(mut.noop()); + mut.mixins = {t.invisible, t.mesh, t.procedural_geometry, t.stats, t.actor}; auto& tasmpi = dom.get_type(std::move(mut)); CHECK(t.t_asmpi == &tasmpi); diff --git a/test/v1compat/tv1-core.cpp b/test/v1compat/tv1-core.cpp index 478501a..ee3ac3e 100644 --- a/test/v1compat/tv1-core.cpp +++ b/test/v1compat/tv1-core.cpp @@ -196,9 +196,6 @@ TEST_CASE("type_template") { mut.add(); mut.add(); - CHECK(mut.adding()); - CHECK(mut.adding()); - const dynamix::type& type = dom.get_type(std::move(mut)); object o1(type); From 706e906847e6cc44abf4af0d49c8751d4ce209f0 Mon Sep 17 00:00:00 2001 From: Borislav Stanimirov Date: Tue, 4 Apr 2023 17:43:09 +0300 Subject: [PATCH 02/10] fixed gcc build --- code/dynamix/domain.cpp | 2 +- code/dynamix/mutate_to_ops.hpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/code/dynamix/domain.cpp b/code/dynamix/domain.cpp index c2a1f99..794dcc7 100644 --- a/code/dynamix/domain.cpp +++ b/code/dynamix/domain.cpp @@ -623,7 +623,7 @@ class domain::impl { // create type for a given mutation requested by a given query const type& create_type(type_mutation& mutation, type_query&& query) { - itlib::span mixins(mutation.mixins); + mixin_info_span mixins(mutation.mixins); // first check validity for (size_t i = 0; i < mixins.size(); ++i) { diff --git a/code/dynamix/mutate_to_ops.hpp b/code/dynamix/mutate_to_ops.hpp index 70d9f2a..31fa002 100644 --- a/code/dynamix/mutate_to_ops.hpp +++ b/code/dynamix/mutate_to_ops.hpp @@ -5,6 +5,7 @@ #include "object_mutate_ops.hpp" #include "mixin_info.hpp" #include "object.hpp" +#include "domain.hpp" #include From 216355b5453f9129dcdd16b75daf35c26bacf982 Mon Sep 17 00:00:00 2001 From: Borislav Stanimirov Date: Tue, 4 Apr 2023 18:10:22 +0300 Subject: [PATCH 03/10] v1compat: some mutation rule compatibility --- code/dynamix/domain.cpp | 2 +- doc/misc/migrating-from-v1.md | 2 +- test/v1compat/CMakeLists.txt | 1 + test/v1compat/tv1-mutation_rules.cpp | 238 ++++++++++++++++++ test/v1compat/tv1-type_classes.cpp | 23 +- v1compat/CMakeLists.txt | 4 + .../v1compat/common_mutation_rules.hpp | 90 +++++++ .../code/dynamix/v1compat/mutation_rule.hpp | 31 +++ .../code/dynamix/v1compat/mutation_rules.cpp | 64 +++++ v1compat/code/dynamix/v1compat/type_class.hpp | 1 - 10 files changed, 438 insertions(+), 18 deletions(-) create mode 100644 test/v1compat/tv1-mutation_rules.cpp create mode 100644 v1compat/code/dynamix/v1compat/common_mutation_rules.hpp create mode 100644 v1compat/code/dynamix/v1compat/mutation_rule.hpp create mode 100644 v1compat/code/dynamix/v1compat/mutation_rules.cpp diff --git a/code/dynamix/domain.cpp b/code/dynamix/domain.cpp index 794dcc7..6bcc372 100644 --- a/code/dynamix/domain.cpp +++ b/code/dynamix/domain.cpp @@ -425,7 +425,7 @@ class domain::impl { throw domain_error("rule interdependency too deep or cyclic"); } if (i == 0) { - // first time running the loop and rules made changes, store original querty to return + // first time running the loop and rules made changes, store original query to return original_query.swap(last_result); } last_result = nt_mixins; diff --git a/doc/misc/migrating-from-v1.md b/doc/misc/migrating-from-v1.md index 6aea30d..5595ab8 100644 --- a/doc/misc/migrating-from-v1.md +++ b/doc/misc/migrating-from-v1.md @@ -15,7 +15,7 @@ The compatibility library does not offer any solutions for the following differe * Unicast priority is inverted! This could've been fixed by the compatibily library, but it would have hidden potentially dangerous bugs. Instead `priority` for unicasts issues a warning, and `upriority` should be used when an instance is fixed and priority is inverted. * `objects::implements` will not work with `foo_msg` as an argument. Instead it must be called with a template argument `foo_msg_t`. * v1 allocators are not supported. -* v1-style mutation rules are not supported +* Custom v1-style mutation rules are not supported (though there is some support for the common ones) * When the declaring message overloads, either `DYNAMIX_MAKE_FUNC_TRAITS` must be used or the "original" message (i. e. at least one not declared as an overload for the same function name) must be visible. * v1 helpers like `object_type_template`, `same_type_mutator`, `single_object_mutator` are not available (and they don't make much sense in v2) * v1 multicast combinator calls are not immediately provided. Instead one must add the macro `DYNAMIX_V1_CREATE_COMBINATOR_CALL_` for the messages that need them. diff --git a/test/v1compat/CMakeLists.txt b/test/v1compat/CMakeLists.txt index 125524d..0bac70d 100644 --- a/test/v1compat/CMakeLists.txt +++ b/test/v1compat/CMakeLists.txt @@ -16,3 +16,4 @@ dynamix_v1compat_test(msg_default_impl tv1-msg_default_impl.cpp) dynamix_v1compat_test(bind_feature tv1-bind_feature.cpp) dynamix_v1compat_test(namespace tv1-namespace.cpp) dynamix_v1compat_test(type_classes tv1-type_classes.cpp) +dynamix_v1compat_test(mutation_rules tv1-mutation_rules.cpp) diff --git a/test/v1compat/tv1-mutation_rules.cpp b/test/v1compat/tv1-mutation_rules.cpp new file mode 100644 index 0000000..d2a5517 --- /dev/null +++ b/test/v1compat/tv1-mutation_rules.cpp @@ -0,0 +1,238 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include +#include +#include +#include + +#include + +DYNAMIX_V1_DECLARE_MIXIN(a); +DYNAMIX_V1_DECLARE_MIXIN(b); +DYNAMIX_V1_DECLARE_MIXIN(c); + +class a {}; +class b {}; +class c {}; + +DYNAMIX_V1_DEFINE_MIXIN(a, dynamix::v1compat::none); +DYNAMIX_V1_DEFINE_MIXIN(b, dynamix::v1compat::none); +DYNAMIX_V1_DEFINE_MIXIN(c, dynamix::v1compat::none); + +using namespace dynamix::v1compat; + +TEST_SUITE_BEGIN("mutation rules"); + +// v2!: custom rules are different +// simple dependency rule +class custom_rule : public mutation_rule { +public: + void apply_to(dynamix::type_mutation& mutation) override { + // v2!: mutation is different: no realtive checks + if (mutation.has()) { + mutation.add_if_lacking(); + } + else { + mutation.remove(); + } + } +}; + +TEST_CASE("custom rule") { + auto id = add_mutation_rule(new custom_rule()); + + CHECK(id == 0); + + object o; + + mutate(o) + .add() + .add(); + + CHECK(o.has()); + CHECK(o.has()); + CHECK(o.has()); + + mutate(o) + .remove(); + + CHECK(!o.has()); + CHECK(!o.has()); + CHECK(o.has()); + + auto rule = remove_mutation_rule(id); + + mutate(o) + .add(); + + CHECK(o.has()); + CHECK(!o.has()); + CHECK(o.has()); + + mutate(o) + .add(); + + CHECK(o.has()); + CHECK(o.has()); + CHECK(o.has()); + + id = add_mutation_rule(rule); + CHECK(id == 0); + + auto depr = std::make_shared>(); + id = add_mutation_rule(depr); + CHECK(id == 1); + + mutate(o) + .remove(); + + CHECK(o.empty()); + + CHECK(rule == remove_mutation_rule(0)); + + mutate(o) + .add() + .add(); + + CHECK(o.has()); + CHECK(!o.has()); + CHECK(!o.has()); + + rule.reset(); + CHECK(rule == remove_mutation_rule(123)); + + CHECK(depr == remove_mutation_rule(1)); +} + +TEST_CASE("dependent") { + auto rule = new dependent_mixins; + rule->set_master(); + rule->add(); + auto id = add_mutation_rule(rule); + + object o; + + mutate(o) + .add(); + + CHECK(o.has()); + CHECK(o.has()); + CHECK(!o.has()); + + mutate(o) + .remove(); + + // v2!: this relation is no longer possible + // b is alays bound to a + //CHECK(o.has()); + //CHECK(!o.has()); + //CHECK(!o.has()); + + //mutate(o) + // .add(); + + CHECK(o.has()); + CHECK(o.has()); + CHECK(!o.has()); + + mutate(o) + .remove(); + + CHECK(o.empty()); + + mutate(o) + .add() + .add(); + + CHECK(!o.has()); + // v2!: this relation is no longer possible: + // b is always bound to a + CHECK_FALSE(o.has()); + CHECK(o.has()); + + auto r = remove_mutation_rule(id); + CHECK(r.get() == rule); +} + +// v2!: bundles are not possible +//TEST_CASE("bundled") + +TEST_CASE("substitute") { + add_mutation_rule(new substitute_mixin()); + + object o; + + mutate(o) + .add() + .add(); + + CHECK(!o.has()); + CHECK(o.has()); + CHECK(o.has()); + + remove_mutation_rule(0); +} + +TEST_CASE("mutually exclusive") { + mutually_exclusive_mixins* rule = new mutually_exclusive_mixins; + + rule->add(); + rule->add(); + + add_mutation_rule(rule); + + object o; + + mutate(o) + .add() + .add(); + + CHECK(o.has()); + CHECK(!o.has()); + CHECK(o.has()); + + mutate(o).add(); + CHECK(!o.has()); + CHECK(o.has()); + CHECK(o.has()); + + mutate(o).add(); + CHECK(o.has()); + CHECK(!o.has()); + CHECK(o.has()); + + remove_mutation_rule(0); +} + +TEST_CASE("mandatory") { + auto id = add_mutation_rule(new mandatory_mixin()); + CHECK(id == 0); + + object o; + + mutate(o) + .add() + .add(); + + CHECK(o.has()); + CHECK(o.has()); + CHECK(o.has()); + + remove_mutation_rule(0); +} + +TEST_CASE("deprecated") { + add_mutation_rule(new deprecated_mixin()); + + object o; + + mutate(o) + .add() + .add() + .add(); + + CHECK(!o.has()); + CHECK(o.has()); + CHECK(o.has()); +} diff --git a/test/v1compat/tv1-type_classes.cpp b/test/v1compat/tv1-type_classes.cpp index a1672cf..2f7ff9f 100644 --- a/test/v1compat/tv1-type_classes.cpp +++ b/test/v1compat/tv1-type_classes.cpp @@ -4,7 +4,7 @@ #include #include -#include "doctest/doctest.h" +#include TEST_SUITE_BEGIN("type classes"); @@ -20,8 +20,7 @@ DYNAMIX_V1_DECLARE_MIXIN(tank); DYNAMIX_V1_DECLARE_MIXIN(pacifist); DYNAMIX_V1_DECLARE_MIXIN(cannon); -TEST_CASE("local") -{ +TEST_CASE("local") { using namespace dynamix::v1compat; object gc; mutate(gc).add().add(); @@ -75,8 +74,7 @@ DYNAMIX_V1_DEFINE_TYPE_CLASS(move_and_ghost_and_tank) { && type.has(); } -TEST_CASE("global") -{ +TEST_CASE("global") { using namespace dynamix::v1compat; object gt; mutate(gt).add().add(); // v2!: no matching_type_classes in type @@ -97,38 +95,33 @@ DYNAMIX_V1_DEFINE_MESSAGE(shoot); class ghost {}; DYNAMIX_V1_DEFINE_MIXIN(ghost, dynamix::v1compat::none); -class visible -{ +class visible { public: void draw() {} }; DYNAMIX_V1_DEFINE_MIXIN(visible, draw_msg); -class soldier -{ +class soldier { public: void move() {} void shoot() {} }; DYNAMIX_V1_DEFINE_MIXIN(soldier, move_msg & shoot_msg); -class tank -{ +class tank { public: void move() {} void shoot() {} }; DYNAMIX_V1_DEFINE_MIXIN(tank, move_msg & shoot_msg); -class pacifist -{ +class pacifist { public: void move() {} }; DYNAMIX_V1_DEFINE_MIXIN(pacifist, move_msg); -class cannon -{ +class cannon { public: void shoot() {} }; diff --git a/v1compat/CMakeLists.txt b/v1compat/CMakeLists.txt index 387f87d..432a4ff 100644 --- a/v1compat/CMakeLists.txt +++ b/v1compat/CMakeLists.txt @@ -55,8 +55,12 @@ target_sources(dynamix-v1compat PRIVATE code/dynamix/v1compat/domain.hpp code/dynamix/v1compat/domain.cpp code/dynamix/v1compat/mutate.hpp + code/dynamix/v1compat/mutation_rule.hpp + code/dynamix/v1compat/common_mutation_rules.hpp + code/dynamix/v1compat/mutation_rules.cpp code/dynamix/v1compat/next_bidder.hpp code/dynamix/v1compat/object.hpp code/dynamix/v1compat/object.cpp + code/dynamix/v1compat/type_class.hpp ${generatedFiles} ) diff --git a/v1compat/code/dynamix/v1compat/common_mutation_rules.hpp b/v1compat/code/dynamix/v1compat/common_mutation_rules.hpp new file mode 100644 index 0000000..807c148 --- /dev/null +++ b/v1compat/code/dynamix/v1compat/common_mutation_rules.hpp @@ -0,0 +1,90 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "mutation_rule.hpp" +#include +#include +#include +#include + +namespace dynamix::v1compat { +namespace impl { +class DYNAMIX_V1COMPAT_API mixin_list { +protected: + std::vector infos; +public: + void add(const mixin_info& info) { infos.push_back(&info); } + void add(std::string_view name); + template + void add() { add(g::get_mixin_info()); } +}; +} + +class dependent_mixins : public mutation_rule, public impl::mixin_list { + const mixin_info* master = nullptr; +public: + template + void set_master() { + master = &g::get_mixin_info(); + } + + virtual void apply_to(type_mutation& mut) override { + if (mut.has(*master)) { + for (auto i : infos) { + mut.add_if_lacking(*i); + } + } + else { + for (auto i : infos) { + mut.remove(*i); + } + } + } +}; + +class mutually_exclusive_mixins : public mutation_rule, public impl::mixin_list { +public: + virtual void apply_to(type_mutation& mut) override { + // we want to keep the last + // so reverse, keep the first, then reverse again + std::reverse(mut.mixins.begin(), mut.mixins.end()); + bool found = false; + itlib::erase_all_if(mut.mixins, [&](const mixin_info* i) { + auto f = itlib::pfind(infos, i); + if (!f) return false; // don't care about this one + if (found) return true; // erase if another mutually exclusive was found + found = true; + return false; // keep the first + }); + std::reverse(mut.mixins.begin(), mut.mixins.end()); + } +}; + +template +class deprecated_mixin : public mutation_rule { +public: + virtual void apply_to(type_mutation& mut) override { + mut.remove(); + } +}; + +template +class mandatory_mixin : public mutation_rule { +public: + virtual void apply_to(type_mutation& mut) override { + mut.add_if_lacking(); + } +}; + +template +class substitute_mixin : public mutation_rule { + virtual void apply_to(type_mutation& mut) override { + if (mut.has()) { + mut.remove(); + mut.add(); + } + } +}; + +} diff --git a/v1compat/code/dynamix/v1compat/mutation_rule.hpp b/v1compat/code/dynamix/v1compat/mutation_rule.hpp new file mode 100644 index 0000000..ad0274f --- /dev/null +++ b/v1compat/code/dynamix/v1compat/mutation_rule.hpp @@ -0,0 +1,31 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "api.h" +#include +#include + +namespace dynamix { +class type_mutation; +namespace v1compat { +class DYNAMIX_V1COMPAT_API mutation_rule { + mutation_rule_info m_info; +public: + mutation_rule() noexcept; + + mutation_rule(const mutation_rule&) = delete; + mutation_rule& operator=(const mutation_rule&) = delete; + + virtual ~mutation_rule(); + virtual void apply_to(type_mutation& mutation) = 0; + + const mutation_rule_info& info() const noexcept { return m_info; }; +}; + +using mutation_rule_id = uint32_t; +DYNAMIX_V1COMPAT_API mutation_rule_id add_mutation_rule(mutation_rule* rule); +DYNAMIX_V1COMPAT_API mutation_rule_id add_mutation_rule(std::shared_ptr rule); +DYNAMIX_V1COMPAT_API std::shared_ptr remove_mutation_rule(mutation_rule_id id); +} +} diff --git a/v1compat/code/dynamix/v1compat/mutation_rules.cpp b/v1compat/code/dynamix/v1compat/mutation_rules.cpp new file mode 100644 index 0000000..f987d9c --- /dev/null +++ b/v1compat/code/dynamix/v1compat/mutation_rules.cpp @@ -0,0 +1,64 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include "mutation_rule.hpp" +#include "common_mutation_rules.hpp" +#include "domain.hpp" +#include +#include +#include + +namespace dynamix::v1compat { + +namespace { +error_return_t mutation_rule_apply_to(dnmx_type_mutation_handle mutation, uintptr_t user_data) { + auto mut = type_mutation::from_c_handle(mutation); + auto rule = reinterpret_cast(user_data); + rule->apply_to(*mut); + return result_success; +} +struct registry { + std::vector> rules; +}; +itlib::data_mutex the_registry; +} + +mutation_rule::mutation_rule() noexcept { + m_info.name = {}; + m_info.apply = mutation_rule_apply_to; + m_info.order_priority = 0; + m_info.user_data = reinterpret_cast(this); +} + +mutation_rule::~mutation_rule() = default; + +mutation_rule_id add_mutation_rule(std::shared_ptr rule) { + auto l = the_registry.unique_lock(); + auto& rules = l->rules; + auto r = itlib::pfind(rules, nullptr); + if (!r) { + r = &rules.emplace_back(); + } + + auto& dom = domain::instance(); + dom.add_mutation_rule(rule->info()); + *r = std::move(rule); + return mutation_rule_id(r - rules.data()); +} + +mutation_rule_id add_mutation_rule(mutation_rule* rule) { + return add_mutation_rule(std::shared_ptr(rule)); +} + +std::shared_ptr remove_mutation_rule(mutation_rule_id id) { + auto l = the_registry.unique_lock(); + auto& rules = l->rules; + if (id >= rules.size()) return {}; + auto r = std::move(rules[id]); + if (!r) return {}; + auto& dom = domain::instance(); + dom.remove_mutation_rule(r->info()); + return r; +} + +} diff --git a/v1compat/code/dynamix/v1compat/type_class.hpp b/v1compat/code/dynamix/v1compat/type_class.hpp index 7ad2a81..6f769cf 100644 --- a/v1compat/code/dynamix/v1compat/type_class.hpp +++ b/v1compat/code/dynamix/v1compat/type_class.hpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MIT // #pragma once -#include "api.h" #include "domain.hpp" #include #include From edfaeec1f16960fa5babbc72a8cec60ed18adede Mon Sep 17 00:00:00 2001 From: Borislav Stanimirov Date: Wed, 5 Apr 2023 16:36:53 +0300 Subject: [PATCH 04/10] docs: non-features --- doc/README.md | 1 + doc/misc/non-features.md | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 doc/misc/non-features.md diff --git a/doc/README.md b/doc/README.md index 3025814..e88aa93 100644 --- a/doc/README.md +++ b/doc/README.md @@ -25,5 +25,6 @@ * [Differences Between v1 and v2](misc/v2-vs-v1.md) * [Migrating from v1](misc/migrating-from-v1.md) * Implementation Notes + * [Non-features](misc/non-features.md) * History and Inspiration * [Roadmap](roadmap.md) diff --git a/doc/misc/non-features.md b/doc/misc/non-features.md new file mode 100644 index 0000000..ab32b99 --- /dev/null +++ b/doc/misc/non-features.md @@ -0,0 +1,17 @@ +# Non-features + +This is a list of potential new features and improvements which have been considered and it was decided against them. + +## Internal messages + +An argument can be made that internal messages can be helpful in certain scenarios. The most common example here would be `on_mutated` - a multicast used to notify all mixins in an object that a mutation has happened and they could update potential internal relations that they have. `on_destroying` is another example called when the object is being destroyed. + +This would complicate the library, mixin infos, and object construction too much. Moreover we cannot possibly envision all service messages that one could need and going down the road of supporting this is not a good option. + +Instead this should be solved on a per-domain basis, by extending mutations, objects and mixins as needed. For example `on_mutated` can be part of a domain where objects should not be mutaded through `mutate` but instead through a custom function like `notifying_mutate` which calls the message. + +If a use case for an internal message is found, which cannot be implemented through extending the library, but can by modifying it internally, this non-feature should be reconsidered. + +## Object mutexes + +Object mutexes which would allow say mutating objects in a thread while messages are called in another can be added by extending the library. There is even a demo planned to showcase such extensions. This will not be an internal feature. From f4a5e543f7658a80fed978ebd4353525978a9554 Mon Sep 17 00:00:00 2001 From: Borislav Stanimirov Date: Wed, 5 Apr 2023 17:10:59 +0300 Subject: [PATCH 05/10] docs: danger --- doc/README.md | 1 + doc/advanced/.gitkeep | 0 doc/advanced/danger.md | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+) delete mode 100644 doc/advanced/.gitkeep create mode 100644 doc/advanced/danger.md diff --git a/doc/README.md b/doc/README.md index e88aa93..a4fa872 100644 --- a/doc/README.md +++ b/doc/README.md @@ -13,6 +13,7 @@ * Creating Plugins * Mutation Rules * Custom Features + * [Dangerous Functionalities](advanced/danger.md) * Working with DynaMix * [Performance](working-with/perf.md) * Thread Safety diff --git a/doc/advanced/.gitkeep b/doc/advanced/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/doc/advanced/danger.md b/doc/advanced/danger.md new file mode 100644 index 0000000..1369279 --- /dev/null +++ b/doc/advanced/danger.md @@ -0,0 +1,34 @@ +# Dangerous Functionalities + +Certain functionalities of the library are possible provided some constraints. They are deemed dangerous because the constraints cannot be enforced through code and wrapped in a safe API. Their use should be should be discouraged but in certain scenarios they can be helpful (and hopefully accompanied with at least a comment in the spirit of `// I know what I'm doing` ). + +## Accessing the object from a mixin's constructor or destructor + +Is it safe to do something with `dm_this` from a mixin constructor or destructor? + +The library takes care provide a valid object to mixins while they're constructed or destroyed. So `dm_this` and `object_of(this)` will lead to a valid object pointer. This is a part of DynaMix's "contract". This will not change. + +However *the type of the object is not reliable* at this point. As long as you don't touch the type, you're safe. Touching the type includes: + +* Naturally, doing anything with the result of `object::get_type` +* Querying the object for mixins or features +* Accessing other mixins of the object +* Executing feature implementations (including calling messages) +* Mutating the object + +Accessing the object without touching the type can certainly be useful in many scenarios and should not be a big no-no. Sadly the touching of the type cannot be safeguarded in any meaningful way. Moreover, going further into the rabit hole of borderline undefined behavior, touching the type in *"certain ways"* *may*, in some cases, be safe. + +Here is an incomplete list of what would likely work, but doing anything from that list *should* be considered a big no-no and should only be done if nothing else works. + +Also note that anything after this line is not explicitly supported. Future versions of the library may make any of these unsafe again. + +* When the object is not mutated, but copied or moved, touching the type may be safe. Touching mixins which are constructed (the ones before the current one) should also be safe. +* When the object is destroyed, accessing the type from a mixin destructor should be safe, but note that other mixins of the object may already be destroyed - the ones after the current one since the destruction order is the oposite of the construction order +* When the object is mutated the result of `object::get_type` in a mixin's constructor or destrutor would be the old object type - the one it's mutated from. This may be what one needs, who knows. Queries on the object will be equivalent to queries before the mutation took place. +* Accessing other mixins of the object which are *external* should be safe, as long as the above is taken into consideration: the type is the old type + +## Mutating an object within a message + +Is it safe to call `mutate(dm_this`? The answer is a definitive "maybe". + +*TBD* From e192cbde5f8bc65f44c4d14ccfe003938ce9bbe1 Mon Sep 17 00:00:00 2001 From: Borislav Stanimirov Date: Thu, 6 Apr 2023 06:51:48 +0300 Subject: [PATCH 06/10] docs: mutate from msg --- doc/advanced/danger.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/doc/advanced/danger.md b/doc/advanced/danger.md index 1369279..4d9e66b 100644 --- a/doc/advanced/danger.md +++ b/doc/advanced/danger.md @@ -1,34 +1,43 @@ # Dangerous Functionalities -Certain functionalities of the library are possible provided some constraints. They are deemed dangerous because the constraints cannot be enforced through code and wrapped in a safe API. Their use should be should be discouraged but in certain scenarios they can be helpful (and hopefully accompanied with at least a comment in the spirit of `// I know what I'm doing` ). +Certain functionalities of the library are possible provided some constraints. They are deemed dangerous because the constraints cannot be enforced through code and wrapped in a safe API. Their use is discouraged but in certain scenarios they can be helpful (and hopefully accompanied with at least a comment in the spirit of `// I know what I'm doing` ). ## Accessing the object from a mixin's constructor or destructor Is it safe to do something with `dm_this` from a mixin constructor or destructor? -The library takes care provide a valid object to mixins while they're constructed or destroyed. So `dm_this` and `object_of(this)` will lead to a valid object pointer. This is a part of DynaMix's "contract". This will not change. +The library takes care to provide a valid object to mixins while they're constructed or destroyed. So `dm_this` and `object_of(this)` will lead to a valid object pointer. This is a part of DynaMix's contract and will not change. However *the type of the object is not reliable* at this point. As long as you don't touch the type, you're safe. Touching the type includes: * Naturally, doing anything with the result of `object::get_type` * Querying the object for mixins or features * Accessing other mixins of the object -* Executing feature implementations (including calling messages) +* Executing feature implementations (like calling messages) * Mutating the object -Accessing the object without touching the type can certainly be useful in many scenarios and should not be a big no-no. Sadly the touching of the type cannot be safeguarded in any meaningful way. Moreover, going further into the rabit hole of borderline undefined behavior, touching the type in *"certain ways"* *may*, in some cases, be safe. +Accessing the object without touching the type can certainly be useful in many scenarios and should not be a big no-no. Sadly this cannot be safeguarded in any meaningful way. Moreover, going further into the rabit hole of borderline undefined behavior, touching the type in *"certain ways"* *may* in some cases be safe. Here is an incomplete list of what would likely work, but doing anything from that list *should* be considered a big no-no and should only be done if nothing else works. Also note that anything after this line is not explicitly supported. Future versions of the library may make any of these unsafe again. -* When the object is not mutated, but copied or moved, touching the type may be safe. Touching mixins which are constructed (the ones before the current one) should also be safe. -* When the object is destroyed, accessing the type from a mixin destructor should be safe, but note that other mixins of the object may already be destroyed - the ones after the current one since the destruction order is the oposite of the construction order -* When the object is mutated the result of `object::get_type` in a mixin's constructor or destrutor would be the old object type - the one it's mutated from. This may be what one needs, who knows. Queries on the object will be equivalent to queries before the mutation took place. -* Accessing other mixins of the object which are *external* should be safe, as long as the above is taken into consideration: the type is the old type +* When the object is not mutated, but copied or moved, touching the type may be safe. Getting mixins which are constructed (the ones before the current one) should also be safe. +* When the object is destroyed, accessing the type from a mixin destructor should be safe, but note that other mixins of the object may already be destroyed - the ones after the current one since the destruction order is the opposite of the construction order +* When the object is mutated the result of `object::get_type` in a mixin's constructor or destrutor would be the old object type - the one it's mutated from. This may be what one needs. Who knows? Queries on the object will be equivalent to queries before the mutation took place. +* In a mutation accessing other mixins of the object which are *external* should be safe, as long as the above is taken into consideration: the type is the old type. ## Mutating an object within a message Is it safe to call `mutate(dm_this`? The answer is a definitive "maybe". -*TBD* +Generally the precautions needed to be taken to mutate the object from a message's implementation are the same as the ones one would take to call `delete this`. + +If the mutation is the last thing to happen when the message is called, it's safe. This is supported behavior. + +If the mutation is in a default implementation, it's safe. It doesn't even have to be the last thing to happen in the function. + +The thing that is definitely not safe and *will* lead to undefined behavoir and crashes is mutating the object from a message's implementation and continue with other implementations in the same invocation, such next implementer calls or simply being a part of a multiacst chain where the current implementation is not the last one. + +If the current implementer is an *external* mixin, the mutation does not have to be the last thing to happen in the function, as long as it does not remove that mixin from the object (removing self from an object would be exactly equivalent to `delete this`). This however should be considered bad practice. If you absolutely need to do this, you should guard it with at least asserts that the current mixin is external. + From e2f3acea560e6ba70cfbcb5b4e0f225450a7e5c7 Mon Sep 17 00:00:00 2001 From: Borislav Stanimirov Date: Thu, 6 Apr 2023 07:09:30 +0300 Subject: [PATCH 07/10] tests: dangerous functionalities --- test/CMakeLists.txt | 2 ++ test/t-danger.cpp | 88 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 test/t-danger.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 592ed58..c7d0494 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -89,6 +89,8 @@ dynamix_test(define_mixin t-define_mixin.cpp) dynamix_test(msg t-msg.cpp) +dynamix_test(danger t-danger.cpp) + if(DYNAMIX_SHARED) add_subdirectory(app-plugin) endif() diff --git a/test/t-danger.cpp b/test/t-danger.cpp new file mode 100644 index 0000000..3862b2c --- /dev/null +++ b/test/t-danger.cpp @@ -0,0 +1,88 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include +#include +#include +#include +#include +#include +#include + +#include + +DYNAMIX_DECLARE_DOMAIN(test); + +class test_obj; + +DYNAMIX_DECLARE_MSG(mutate_self_msg, mutate_self, void, (test_obj&)); + +class test_obj : public dynamix::object { +public: + int data; + + test_obj(int data) : dynamix::object(dynamix::g::get_domain()), data(data) {} + + static test_obj* of(void* mixin) { + return static_cast(dynamix::object_of(mixin)); + } + static const test_obj* of(const void* mixin) { + return static_cast(dynamix::object_of(mixin)); + } +}; + +#define t_this ::test_obj::of(this) + +DYNAMIX_DECLARE_MIXIN(struct access_object); +DYNAMIX_DECLARE_MIXIN(struct dummy); + +struct access_object { + int change; + + access_object(int expected, int change) : change(change) { + CHECK(t_this->data == expected); + t_this->data = change; + } + + ~access_object() { + CHECK(t_this->data == change); + } +}; + +TEST_CASE("access object") { + test_obj obj(5); + mutate(obj, dynamix::add(5, 10)); + CHECK(obj.data == 10); + mutate(obj, dynamix::add()); + CHECK(obj.data == 10); +} + +DYNAMIX_DECLARE_MIXIN(struct self_mutator); + +TEST_CASE("mutate self") { + test_obj obj(10); + mutate(obj, dynamix::add()); + mutate_self(obj); + CHECK(obj.has()); +} + +struct self_mutator { + void mutate_self() { + mutate(*t_this, dynamix::add()); + } +}; + +struct dummy {}; + +#include +#include +#include +#include + +DYNAMIX_DEFINE_DOMAIN(test); +DYNAMIX_DEFINE_MIXIN(test, access_object); +DYNAMIX_DEFINE_MIXIN(test, self_mutator).implements(); +DYNAMIX_DEFINE_MIXIN(test, dummy); + +DYNAMIX_MAKE_FUNC_TRAITS(mutate_self); +DYNAMIX_DEFINE_MSG(mutate_self_msg, unicast, mutate_self, void, (test_obj&)); From 35bbc013797ba179dca5623511549b13bb04df2e Mon Sep 17 00:00:00 2001 From: Borislav Stanimirov Date: Thu, 6 Apr 2023 07:24:22 +0300 Subject: [PATCH 08/10] docs: rename intro to overview --- doc/README.md | 2 +- doc/basics/glossary.md | 6 ++++-- doc/intro.md | 0 doc/overview.md | 5 +++++ 4 files changed, 10 insertions(+), 3 deletions(-) delete mode 100644 doc/intro.md create mode 100644 doc/overview.md diff --git a/doc/README.md b/doc/README.md index a4fa872..cb228e5 100644 --- a/doc/README.md +++ b/doc/README.md @@ -2,7 +2,7 @@ *The documentation is still in progress* -* [Introduction](intro.md) +* [Overview](overview.md) * Basics * Adding DynaMix to a Project * [Elements and Glossary](basics/glossary.md) diff --git a/doc/basics/glossary.md b/doc/basics/glossary.md index 66851f4..1f096f6 100644 --- a/doc/basics/glossary.md +++ b/doc/basics/glossary.md @@ -1,13 +1,15 @@ # Glossary +## Mixin + +A mixin is a building block of objects. + ## Domain ## Object ## Object Type -## Mixin - ## Mixin Feature ### Bid and Priority diff --git a/doc/intro.md b/doc/intro.md deleted file mode 100644 index e69de29..0000000 diff --git a/doc/overview.md b/doc/overview.md new file mode 100644 index 0000000..ad715e1 --- /dev/null +++ b/doc/overview.md @@ -0,0 +1,5 @@ +# Overview + +DynaMix is a new approach to dynamic polymorphism. It is a library which allows the composition of polymorphic objects at run time. The objects are composed of building blocks called *mixins* which provide *abstract features* to the objects and allow client code to make use of them while remaining oblivious to the concrete composition. + +The result is similar to multiple inheritance in C++, but more closely resembles mixins in Ruby, traits in Self, Scala, PHP, and others, or inheritance in Eiffel. \ No newline at end of file From 6f62eca6db6cbeac12ef8bc8396f8627875675b7 Mon Sep 17 00:00:00 2001 From: Borislav Stanimirov Date: Thu, 6 Apr 2023 12:21:29 +0300 Subject: [PATCH 09/10] docs: working on overview --- doc/README.md | 1 + doc/misc/dynamix-vs-x.md | 15 +++++++++++++++ doc/overview.md | 10 +++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 doc/misc/dynamix-vs-x.md diff --git a/doc/README.md b/doc/README.md index cb228e5..7c4cbba 100644 --- a/doc/README.md +++ b/doc/README.md @@ -25,6 +25,7 @@ * [FAQ](misc/faq.md) * [Differences Between v1 and v2](misc/v2-vs-v1.md) * [Migrating from v1](misc/migrating-from-v1.md) + * [Comparisons: Dynamix vs X](misc/dynamix-vs-x.md) * Implementation Notes * [Non-features](misc/non-features.md) * History and Inspiration diff --git a/doc/misc/dynamix-vs-x.md b/doc/misc/dynamix-vs-x.md new file mode 100644 index 0000000..a0622e6 --- /dev/null +++ b/doc/misc/dynamix-vs-x.md @@ -0,0 +1,15 @@ +# Comparisons + +Here's how DynaMix compares with existing solutions to dynamix polymorphism + +## C++ multiple inheritance + +### CRTP mixins + +## COM + +## Ruby Mixins + +## Traits + +## Entity Component System diff --git a/doc/overview.md b/doc/overview.md index ad715e1..c1bb1fc 100644 --- a/doc/overview.md +++ b/doc/overview.md @@ -2,4 +2,12 @@ DynaMix is a new approach to dynamic polymorphism. It is a library which allows the composition of polymorphic objects at run time. The objects are composed of building blocks called *mixins* which provide *abstract features* to the objects and allow client code to make use of them while remaining oblivious to the concrete composition. -The result is similar to multiple inheritance in C++, but more closely resembles mixins in Ruby, traits in Self, Scala, PHP, and others, or inheritance in Eiffel. \ No newline at end of file +The result is similar to [multiple inheritance in C++](https://www.learncpp.com/cpp-tutorial/multiple-inheritance/), but more closely resembles [mixins in Ruby](https://www.tutorialspoint.com/ruby/ruby_modules.htm), [Dart](https://dart.dev/language/mixins) and [Scala](https://docs.scala-lang.org/tour/mixin-class-composition.html), traits in [Self](https://handbook.selflanguage.org/2017.1/glossary.html), [PHP](https://www.php.net/manual/en/language.oop5.traits.php), and [others](https://en.wikipedia.org/wiki/Trait_(computer_programming), the [roles in Perl](https://docs.raku.org/language/objects#Roles) or [inheritance in Eiffel](https://www.eiffel.org/doc/eiffel/I2E-_Inheritance). + +It must be noted that the library is a means to create a project's *architecture* rathern than implement its purpose. The library doesn't *do* anything, but introduces idioms and a paradigm by which one can create the interface for what's being done. In a way it can be viewed as a language extention rather than a utility. + +It can be compared to [COM](https://en.wikipedia.org/wiki/Component_Object_Model) which is a library that introduces more orhtodox (in the style of Java or C#) type of dynamic polymorphism to C. This documentation has a [list of more comparisons](misc/dynamix-vs-x.md) of DynaMix to existing solutions. + +## Key library features + +## When (and when not) to use DynaMix From f98086c184fe1e06cac2fe087970ab09d889df68 Mon Sep 17 00:00:00 2001 From: Borislav Stanimirov Date: Thu, 6 Apr 2023 14:03:08 +0300 Subject: [PATCH 10/10] missing header --- v1compat/code/dynamix/v1compat/mutation_rules.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/v1compat/code/dynamix/v1compat/mutation_rules.cpp b/v1compat/code/dynamix/v1compat/mutation_rules.cpp index f987d9c..97fafcb 100644 --- a/v1compat/code/dynamix/v1compat/mutation_rules.cpp +++ b/v1compat/code/dynamix/v1compat/mutation_rules.cpp @@ -4,10 +4,14 @@ #include "mutation_rule.hpp" #include "common_mutation_rules.hpp" #include "domain.hpp" + #include + #include #include +#include + namespace dynamix::v1compat { namespace {