Entt: Member function for on_construct() without instance?

Created on 9 Jan 2020  路  58Comments  路  Source: skypjack/entt

Would it be possible to add an overload for the on_construct()-family of methods on the sinks to allow for a member function of the underlying component type, without an instance of the member function's type?

Here's a visual example:

struct some_component {
    int i = 0;

    void init() {
        cout << "I've init'd: " << i << endl;
    }
};

registry.on_construct<some_component>().connect<&some_component::init>();

In the above pseudo-code, we don't use a static function nor do we use a member function with a point/reference to a specific instance. Instead, we use a pointer to a member function _of the component itself_ to call, with this being the pointer to the newly created component itself.

This would greatly clean up some of the glue code in some parts of the engine, as right now I am either passing lambdas or creating stubs to do pretty much exactly this.

For context, I could be using constructors and destructors, but given the inherently POD-like structures, default move constructors, et al, I'm writing my components in a way they can be re-used via init() and destroy() member methods (where a call to destroy() could, in theory, be followed by init() assuming all of the non-default POD properties have been set prior). I fully realize this isn't a design requirement by ENTT but it seems like a clean approach for my use-case.

Would love to hear your thoughts, or why this might not be a good idea. :)

enhancement

Most helpful comment

@skypjack The current implementation calls get

All 58 comments

Also, side-note: why doesn't on_destroy pass the component reference? Getting it manually via registry.get<type>(entity) (where entity and registry are passed to the on_destroy handler) seems to work fine, otherwise (perhaps I haven't run into a problem _yet_?), but it feels like a hack 馃槄

Would something like this work?

template <typename Component>
void init(entt::entity, entt::registry &, Component &comp) {
  comp.init();
}

template <typename Component>
void destroy(entt::entity entity, entt::registry &reg) {
  reg.get<Component>(entity).destroy();
}

template <typename Component>
void attachInit(entt::registry &reg) {
  reg.on_construct<Component>().template connect<&init<Component>>();
}

template <typename Component>
void attachDestroy(entt::registry &reg) {
  reg.on_destroy<Component>().template connect<&destroy<Component>>();
}

Sure, of course - I was just curious as to why it's not in the mainline api 馃槄 So far, if something hasn't been included in ENTT it's been a pretty good litmus as to whether or not I'm trying to shoot myself in the foot 馃檭 At least, that's been my indicator so far.

I don't think there's anything wrong with what you're doing. It seems pretty normal to me. I think the API is designed the way it is to be generic and fast. As for why on_destroy doesn't pass the component, this section of the wiki briefly mentions it.

The [sink] of the destruction signal is also similar, except for the Component parameter:

void(entt::entity, entt::registry &);

This is mainly due to performance reasons. While the component is made available after the construction, it is not when destroyed. Because of that, there are no reasons to get it from the underlying storage unless the user requires so.

Oh neat, I missed that paragraph!

Really happy to see that performance seems to be the main goal here :)

As for the API, I don't think adding this sort of overload would incur a runtime overhead, would it?

EDIT: I guess an X-Y question would be, why the construct/destruct functions at all? Wouldn't proper constructor definitions do the job?

It looks like this doesn't compile:

entt::sigh<void(foo &)> sig;
entt::sink{sig}.connect<&foo::init>();
foo obj;
sig.publish(obj);

The precise syntax you're after wouldn't work but I think the above snippet could be made possible (but should it?). There would have to be another construction signal that has a different signature. This will have a performance hit and it's also a bit too specific.

I think I don't get exactly what the expected result is for this:

registry.on_construct<some_component>().connect<&some_component::init>();

Do you want the component to be used as the current this time by time?
The problem here is that a listener is either _free_ (that is, not associated with any instance) or tightly bound to a specific object. This is due to how the delegate class works and the latter know anything about the registry and the components. What you ask for is instead something really specific.

That said, you can obtain exactly the same result if you define the init function as:

struct some_component {
    int i = 0;

    static void init(entt::entity, entt::registry &, some_component &instance) {
        cout << "I've init'd: " << instance.i << endl;
    }
};

This requires only one listener. This is pretty good since it reduces the number of callbacks invoked when you construct a new component.

Also, I don't know how you solved the problem by:

either passing lambdas or creating stubs to do pretty much exactly this.

Can you make an example? It seems to me easier than expected actually.

@Qix- _ping_. :smile:

Hey, sorry for the delay.

Do you want the component to be used as the current this time by time?

Yes, exactly. It would just be syntactic sugar, really, and would behave much like the non-bound listener would.

