Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core] operator== hook #1455

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open

Conversation

jpeletier
Copy link
Contributor

Note, requires #1454 to be merged first.

Building on the previous PR, this one adds a hook for operator==. It will detect this operator in a component definition and bind a hook to it that can be used to test components for equality.

If this operator is not defined but the compare operator is, Flecs will automatically derive this operator.

    class Vec2D {
    public:
        float x, y;
    
        Vec2D(float x = 0.0f, float y = 0.0f) : x(x), y(y) {}
    
        // Function to calculate the modulus (magnitude) of the vector
        float modulus() const {
            return std::sqrt(x * x + y * y);
        }
    
        // Overload operator< to compare vectors based on their modulus
        bool operator<(const Vec2D& other) const {
            return this->modulus() < other.modulus();
        }
    
        // Overload operator== to compare vectors with a precision tolerance of 0.001
        bool operator==(const Vec2D& other) const {
            return std::fabs(this->x - other.x) < 0.001 && std::fabs(this->y - other.y) < 0.001;
        }
    };

    flecs::world ecs;

    auto vtype =
        ecs.component<Vec2D>();  // will automatically register the above hooks.

    Vec2D a(1, 2);
    Vec2D b(2, 3);
    Vec2D c(1.00001, 1.9999999);

    const ecs_type_info_t *ti = ecs_get_type_info(ecs, vtype);

    assert(ti->hooks.equals(&a, &b, ti) == false); // ok!
    assert(ti->hooks.equals(&a, &c, ti) == true);  // ok!
    assert(ti->hooks.cmp(&b, &a, ti) > 0);         // ok!



@jpeletier jpeletier force-pushed the equals-hook branch 7 times, most recently from fa1a619 to 2c416ea Compare December 5, 2024 17:01
@jpeletier jpeletier force-pushed the equals-hook branch 2 times, most recently from b4af2c5 to 9fb4d95 Compare December 23, 2024 11:13
@@ -397,14 +442,14 @@ struct component : untyped_component {
template <typename Func>
component<T>& on_add(Func&& func) {
using Delegate = typename _::each_delegate<typename std::decay<Func>::type, T>;
flecs::type_hooks_t h = get_hooks();
flecs::type_hooks_t h = this->get_hooks();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: remove this-> so it's consistent with set_hooks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -429,14 +474,48 @@ struct component : untyped_component {
component<T>& on_set(Func&& func) {
using Delegate = typename _::each_delegate<
typename std::decay<Func>::type, T>;
flecs::type_hooks_t h = get_hooks();
flecs::type_hooks_t h = this->get_hooks();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: see above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


/** Register operator compare hook. */
using untyped_component::op_compare;
component<T>& op_compare() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I made up my mind about using on_X naming. It makes more sense to me, since it's still a hook that will be invoked when the core needs to do a comparison, so it is "on compare", "on equals".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

dtor_hook_required,
move_hook_required,
copy_hook_required);
ctor_hook_required ? flecs_rtt_struct_ctor : NULL,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep the existing design where we just pass in booleans to flecs_rtt_init_default_hooks_struct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.
This change made sense in some internal refactor. Not anymore.


ecs_type_hooks_t hooks = *ecs_get_hooks_id(world, component);

hooks.ctor = ctor_hook_required && !(flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) ?
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this code added? It doesn't seem related to equals/compare

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is due to the new flags checks in ecs_set_hooks_id(). It now requires that hooks are set to NULL if they are to be illegal: The PR enables the commented-out asserts in there:

/* TODO: enable asserts once RTT API is updated */

... and also unquarantines several tests in ComponentLifecycle.c.


if (hooks.lifecycle_ctx_free) {
hooks.lifecycle_ctx_free(hooks.lifecycle_ctx);
hooks.lifecycle_ctx_free = NULL;
hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_nop;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a nop? Can we keep the existing approach?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing approach didn't actually work as intended. Specifically, ecs_set_hooks_id does not allow you to unset lifecycle_ctx_free once it's been set.

This issue went unnoticed in other cases (e.g., for ctor, dtor, etc.) because if any field in a struct required a runtime-generated hook, the struct would always continue requiring hooks as new fields were added. In those cases, the lifecycle context wouldn't need to be removed.

However, handling cmp and equals hooks is different. Initially, a struct might be comparable because its first field is comparable, and so an autogenerated compare hook is added. But if a later field is not comparable, the entire struct becomes uncomparable, even if further comparable fields are added later. This means the lifecycle context must be removed, unless the struct coincidentally still requires other hooks (e.g., ctor or dtor).

Since ecs_set_hooks_id doesn't support unsetting lifecycle_ctx_free, I opted to replace it with a no-op function.

I could change ecs_set_hooks_id to do what I want and avoid the nop, but that would break a pattern in there, so it is better to have this conversation and decide.

So that's why 😉

}

if (ctor_hook_required || dtor_hook_required || move_hook_required ||
copy_hook_required) {
if (hooks.ctor || hooks.dtor || hooks.move ||
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did the existing approach change here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, due to the refactor required to comply with ecs_set_hooks_id() strict flags check.

@@ -1464,9 +1539,10 @@ void ecs_set_hooks_id(
}

if(ti->hooks.flags & ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL) {
ti->hooks.ctor_move_dtor = flecs_move_ctor_illegal;
ti->hooks.move_dtor = flecs_move_ctor_illegal;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated to this PR, I just spotted this bug and fixed it along the way before I forgot: The wrong hook was being set to the flecs_move_ctor_illegal illegal handler.

int16_t value;
};

int compare(flecs::world& ecs, flecs::entity_t id, const void *a, const void *b) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make these functions static

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants