Skip to content

Commit

Permalink
Merge pull request #72
Browse files Browse the repository at this point in the history
  • Loading branch information
iboB committed Mar 31, 2023
2 parents 304f8e5 + 802e3a9 commit 1b0e794
Show file tree
Hide file tree
Showing 58 changed files with 3,378 additions and 22 deletions.
7 changes: 6 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ include(icm_dev_mode)
# options

option(DYNAMIX_STATIC "DynaMix: build as a static library" OFF)
option(DYNAMIX_BUILD_V1COMPAT "DynaMix: build v1 compatibility lib" ${ICM_DEV_MODE})
option(DYNAMIX_BUILD_TESTS "DynaMix: enable testing and build tests" ${ICM_DEV_MODE})
option(DYNAMIX_BUILD_EXAMPLES "DynaMix: build examples" ${ICM_DEV_MODE})
option(DYNAMIX_BUILD_SCRATCH "DynaMix: build scratch project (dev experiments)" ${ICM_DEV_MODE})
option(DYNAMIX_BUILD_TUTORIALS "DynaMix: build tutorials" ${ICM_DEV_MODE})
option(DYNAMIX_BUILD_BENCH "DynaMix: build benchmarks" ${ICM_DEV_MODE})

# Off by default since it's slow
option(DYNAMIX_BUILD_COMPILER_BENCH "DynaMix: build compilation time benchmarks (requires manual code generation step)" OFF)
option(DYNAMIX_BUILD_COMPILER_BENCH "DynaMix: build compilation time benchmarks (may be really slow)" OFF)

#######################################
# cfg
Expand All @@ -50,6 +51,10 @@ endif()

add_subdirectory(code)

if(DYNAMIX_BUILD_V1COMPAT)
add_subdirectory(v1compat)
endif()

if(DYNAMIX_BUILD_SCRATCH)
add_subdirectory(scratch)
endif()
Expand Down
6 changes: 4 additions & 2 deletions code/dynamix/define_mixin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ struct mixin_info_data_instance {

#define I_DYNAMIX_MIXIN_INFO_VAR(mixin) I_DNMX_PP_CAT(_dynamix_mixin_info_data_, mixin)

#define DYNAMIX_DEFINE_MIXIN(domain_tag, mixin) \
#define DYNAMIX_DEFINE_MIXIN_EX(domain_tag, mixin, builder) \
static const ::dynamix::util::mixin_info_data& I_DYNAMIX_MIXIN_INFO_VAR(mixin) = ::dynamix::impl::mixin_info_data_instance<domain_tag, mixin>::the_data_safe(); \
const ::dynamix::mixin_info& _dynamix_get_mixin_info(mixin*) { \
return I_DYNAMIX_MIXIN_INFO_VAR(mixin).info; \
} \
using namespace ::dynamix::util::builder_literals; \
static ::dynamix::impl::mixin_info_data_instance<domain_tag, mixin> I_DNMX_PP_CAT(_dynamix_mixin_info_data_instance_for_, mixin) = \
::dynamix::util::mixin_info_data_builder<mixin>(::dynamix::impl::mixin_info_data_instance<domain_tag, mixin>::the_data_safe(), dnmx_make_sv_lit(I_DNMX_PP_STRINGIZE(mixin)))
builder<mixin>(::dynamix::impl::mixin_info_data_instance<domain_tag, mixin>::the_data_safe(), dnmx_make_sv_lit(I_DNMX_PP_STRINGIZE(mixin)))

#define DYNAMIX_DEFINE_MIXIN(domain_tag, mixin) \
DYNAMIX_DEFINE_MIXIN_EX(domain_tag, mixin, ::dynamix::util::mixin_info_data_builder)
13 changes: 12 additions & 1 deletion code/dynamix/mixin_info_data.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ class mixin_info_data_builder {
return uses_allocator(make_any<Alloc>(m_data.get_allocator()));
}

// user data
self& user_data(uintptr_t data) {
m_data.info.user_data = data;
return *this;
}

// dependency status
self& dependency(bool b = true) {
m_data.info.dependency = b;
Expand All @@ -122,7 +128,8 @@ class mixin_info_data_builder {
return *this;
}

self& implements_by(const feature_info& info, feature_payload pl, builder_perks perks_a = {}, builder_perks perks_b = {}) {
template <typename Payload>
self& implements_by(const feature_info& info, Payload& pl, builder_perks perks_a = {}, builder_perks perks_b = {}) {
return implements_with_payload(info, fwd_any(pl), perks_a, perks_b);
}

Expand Down Expand Up @@ -192,6 +199,10 @@ class mixin_info_data_builder {
m_data.mutation_rule_infos.push_back(&m_data.mutation_rule_info_storage.back());
return *this;
}

// noop: useful for extending mixin macro with custom features and feature sets
template <typename T>
self& noop(T&&) { return *this; }
};

}
22 changes: 22 additions & 0 deletions code/dynamix/msg/msg_caller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,27 @@ struct msg_caller {

return try_default_payload(info, obj, std::forward<Args>(args)...);
}

template <typename V1Combinator>
static void call_with_v1_combinator(const feature_info& info, V1Combinator& combinator, Object& obj, Args&&... args) {
const type& t = obj.get_type();

auto fe = t.ftable_at(info.id); // ftable entry

if /*likely*/ (fe) {
combinator.set_num_results(size_t(fe.top_bid_back - fe.begin) + 1);

for (auto i = fe.top_bid_back; i != fe.begin; --i) {
if (!combinator.add_result(call(*i, obj, args...))) { // args are copied!
return;
};
}
combinator.add_result(call(*fe.begin, obj, std::forward<Args>(args)...));
}
else {
combinator.set_num_results(1);
combinator.add_result(try_default_payload(info, obj, std::forward<Args>(args)...));
}
}
};
}
42 changes: 41 additions & 1 deletion code/dynamix/msg/next_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace dynamix {
template <typename Msg, typename Mixin>
bool has_next_impl_msg(Mixin* mixin) {
auto* obj = object_of(mixin);
return !!obj->get_type().find_next_implementer(Msg::info, g::get_mixin_info<Mixin>());
return !!obj->get_type().find_next_implementer(Msg::info, g::get_mixin_info<std::remove_cv_t<Mixin>>());
}

template <typename Msg, typename Mixin, typename... Args>
Expand All @@ -26,7 +26,47 @@ auto call_next_impl_msg(Mixin* mixin, Args&&... args) -> typename msg_traits<Msg

return traits::caller::call(*next, *obj, std::forward<Args>(args)...);
}