The problem here is that a listener is either free (that is, not associated with any instance) or tightly bound to a specific object.

In this case, it would be _free_ in the sense that it wouldn't be bound to any specific instance.

Also, I don't know how you solved the problem by [either passing lambdas or creating stubs to do pretty much exactly this]

Sorry, lambdas was incorrect here (you can't pass a lambda to a template parameter) - I just meant stubs, like your init static function, to trampoline the call to a member call. I realize this optimizes down the same way, but it still clutters the code.


Here's a naive example (this isn't specific to the implementation of listener/emitter, I realize Entt probably does something slightly different):

#include <iostream>
#include <vector>
#include <memory>
#include <string>

using namespace std;


template <typename T>
struct listener {
    virtual ~listener() = default;
    virtual void emit(T&) = 0;
};

template <typename T>
struct free_listener : public listener<T> {
    void (*fn)(T&);
    virtual void emit(T &c) { fn(c); }
    free_listener(decltype(fn) fn) : fn(fn) {}
};

template <typename T, typename V>
struct bound_listener : public listener<T> {
    V *ptr;
    void (V::* fn)(T&);
    bound_listener(V *ptr, decltype(fn) fn) : ptr(ptr), fn(fn) {}
    virtual void emit(T &c) { (ptr->*fn)(c); }
};

template <typename T>
struct member_listener : public listener<T> {
    void (T::* fn)();
    member_listener(decltype(fn) fn) : fn(fn) {}
    virtual void emit(T &c) { ((&c)->*fn)(); }
};

template <typename T>
struct emitter {
    template <void (*Fn)(T&)>
    void connect() {
        listeners.emplace_back(new free_listener<T>(Fn));
    }

    template <typename V, void (V::* Fn)(T&)>
    void connect(V *ptr) {
        listeners.emplace_back(new bound_listener<T, V>(ptr, Fn));
    }

    template <typename V, void (V::* Fn)(T&)>
    void connect(V &ptr) {
        listeners.emplace_back(new bound_listener<T, V>(&ptr, Fn));
    }

    template <void (T::* Fn)()>
    void connect() {
        listeners.emplace_back(new member_listener<T>(Fn));
    }


    void emit(T &v) {
        for (auto &l : listeners) {
            l->emit(v);
        }
    }

    vector<unique_ptr<listener<T>>> listeners;
};

struct thing {
    string noun;

    void on_emit() {
        cout << "Hello, member " << noun << "!" << endl;
    }
};

static void free_function(thing &v) {
    cout << "Hello, free " << v.noun << "!" << endl;
}

struct unrelated {
    int _;

    void on_emit(thing &v) {
        cout << "Hello, instanced " << v.noun << "!" << endl;
    }
};

int main() {
    emitter<thing> e;

    // free function
    e.connect<free_function>();

    // bound member function
    unrelated u;
    e.connect<unrelated, &unrelated::on_emit>(u);

    // un-bound member function (what I'm proposing)
    e.connect<&thing::on_emit>();

    thing v = {"world"};
    e.emit(v);

    return 0;
}

I'm asking for the un-bound member function signature be added as a viable overload to connect(), basically.

I just meant stubs, like your init static function, to trampoline the call to a member call. I realize this optimizes down the same way, but it still clutters the code.

What I don't understand is why you use this function to _trampoline_ the call to a member. This isn't required in C++. You can define the member as a static function and do there whatever you want on the instance you receive, as you would do on the this otherwise.

I'm asking for the un-bound member function signature be added as a viable overload to connect(), basically.

Since signalling stuff is independent from the registry (you can find it in the signal directory), it's not that easy to do what you're asking for. There is no notion of _component_.
Probably we can do something like this though (out of my mind, just brainstorming):

registry.sink<T>().connect<entt::invoke_on_component<&T::init>>();

Of course, invoke_on_component is just a _longer-than-needed_ name to make the purpose clear. It's unlikely I'll ever use a name like this, don't worry. :smile:
Could it work for you? This is a compromise that doesn't require to change the signalling part and make it aware of the registry, that is something I don't want to do actually.

I'm still a bit confused why you'd have to change the registry. Maybe I'm not fully understanding the issue - the member function requires no additional information, it just needs to be invoked differently (different syntax). Since upon emission you already have the component reference (since it's passed to the handler), does it really require such a drastic change to make it work?

Not trying to argue here 馃槄 I'm just trying to understand why a huge change to the registry is required.


