Skip to content

unrays/Hook

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Hook

Hello, here I present the result of several days of iteration and experimentation with crtp, friend, mixins, tmp, and many other concepts to achieve the perfect API! The result? A 100% crtp API, almost zero cost, an extremely clean facade with only the .update() method available, and all this while trying to adhere as closely as possible to the principles of separation of responsibilities and contracts. Actually, I find it quite amusing because it's almost magic; everything happens automatically without the programmer really having to worry about the backend. They just add the hook methods, and voilà, it works perfectly. Furthermore, one of the most complex aspects of this architecture was making the derived hooks private. I swear I really thought about this for a very long time, and I must have done at least four complete iterations from scratch before getting a result I was happy with. I'm really proud of my work, I hope you'll make good use of it :) That's about all for now. Note that this is currently a module of my Mixins project, so it's destined to show what it's capable of very soon! Have a good day :)

I'm seriously very proud of this result; I really worked hard to create a very simple and intuitive API! The beauty of it is that when you instantiate a new System, the only method available to execute for the object is .update(), only that one, nothing else. I think that's excellent, I'm very happy :)
// Copyright (c) December 2025 Félix-Olivier Dumas. All rights reserved.
// Licensed under the terms described in the LICENSE file

template<typename Hooked>
struct IEventHookable {
protected:
    IEventHookable() {
        if constexpr (requires(Hooked h) { h.onCreated(); })
            static_cast<Hooked*>(this)->onCreated();
        else onCreatedDefault();
    }

    ~IEventHookable() {
        if constexpr (requires(Hooked h) { h.onDestroyed(); })
            static_cast<Hooked*>(this)->onDestroyed();
        else onDestroyedDefault();
    }

protected:
    void invokePreUpdate() {
        if constexpr (requires(Hooked h) { h.onPreUpdate(); })
            static_cast<Hooked*>(this)->onPreUpdate();
        else onPreUpdateDefault();
    }

    void invokeUpdate() {
        invokePreUpdate();
        if constexpr (requires(Hooked h) { h.onUpdate(); })
            static_cast<Hooked*>(this)->onUpdate();
        else onUpdateDefault();
        invokePostUpdate();
    }

    void invokePostUpdate() {
        if constexpr (requires(Hooked h) { h.onPostUpdate(); })
            static_cast<Hooked*>(this)->onPostUpdate();
        else onPostUpdateDefault();
    }

protected:
    void onCreatedDefault() { std::cout << "No 'onCreated' using default.\n"; }
    void onDestroyedDefault() { std::cout << "No 'onDestroyed' using default.\n"; }

    void onUpdateDefault() { std::cout << "No 'onUpdate' using default.\n"; }
    void onPreUpdateDefault() { std::cout << "No 'onPreUpdate' using default.\n"; }
    void onPostUpdateDefault() { std::cout << "No 'onPostUpdate' using default.\n"; }
};

template<typename Derived>
struct IUpdatable {
    void update()
        requires std::is_base_of_v<IEventHookable<Derived>, Derived>
    { static_cast<Derived*>(this)->invokeUpdate(); }
};

struct System : public IEventHookable<System>, public IUpdatable<System> {
public:         friend IEventHookable<System>; friend IUpdatable<System>;
private:
    void onCreated() {
        std::cout << "Creating new system...\n";
    }

    void onUpdate() {
        std::cout << "Updating System...\n";
    }

    void onDestroyed() {
        std::cout << "Destroying system...\n";
    }
};

int main() {
    System sys;
    sys.update();

    //only exposes .update()
}
Creating new system...
No 'onPreUpdate' using default.
Updating System...
No 'onPostUpdate' using default.
Destroying system...