template <typename Msg, typename Mixin>
bool has_next_bidder_set(Mixin* mixin) {
auto* obj = object_of(mixin);
return !obj->get_type().find_next_bidder_set(Msg::info, g::get_mixin_info<std::remove_cv_t<Mixin>>()).empty();
}

template <typename Msg, typename Mixin, typename... Args>
auto call_next_bidder_set(Mixin* mixin, Args&&... args) -> typename msg_traits<Msg>::ret_t {
using traits = msg_traits<Msg>;
auto* obj = static_cast<typename traits::obj_t*>(object_of(mixin));

auto next = obj->get_type().find_next_bidder_set(Msg::info, g::get_mixin_info<std::remove_cv_t<Mixin>>());
if (next.empty()) throw bad_feature_access("next bidder set");

auto begin = next.begin();
auto back = next.end() - 1;

for (auto i = back; i != begin; --i) {
traits::caller::call(*i, *obj, args...); // args are copied! return value is ignored
}
// return first (top) result
return traits::caller::call(*begin, *obj, std::forward<Args>(args)...);
}

template <typename Msg, typename Mixin, typename... Args>
auto call_next_bidder_top(Mixin* mixin, Args&&... args) -> typename msg_traits<Msg>::ret_t {
using traits = msg_traits<Msg>;
auto* obj = static_cast<typename traits::obj_t*>(object_of(mixin));

auto next = obj->get_type().find_next_bidder_set(Msg::info, g::get_mixin_info<std::remove_cv_t<Mixin>>());
if (next.empty()) throw bad_feature_access("next bidder top");

return traits::caller::call(*next.begin(), *obj, std::forward<Args>(args)...);
}

}

#define DYNAMIX_HAS_NEXT_IMPL_MSG(msg) ::dynamix::has_next_impl_msg<msg>(this)
#define DYNAMIX_CALL_NEXT_IMPL_MSG(msg, ...) ::dynamix::call_next_impl_msg<msg>(this, ##__VA_ARGS__)