I understand the mention of the static member, I'll probably use that. I think this feature paired with perhaps an overload that doesn't take the entity/registry reference (and thus just the component reference) would be much more useful.

The resulting entt code for simple initialization/cleanup operations would then just look like:

struct my_component {
    unique_ptr<some_non_default_constructable_type> thing;

    void init() {
        assert(!thing);
        thing.reset(new something());
    }

    void destroy() {
        thing.reset();
    }
}

registry.on_construct<my_component>().connect<&my_component::init>();
registry.on_destroy<my_component>().connect<&my_component::destroy>();

I'm not sure what the common convention is for components but this is the kind of initialization that my particular codebase has kind of been reduced to. The only difference is that I have to make intermediary static functions, which means any other code that wishes to initialize components must call the static my_component::init(component), which isn't as clean as it could be and doesn't follow the common paradigm of how the APIs are set up.

I fully realize this is a code cleanliness thing and not any request for specific functionality, so feel free to close. I thought that maybe this was a pretty unintrusive addition, hence why I initially reported it.

So, went to take a whack at a PR, and now I see what you mean. I didn't realize the signal handler implementation was generically designed and not form-fitted for entt. I now see why that wouldn't work, and why the registry would have to be changed.

I'll stick to static methods. :) It's not worth the extra nonsense to hack this onto the registry, I agree.

Thanks for putting up with me ^^

You're welcome. I was writing an answer to explain what you found yourself by looking at the code. :smile:
Btw the thing below is _possible_ afaik:

registry.sink<T>().connect<entt::invoke_on_component<&T::init>>();

Where init is a non-static member function ofc. This would make possible to do what you asked for and the price is reasonable imho.
If you find it's something useful, feel free to reopen the issue and I'll explore further the topic and let you know. :+1:

Yeah I think it'd be useful to have out of the box :) I can play around with an implementation on my end and see what simplifies things the best ^^

Sure, that's fine. I'm working on another feature right now, so I need a few days before to tackle in this one.
If you came up with something that works or that can help in the meantime, it would be appreciated. :+1:

Is there a need to worry about ref-qualifiers?

namespace entt {

namespace internal {

template <typename Arg>
auto component_type(void (*)(Arg)) -> Arg;

template <typename Class>
auto component_type(void (Class::*)()) -> Class &;

template <typename Class>
auto component_type(void (Class::*)() const) -> const Class &;

}

template <auto Func, typename Entity = entt::entity>
void invoke_on_component(
  Entity,
  entt::basic_registry<Entity> &,
  decltype(internal::component_type(Func)) comp
) {
  std::invoke(Func, comp);
}

}

So weird, I couldn't get mine to work even though it was pretty much exactly yours. Yours works, though.

Might I suggest renaming invoke_on_component simply to member?


Side note, maybe I can learn some C++ while we're at this 馃槄 This is what I had, but it was complaining when used directly as a template parameter (but worked fine when used within a stub function).

namespace entt {

namespace detail {

template <typename R, typename Base>
Base ptm_base(R Base::*);

}

template <auto Function>
void member(entt::entity, entt::registry &, decltype(detail::ptm_base(Function)) &c) {
    (c.*Function)();
}

}

EDIT: nevermind, just went back to build and mine works now too? I'm so confused. I think I should take a break 馃槗

ptm_base takes a member variable pointer instead of a member function pointer.

@Kerndog73 according to clang, that's just fine. Not sure if clang is being non-standard though.

The following also works and is more "robust" (for the purpose of the template function):

template <typename R, typename Base, typename... Args>
Base ptm_base(R (Base::*)(Args...));

