eventpp

repository·master·Indexed 23 days ago

https://github.com/wqking/eventpp

A high-performance, header-only C++11 library for managing callbacks, event dispatching, and asynchronous event queues. It supports Observer and Publisher/Subscriber patterns through components like CallbackList, EventDispatcher, and EventQueue. Key features include thread safety, nested event support, and AnyData—a specialized data structure for reducing heap allocations in high-throughput scenarios.

Tokens
42.2K
Snippets
98
Records
158
Agent score
81%

What's inside eventpp

  1. Overview of eventpp

    master

    eventpp is a mature, production-ready C++ library designed for implementing callbacks, event dispatchers, and event queues. It provides the building blocks for signal and slot mechanisms, publisher/subscriber patterns, and the observer pattern.

    Key features include:

    • Synchronous & Asynchronous: Supports both immediate synchronous dispatching and asynchronous event queuing.
    • Robustness: Supports nested events (listeners can safely modify the listener list during dispatch), thread safety for multi-threading, and strong exception safety.
    • Performance: Highly optimized for high-throughput scenarios (e.g., CallbackList can invoke 100M callbacks per second).
    • Flexibility: Header-only, no dependencies, and requires only C++11. Listeners and events can be any type without needing a common base class.
  2. What is CallbackList and how to use it

    master

    The CallbackList is the core foundation of eventpp. It maintains a list of callback functions (such as functions, function pointers, member function pointers, lambdas, or function objects) and executes them sequentially when the list is called. It is conceptually similar to Qt's signal/slot system.

    To use it, include eventpp/callbacklist.h and instantiate it with a function prototype.

    #include <eventpp/callbacklist.h>
    #include <iostream>
    #include <string>
    
    // Define the prototype: void(int, std::string)
    using MyCallbackList = eventpp::CallbackList<void(int, std::string)>;
    
    int main() {
        MyCallbackList list;
    
        // Add a lambda callback
        list.append([](int a, std::string b) {
            std::cout << "Callback 1: " << a << ", " << b << std::endl;
        });
    
        // Trigger all callbacks
        list(42, "Hello World");
    
        return 0;
    }
  3. What is CallbackList and how does it work

    master

    Overview

    CallbackList is the fundamental building block of eventpp. It is a container that holds a list of callbacks (functions, lambdas, member function pointers, etc.) and invokes them sequentially when requested.

    Key Characteristics

    • Callback Targets: Supports functions, function pointers, member function pointers, lambda expressions, and function objects.
    • Invocation: When the list is invoked, it calls each callback one by one in the same thread as the caller.
    • Nested Safety: If a callback adds or removes other callbacks during an invocation, eventpp ensures that the newly added or removed callbacks are not triggered within that same invocation cycle (preventing infinite loops or iterator invalidation).
    • Thread Safety Note: While nested safety is guaranteed in a single-threaded context, it is not guaranteed in multi-threaded environments. If one thread invokes the list while another adds/removes callbacks, the behavior is undefined/unpredictable.
  4. What is HeterCallbackList and how does it work?

    master

    HeterCallbackList

    HeterCallbackList is a heterogeneous callback list that allows you to store multiple callbacks with different function signatures (prototypes) in a single container. It is the foundation for HeterEventDispatcher and HeterEventQueue.

    When you invoke the list using operator(), it automatically identifies and executes every callback whose signature matches the provided arguments.

    Key Characteristics:

    • Heterogeneous Prototypes: Unlike a standard CallbackList, you can mix different function types (e.g., void(int) and void(std::string)) in one list.
    • Flexible Targets: Supports functions, function pointers, member function pointers, lambda expressions, and function objects.
    • Safe Modification: If you append, prepend, or insert a new callback while the list is currently being invoked (inside a callback), the new callback is guaranteed not to be triggered during that same invocation cycle.
    • Complexity: append, prepend, insert, and remove all operate with $O(1)$ time complexity.
  5. What is AnyData and when to use it

    master

    Overview

    AnyData is a high-performance data structure designed to hold various data types without dynamic heap allocation (unlike std::shared_ptr). It is intended for extreme performance optimization scenarios, such as the core event system of a game engine, where reducing heap allocation overhead is critical.

    Key Characteristics

    • Performance: Can improve performance by 30%~50% compared to std::shared_ptr by avoiding heap allocations.
    • Memory Usage: It uses at least maxSize bytes (the template parameter) even for smaller types. It may use slightly more memory than a shared pointer solution due to this fixed buffer, but it avoids the overhead of control blocks.
    • Type Safety: AnyData is not type-safe. Misusing it (e.g., casting to the wrong type) can lead to program crashes.
    • Intended Use: It should only be used within an EventQueue. It is not a general-purpose container.

    How it works

    AnyData takes a maxSize template parameter. If the data being stored is smaller than or equal to maxSize, it is stored internally. If the data is larger, it falls back to dynamic heap allocation.

  6. What is EventQueue and how does it work

    master

    An EventQueue is an asynchronous event dispatcher that adds queuing capabilities to the standard EventDispatcher. Unlike EventDispatcher, which executes callbacks immediately, EventQueue caches events when enqueue is called and only dispatches them when process (or other processing methods) is invoked.

    Key Characteristics:

    • Asynchronous: Events are stored in a queue and processed later.
    • Thread Affinity: Listeners are executed in the same thread that calls the process method.
    • Relationship to EventDispatcher: EventQueue is not derived from EventDispatcher. Do not attempt to cast an EventQueue to an EventDispatcher type.
    • Analogy: It functions similarly to Qt's event system (QEvent) or Windows API message processing.
    template <
    	typename Event,
    	typename Prototype,
    	typename Policies = DefaultPolicies
    > 
    class EventQueue;
  7. What is HeterEventDispatcher and how does it work?

    master

    A HeterEventDispatcher acts like a std::map<EventType, HeterCallbackList>. It maintains a mapping between event types and lists of heterogeneous callbacks.

    When you call dispatch, the dispatcher finds the corresponding HeterCallbackList for that event type and invokes the listeners synchronously in the caller's thread. This allows you to manage multiple different callback signatures (prototypes) under a single dispatcher instance, keyed by an event type.

  8. Configure threading models using the Threading policy

    master

    The Threading policy controls how data is protected in multi-threaded environments.

    Available Options:

    • eventpp::MultipleThreading (Default): Protects core data with a mutex (defaults to std::mutex).
    • eventpp::SingleThreading: No protection; data cannot be accessed from multiple threads.

    Customizing Threading: You can use eventpp::GeneralThreading to provide a custom mutex, atomic type, or condition variable.

    Using SpinLock for performance: When the number of threads is close to the number of CPU cores, eventpp::SpinLock may perform better than std::mutex. Use eventpp::GeneralThreading<eventpp::SpinLock> to implement this.

    // Using SpinLock via GeneralThreading shortcut
    struct MyEventPolicies {
        using Threading = eventpp::GeneralThreading<eventpp::SpinLock>;
    };
    eventpp::EventDispatcher<int, void (), MyEventPolicies> dispatcher;
    eventpp::CallbackList<void (), MyEventPolicies> callbackList;
  9. Why rvalue references are forbidden in callback prototypes

    master

    You cannot use rvalue references (e.g., void(int &&)) as callback prototypes in EventDispatcher or CallbackList.

    This is a deliberate design choice to prevent bugs. Since CallbackList invokes multiple callbacks sequentially, if the first callback moves an rvalue argument, subsequent callbacks would receive an invalid or empty value instead of the original data. To ensure all listeners receive the intended data, only lvalue references or values are supported.

    eventpp::CallbackList<void(std::string &&)> callbackList;
    callbackList("Hello"); // compile error
  10. Use EventDispatcher for managing many event types

    master

    An EventDispatcher acts like a map of <EventType, CallbackList> pairs. It is ideal for systems with many or dynamic event types (like GUI or game engines) because you can distinguish events by a type rather than creating separate CallbackList instances for every single event.

    Key Characteristics:

    • Synchronous: Listeners are triggered immediately when EventDispatcher::dispatch is called.
    • Uniform Prototype: All events handled by a single dispatcher must share the same callback signature. To handle different data types, use a base event class and have all specific events derive from it.
    • Policy-based: Requires a policy class to map an event object to its type.
    enum class MyEventType
    {
        redraw,
        mouseDown,
        mouseUp,
        //... maybe 200 other events here
    };
    
    struct MyEvent {
        MyEventType type;
        // data that all events may need
    };
    
    struct MyEventPolicies
    {
        static MyEventType getEvent(const MyEvent & e) {
            return e.type;
        }
    };
    
    eventpp::EventDispatcher<MyEventType, void(const MyEvent &), MyEventPolicies> dispatcher;
    dispatcher.dispatch(MyEvent { MyEventType::redraw });
  11. Configure listener removal conditions

    master

    When adding a listener via ConditionalRemover, you provide a condition parameter. This is a predicate function that returns a bool. The condition is evaluated after each trigger; if it returns true, the listener is automatically removed from the dispatcher.

    There are two supported prototypes for the condition function:

    1. No arguments: bool condition() — Used when the condition does not depend on the event arguments.
    2. With arguments: bool condition(Args ...args) — Receives the same arguments that were passed to the listener.
  12. How mixins work in eventpp

    master

    A mixin is a template class used to inject code into the EventDispatcher or EventQueue inheritance hierarchy to extend functionality.

    When you inject mixins, they sit between the EventDispatcher and the EventDispatcherBase. For example, if you inject MixinA and MixinB, the hierarchy becomes: EventDispatcher $\leftarrow$ MixinA $\leftarrow$ MixinB $\leftarrow$ EventDispatcherBase.

    Key properties:

    • Mixins can access all public and protected members of EventDispatcherBase.
    • All public members of the mixins are visible to the end-user.
    • For multiple mixins, the first mixin listed in the MixinList is the lowest in the inheritance hierarchy (closest to the user).
    template <typename Base>
    class MyMixin : public Base
    {
    };