#define DYNAMIX_HAS_NEXT_BIDDER_SET(msg) ::dynamix::has_next_bidder_set<msg>(this)
#define DYNAMIX_CALL_NEXT_BIDDER_SET(msg, ...) ::dynamix::call_next_bidder_set<msg>(this, ##__VA_ARGS__)
#define DYNAMIX_CALL_NEXT_BIDDER_TOP(msg, ...) ::dynamix::call_next_bidder_top<msg>(this, ##__VA_ARGS__)
14 changes: 14 additions & 0 deletions code/dynamix/mutate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,20 @@ class gradual_mutation {
return *this;
}

template <typename Mixin>
self_t& add_if_lacking() {
m_type_mut.add_if_lacking<Mixin>();
return *this;
}
self_t& add_if_lacking(const mixin_info& info) {
m_type_mut.add_if_lacking(info);
return *this;
}
self_t& add_if_lacking(std::string_view name) {
m_type_mut.add_if_lacking(name);
return *this;
}

template <typename Mixin>
self_t& remove() {
m_type_mut.remove<Mixin>();
Expand Down
38 changes: 30 additions & 8 deletions code/dynamix/type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,42 @@ bool type::implements(const feature_info& info) const noexcept {
return implements_strong(info.id);
}

namespace {
const type::ftable_payload* find_implementer(const type::ftable_entry& fe, mixin_index_t mixin_index) noexcept {
for (auto i = fe.begin; i != fe.end; ++i) {
if (i->mixin_index == mixin_index) return i;
}
return nullptr;
}
}

const type::ftable_payload* type::find_next_implementer(const feature_info& feature, const mixin_info& mixin) const noexcept {
auto fe = ftable_at(feature.id);
if (!fe) return nullptr; // type does not implement feature

auto mixin_index = index_of(mixin.id);
for (auto i = fe.begin; i != fe.end; ++i) {
if (i->mixin_index != mixin_index) continue;
++i;
if (i == fe.end) return nullptr; // provided mixin was the last - there is no next implementer
return i;
}
auto f = find_implementer(fe, mixin_index);
if (!f) return nullptr; // type does not implement feature for provided mixin
++f;
if (f == fe.end) return nullptr; // provided mixin was the last - there is no next implementer
return f;
}

// entry for provided mixin was not found
return nullptr;
itlib::span<const type::ftable_payload> type::find_next_bidder_set(const feature_info& feature, const mixin_info& mixin) const noexcept {
auto fe = ftable_at(feature.id);
if (!fe) return {}; // type does not implement feature

auto mixin_index = index_of(mixin.id);
auto f = find_implementer(fe, mixin_index);
if (!f) return {}; // type does not implement feature for provided mixin
auto fbid = f->data->bid;
while (f != fe.end && f->data->bid == fbid) ++f; // find the end of the current bidder set
if (f == fe.end) return {}; // provided mixin was of the last bidder set - there is no next one

auto begin = f;
auto end = begin + 1;
while (end != fe.end && end->data->bid == f->data->bid) ++end; // find the end of the next bidder set
return {begin, end};
}

int type::compare(const type& other) const noexcept {
Expand Down
11 changes: 10 additions & 1 deletion code/dynamix/type.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class DYNAMIX_API type : public dnmx_basic_type {
template <typename Feature>
[[nodiscard]] bool implements() const noexcept { return implements(g::get_feature_info_fast<Feature>()); }

// find the implemeter of feature after mixins
// find the implemeter of feature after mixin
// if:
// the mixin is a part of this type
// the mixin implements the feature
Expand All @@ -104,6 +104,15 @@ class DYNAMIX_API type : public dnmx_basic_type {
// if any of the conditions is not true, returns nullptr
const ftable_payload* find_next_implementer(const feature_info& feature, const mixin_info& mixin) const noexcept;

// find the next bidder set for feature after mixin
// if:
// the mixin is a part of this type
// the mixin implements the feature
// has overridden by a higher bid any other mixins which implements it
// -- returns the overridden implementers with the next bid
// if any of the conditions is not true, returns an empty span
itlib::span<const ftable_payload> find_next_bidder_set(const feature_info& feature, const mixin_info& mixin) const noexcept;

// type class
[[nodiscard]] bool is_of(const type_class& tc) const noexcept {
return tc.matches(*this);
Expand Down
16 changes: 14 additions & 2 deletions code/dynamix/type_mutation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,25 @@ bool type_mutation::type_template::implements(const feature_info& info) const no
return implements_strong(info);
}

const mixin_info& type_mutation::add(std::string_view name) {
const mixin_info* type_mutation::safe_add(std::string_view name) {
auto info = get_domain().get_mixin_info(name);
if (!info) throw mutation_error("missing mixin name");
if (!info) return nullptr;
add(*info);
return info;
}

const mixin_info& type_mutation::add(std::string_view name) {
auto info = safe_add(name);
if (!info) throw mutation_error("missing mixin name");
return *info;
}

const mixin_info* type_mutation::safe_add_if_lacking(std::string_view name) {
auto f = itlib::pfind_if(m_new_type.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);
if (f) return nullptr;
Expand Down
2 changes: 2 additions & 0 deletions code/dynamix/type_mutation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class DYNAMIX_API type_mutation {
// add mixin
void add(const mixin_info& info) { m_new_type.add(info); }
const mixin_info& add(std::string_view name);
const mixin_info* safe_add(std::string_view name); // no bad_mutation if name is not a registered mixin
template <typename Mixin>
const mixin_info& add() {
auto& info = g::get_mixin_info<Mixin>();
Expand All @@ -114,6 +115,7 @@ 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);
const mixin_info* safe_add_if_lacking(std::string_view name); // no bad_mutation if name is not a registered mixin
template <typename Mixin>
bool add_if_lacking() {
auto& info = g::get_mixin_info<Mixin>();
Expand Down
1 change: 1 addition & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* Misc
* [FAQ](misc/faq.md)
* [Differences Between v1 and v2](misc/v2-vs-v1.md)
* [Migrating from v1](misc/migrating-from-v1.md)
* Implementation Notes
* History and Inspiration
* [Roadmap](roadmap.md)
22 changes: 22 additions & 0 deletions doc/misc/migrating-from-v1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Migrating from v1

## Compatibility Library

Likely the easiest begin the migration v1 to v2 is to use the compatibility library. It provides a compatibility domain instance, object, and compatibility macros for defining messages and mixins.

A compatibility library is included in this repo: `v1compat`. In CMake it's aliased as `dynamix::v1compat`. It is not a header only library. One needs to link with it to use it.

It domain instance, as all v1 mixins are implicitly registered in the v1 domain. The compatibility domain sorts mixins by canonical order just as v1 did.

The compatibility macros have the same names as the macros in v1, but they are prefixed with `DYNAMIX_V1_`, as opposed to just `DYNAMIX_`.

The compatibility library does not offer any solutions for the following differences between v1 and v2:

* 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
* 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_<N>` for the messages that need them.
* Accessing the object in a default implementation does *not* happen through `dm_this`. Instead an argument `dm_self` is automatically provided. The type is `object&` or `const object&` for const messages.
2 changes: 1 addition & 1 deletion doc/misc/v2-vs-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ This is a list of the most notable differences between v1 and v2:
* `mixin_info::force_external` is set to true
* V2 uses `std::pmr::polymorphic_allocator` for all allocations. V1 used to have dedicated allocators like `domain_allocator`, `object_allocator` and more. The only non-pmr allocator in V2 is the optional `mixin_allocator`
* V2 message definitions are a bit more verbose. V1 used to allow defining messages with a declare macro and a define macro which only used the message name. V2 messages, require an additional traits macro, and the definition requires the signature as well.

* V2 unicast priority is inverted. With V1 bigger priority values would put the implementation to the front of the bid group, V2 puts it at the back. Note however that this does not affect multicast execution order. V2 multicasts are executed from back to front.
5 changes: 5 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,9 @@ if(Threads_FOUND)
dynamix_mt_test(domain-mt t-domain-mt.cpp)

dynamix_mt_test(fuzz-features fuzz-features.cpp)
dynamix_mt_test(fuzz-mixins fuzz-mixins.cpp)
endif()

if(DYNAMIX_BUILD_V1COMPAT)
add_subdirectory(v1compat)
endif()

0 comments on commit 1b0e794

Please sign in to comment.