EDIT: Also, your use of auto return types is unnecessary (unless you're adhering to convention); there are no symbols in the decltype that are declared after the return type.

I just remembered that the destruction signal has a different signature so there'd have to be a separate function for that.

template <auto Func, typename Entity = entt::entity>
void invoke_on_component_destroy(
  const Entity entity,
  entt::basic_registry<Entity> &reg
) {
  using ComponentRef = decltype(internal::component_type(Func));
  using Component = std::remove_cv_t<std::remove_reference_t<ComponentRef>>;
  std::invoke(Func, reg.get<Component>(entity));
}

Also, I was using auto return types because I think it's more readable in this situation. You're matching a pattern and then an arrow points to the result. Similar to this.

Actually, we don't need a separate function because trailing arguments can be discarded.

namespace entt {

namespace internal {

template <typename Arg>
auto component_type(void (*)(Arg)) -> std::decay_t<Arg>;

template <typename Class>
auto component_type(void (Class::*)()) -> Class;

template <typename Class>
auto component_type(void (Class::*)() const) -> Class;

}

template <auto Func, typename Entity = entt::entity>
void invoke_on_component(const Entity entity, entt::basic_registry<Entity> &reg) {
  using Component = decltype(internal::component_type(Func));
  std::invoke(Func, reg.get<Component>(entity));
}

}

Here's the final version I'm working with that has simplified a number of components in my codebase, with some enhancements taken from @Kerndog73's version above.

namespace entt {

namespace detail {

template <typename R, typename Base, typename... Args>
auto ptm_base(R (Base::*)(Args...)) -> Base;

template <typename R, typename Base, typename... Args>
auto ptm_base(R (Base::*)(Args...) const) -> Base;

}

template <
    auto Function,
    typename Entity = entt::entity,
    void (decltype(detail::ptm_base(Function))::*)() = Function
>
void member_create(Entity, entt::basic_registry<Entity> &, decltype(detail::ptm_base(Function)) &c) {
    (c.*Function)();
}

template <
    auto Function,
    typename Entity = entt::entity,
    void (decltype(detail::ptm_base(Function))::*)(entt::entity) = Function
>
void member_create(Entity e, entt::basic_registry<Entity> &, decltype(detail::ptm_base(Function)) &c) {
    (c.*Function)(e);
}

template <
    auto Function,
    typename Entity = entt::entity,
    void (decltype(detail::ptm_base(Function))::*)(entt::entity, entt::basic_registry<Entity> &) = Function
>
void member_create(Entity e, entt::basic_registry<Entity> &r, decltype(detail::ptm_base(Function)) &c) {
    (c.*Function)(e, r);
}

template <
    auto Function,
    typename Entity = entt::entity,
    void (decltype(detail::ptm_base(Function))::*)() = Function
>
void member_destroy(Entity e, entt::basic_registry<Entity> &r) {
    using Component = decltype(detail::ptm_base(Function));
    ((r.get<Component>(e)).*Function)();
}

template <
    auto Function,
    typename Entity = entt::entity,
    void (decltype(detail::ptm_base(Function))::*)(entt::entity) = Function
>
void member_destroy(Entity e, entt::basic_registry<Entity> &r) {
    using Component = decltype(detail::ptm_base(Function));
    ((r.get<Component>(e)).*Function)(e);
}

template <
    auto Function,
    typename Entity = entt::entity,
    void (decltype(detail::ptm_base(Function))::*)(entt::entity, entt::basic_registry<Entity> &) = Function
>
void member_destroy(Entity e, entt::basic_registry<Entity> &r) {
    using Component = decltype(detail::ptm_base(Function));
    ((r.get<Component>(e)).*Function)(e, r);
}

}

I think @Kerndog73's is more generic for the simpler cases but I was also able to simplify some other component cases of mine where I also wanted to store the component's entity handle, too - which I couldn't get working with Kerndog73's version :/

Here's another version that should work in every situation that yours does. For your particular situation, I don't think you need the first overload of component_type (nor the third actually) but it's there to be generic.

namespace entt {

namespace internal {

template <typename Arg, typename... Args>
auto component_type(void (*)(Arg, Args...)) -> std::decay_t<Arg>;

template <typename Class, typename... Args>
auto component_type(void (Class::*)(Args...)) -> Class;

template <typename Class, typename... Args>
auto component_type(void (Class::*)(Args...) const) -> Class;

}

template <auto Func, typename Entity = entt::entity>
void invoke_on_component(const Entity entity, entt::basic_registry<Entity> &reg) {
  using Component = decltype(internal::component_type(Func));
  // decltype(auto) instead of Component & to handle the empty component optimization
  decltype(auto) comp = reg.get<Component>(entity);
  if constexpr (std::is_invocable_v<decltype(Func), decltype(comp), Entity, decltype(reg)>) {
    std::invoke(Func, comp, entity, reg);
  } else if constexpr (std::is_invocable_v<decltype(Func), decltype(comp), Entity>) {
    std::invoke(Func, comp, entity);
  } else {
    std::invoke(Func, comp);
  }
}

}

I'm beginning to think that this is very specific to your use-case.

@Kerndog73 You're right about the first overload, that's why I added it there, too. ^^ I would have opted just for the original means of connecting a handler if any of my components needed all three values. I know that's specific to my use-case, though - I'm sure all three are used by others.

You beat me, though. I was in the process of re-inventing std::is_invocable until I saw your comment 馃檮

That indeed works :) Much cleaner.

I'm beginning to think that this is very specific to your use-case.

Potentially, yes. Ultimately, I was looking for a way to clean things up. The static init/destroy method was the next-cleanest, but since all I cared about in most of my cases was the initialization of non-copyable/non-default-constructable objects, there was a lot of redundant prototyping happening.

This is definitely much cleaner that what I had before, though. Feel free to omit this, as I'll use it anyway - but it was something I certainly would have used if I saw it mentioned on the entity component system wiki page, for example.

Feel free to omit this

Well, that's up to @skypjack to decide. I'm kind of on the fence.

Yep; just say the world and I'll git commit --author Kerndog73 and PR.

Sure, go right ahead

Is invoke_on_component settled on? Seems... long 馃槄

I can't think of a better name. invoke_on_component seems too long but member seems too short.

Possible alternatives:

  • member_cb
  • member_fn
  • member_handler
  • s/member/method (e.g. method, method_cb, method_fn, method_handler, etc.)

I'll PR and we can discuss there. :)

I just had a crazy idea for a completely different approach. This will be difficult to implement and might not be worth the effort. I just thought I'd put it out there.

entt::delegate already supports discarding trailing arguments. What if it could also reorder the arguments? I mean, what if you could do this:

void f1() {}
void f2(B, A) {}
void f3(A, B, C) {}
void f4(C) {}

entt::delegate<void(A, B, C)> delegate;
delegate.connect_unordered<&f1>();
delegate.connect_unordered<&f2>();
delegate.connect_unordered<&f3>();
delegate.connect_unordered<&f4>();
delegate(A{}, B{}, C{});

Of course, there could be some ambiguity in the case that some arguments are implicitly convertible to others or if multiple arguments have the same type. That's why this would have to be opt-in like connect_unordered or something.

This reordering of the arguments combined with the feature below should allow you to write registry.on_construct<comp>().connect<&comp::init>() and have it just work.

struct Foo {
  void bar(int);
};

entt::delegate<void(Foo, int)> delegate;
delegate.connect<&Foo::bar>();
Foo foo;
delegate(foo, 3);

I'm not saying this is a good idea. I'm just throwing it out there.


That second feature actually seems quite reasonable to me. It's as if you just did std::invoke(&Foo::bar, foo, 3). It doesn't seem that crazy.

Thank you for spending so much time on this, really appreciated.
Unfortunately, I've a bad new though.

386 pointed out that users can do literally everything in a callback, even move around components.

This can invalidate the instance passed to the callback. Imagine you've two functions attached to a type, the first one swaps two entities and therefore their components in the pool for __. The second one will receive a reference to the _wrong_ instance, that is not the one associated with the entity.

Although we cannot prevent users from doing whatever they want in a callback, we can still make this _safe_. We already pass the entity and the registry in the callback. Users can and should get the component from them when needed, this is the only reliable way to get the _right_ component in every callback.

This doesn't mean that the _wrapper_ we discussed isn't possible, it just means that the proposed implementations aren't the way to go because they expect an instance of the component and it won't arrive anymore with the next release.
I'm sorry but I've just discovered it. I didn't want to waste your time on this, I apologize.

@skypjack The current implementation calls get

I'm really confused @skypjack - the helper function is just syntax sugar; I don't see how it would introduce a problem not already present.

@Qix- I'm not saying it, sorry, there is a misunderstanding.
I'm just saying that the callback won't receive anymore the components and it must use get.
It uses it already as pointed out by @Kerndog73 so you can just ignore me. :)

This feature actually makes a lot more sense now that the component is removed from the callback. It seems like something that lots of people would use.

Indeed. :+1:

@skypjack I realised that the feature I was talking about above could be implemented very easily.

namespace entt::internal {

template<typename Class, typename Ret, typename... Args>
auto to_function_pointer(Ret(Class:: *)(Args...)) -> Ret(*)(Class &, Args...);

template<typename Class, typename Ret, typename... Args>
auto to_function_pointer(Ret(Class:: *)(Args...) const) -> Ret(*)(const Class &, Args...);

}

What do you think about this feature?

I think that the feature is worth entering the library, especially now that we no longer pass the component to the callbacks. :+1:


Sorry if I'm late, I was out of home today.

I wonder if this could be done with a "sink adapter". Something like this:

entt::component_sink<Foo>{reg.on_construct<Foo>()}.connect<&Foo::init>();

Maybe this would introduce a tool that lets users easily make their own adapters. It's the sort of thing that you could do easily with lambdas and std::function but it's a bit tricky with entt::delegate. I'm not sure how useful this would be (or if it's even possible). invoke_on_component has no way of accepting an instance of a class. It uses the component as an instance. It just seems inflexible to me so I've been trying to brainstorm different approaches.


@skypjack Should I open a separate issue for connecting member functions without instances? std::function supports it so it only makes sense for entt::delegate to have it too. Also, it's really easy to implement (as I pointed out above).

@Kerndog73 since the title of this issue is _member functions for on_construct() without instance_, I don't think we need another issue for that. :)
Afaik it's _possible_ to do that as long as the first argument for the function type of the delegate is the class of the member you connect. This should already work out of the box, it's only a matter of passing the argument to the opaque function stored internally.
However, for registry's callbacks, we still need a tool to make this match since the component doesn't arrive anymore. Right?

Yeah that makes sense. I had a crack at implementing a generic sink adapter and that turned into a nightmare. Implementing component_sink on its own might still be a nightmare but I'm not sure yet.

I'm overthinking this. The PR contains the best implementation that I am aware of.

@Kerndog73

Should I open a separate issue for connecting member functions without instances?

Upstream on experimental. :+1:

@Qix- I'm closing pending issues. I don't remember what is missing here. Is it something like this?

struct clazz { void init() { /* ... */ } };
registry.on_construct<T>().connect<entt::invoke<&clazz::init>>();

I've used a type T that is different from clazz voluntarily, so as to make it clear that we can also do fancy things like invoking a member function on a component owned by an entity when a different type is assigned.

Let me know if I'm missing something or this is the expected result. Thanks.

invoking a member function on a component owned by an entity when a different type is assigned.

Might be too early in the morning for me 馃槄 how would this work?

Well, as an example, whenever you add T to an entity, you want to invoke a method on the component U assigned to the same entity.
Not sure if there are many use cases for this but since this would be for free... well... :smile:

Oh I see, I could think of a few use-cases for that actually. Not sure if I'd ever use them personally, but I could see it being useful.

Anyway, I think invoke is a great name and that's exactly the functionality I'd expect ^^ Looking forward to it.

@Qix- @Kerndog73
What about this form?

entt::invoke<&clazz::func>(registry.on_construct<clazz>());

I've implemented it and _it works_. We need a sort of invoke_remove or similar to eventually disconnect the member function from the sink. However, I'd like to have a feedback on this before to proceed.

I think it's a bit strange to be inverted like that but I don't hate it. :)

I think it's a bit strange to be inverted like that

Yeah, I see but this is the only way to deduce the entity type. Otherwise you've to specify it explicitly.

Yeah I think it's acceptable. I think the only real issue I have with it is that entt::invoke isn't namespaced alongside the event stuff, so one might think it has other uses outside of event handlers.

That's a massive nitpick though. I think that'd be just fine :)

@Qix- I'm fine also with the other solution around, that is a function like the one proposed by @Kerndog73 if I remember it right:

template<auto Member, typename Entity = entt::entity>
void invoke(basic_registry<Entity> &reg, const Entity entt);

In this case, you can use it as:

sink.connect<&internal::invoke<&clazz::func>>();

Unless you specialized your registry with a different identifier, in this case it's:

sink.connect<&internal::invoke<clazz::func, my_entity_type>>();

At least it's not flipped on its head. Any thought?

I like that one a lot more, personally.

Well, this one is used internally by the previous one, so it's not that difficult to get rid of the latter! :smile:
Here a possible implementation:

template<auto Member, typename Entity = entity>
void invoke(basic_registry<Entity> &reg, const Entity entt) {
    delegate<void(basic_registry<Entity> &, const Entity)> func;
    func.template connect<Member>(reg.get<member_class_t<decltype(Member)>>(entt));
    func(reg, entt);
}

The delegate helps to support functions that _require_ the registry and the entity, only the registry, no arguments at all.
Since it already exists and it comes for free here, why not. :smile:
Otherwise we either would have duplicated code to do the same thing or support only for functions that don't receive arguments.

@Qix- see experimental branch. :+1:
Let me know if it works for you.

Sure thing, might take me a few days to get out of this rabbit hole I'm in at the moment :D will report back!

28 days later... _*cough*_...

Sorry about the delay. I forgot to report back here that I've tried it out and it works as advertised. Glad to see it merged in :)

Thanks again for the great maintainership, @skypjack!

Was this page helpful?
0 / 5 - 0 ratings