{"id":21995812,"url":"https://github.com/lb--/events","last_synced_at":"2025-07-27T11:33:26.055Z","repository":{"id":148327976,"uuid":"42657363","full_name":"LB--/events","owner":"LB--","description":"Two-phase event library for C++","archived":false,"fork":false,"pushed_at":"2016-03-22T13:16:26.000Z","size":57,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"events","last_synced_at":"2025-03-15T03:41:52.061Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LB--.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-09-17T13:21:19.000Z","updated_at":"2023-09-08T17:01:47.000Z","dependencies_parsed_at":"2023-05-19T19:45:23.334Z","dependency_job_id":null,"html_url":"https://github.com/LB--/events","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LB--%2Fevents","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LB--%2Fevents/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LB--%2Fevents/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LB--%2Fevents/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LB--","download_url":"https://codeload.github.com/LB--/events/tar.gz/refs/heads/events","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245053714,"owners_count":20553381,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-29T21:18:42.735Z","updated_at":"2025-03-23T04:22:31.837Z","avatar_url":"https://github.com/LB--.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"events [![travis](https://travis-ci.org/LB--/events.svg?branch=events)](https://travis-ci.org/LB--/events)\n======\nWhereas most event libraries allow changing both the event itself and the state of the program while enumerating handlers, this library specifically forbids that by separating event handling into two phases.\nThe first phase is the _processing_ phase, where the event iself can be changed, but the state of the program _must not change_.\nThe second phase is the _reacting_ phase, where the event itself cannot be changed, and the state of the program _should_ change.\nEvents are classes and support inheritance, including multiple inheritance.\n\n_This library requires that your compiler support C++1z (the C++ standard after C++14)_\n\n### CMake Usage\nFrom the `events/cmake` directory, copy the `FindLB` directory to a place in your `CMAKE_MODULE_PATH`.\nThen, add `find_package(LB/events REQUIRED)` to your CMake script.\nYou may need to set the CMake variable `LB/events_ROOT` if you installed to a nonstandard location.\nFinally, link to the `LB::events` imported target with `target_link_libraries()`.\n\n## Processing vs Reacting\nThe difference between processing and reacting is by design - there is no common \"listener\" class between processors and reactors.\nProcessing an event means _only the event should be modified_.\nTrying to change the state of the application during the processing phase is _wrong_ and should never be done.\nReacting is where the event can no longer be modified, but you are _expected_ to change the state of the application.\nThis is because an event might never move on from the processing phase to the reacting phase, and if you already did something because of the event during the processing phase, there is no way to know that you need to undo it.\nAlternatively, the caller may wish that the event be reacted to without processing it first.\n\nThis library _tries_ to enforce the distinction between processing and reacting through const-correctness, but it's not possible to prevent misuse entirely.\nFor processing an event, the event is non-const but the processor is const.\nFor reacting to an event, the event is const but the reactor is non-const.\nAdditionally, some events' member functions have inversed const-correctness to mirror processing vs reacting (you may be familiar with logical-const vs bitwise-const).\nIf you try to use `const_cast`, that's _wrong_, **even if you're going from non-const to const**.\nBeware implicit casts - they can bypass the two-phase dichotomy unintentionally.\nIt is up to you to not violate the contract.\n\n## Order of Execution\nWhen events are called, the call propagates through the entire class hierarchy.\nThe very first thing that happens is that the root `Event` class' listeners are triggered, then it moves down through the class hierarchy.\nWith multiple inheritance, the base class listeners are triggered in order from left to right as specified.\nPer invocation of an event, all classes in the hierarchy get their listeners called exactly once.\nThis logic is generated at compile time via template metaprograms, thus there is very little runtime overhead.\n\nExample:\n```txt\n   0\n  / \\\n/ / \\ \\\n1 2 3 4\n\\ / \\ /\n 5   6\n  \\ /\n   7\n```\n0 is the root `Event` class, and 7 is the fully derived type of the event instance that was called.\n\n## Listening to events\nClasses which process events derive from `LB::events::Processor\u003c\u003e`, and classes which react to events derive from `LB::events::Reactor\u003c\u003e`.\nThere is also `LambdaProcessor\u003c\u003e` and `LambdaReactor\u003c\u003e` for the occasional time when you just need a simple lambda to handle an event.\nA single class can derive from multiple instantiations of the `Processor`/`Reactor` classes to listen to multiple types of events.\n\n**Note** that if your processor or reactor doesn't have any state, you should only ever need a single instance of it - otherwise you will process/react to an event multiple times in the same way, which is almost always undesireable.\n\nYou should also be aware of `LB::events::ListenerPriority`, which you can pass to the constructors for `Processor`/`Reactor`.\nPriorities with lower values are executed before priorities with higher values.\nIf multiple listeners have the same priority, they are executed in the order they began listening.\nYou can change your priority or stop listening by calling `ignore()` from the particular `Processor`/`Reactor` and then optionally later calling `listen()` with, optionally, the priority.\nFor extreme cases, you can use `ListenerPriority::FIRST` or `ListenerPriority::LAST`.\n\n**Note:** even with `FIRST` or `LAST` priority, less-specific listeners are always called before more-specific listeners.\nThat is, the inheritance tree is more important than the listener priority.\n\nExample event listener:\n```cpp\nusing WidgetEvent = MyNamespace::Widget::Event;\nstruct MyListener\n: private LB::events::Processor\u003cWidgetEvent\u003e\n, private LB::events::Reactor\u003cWidgetEvent\u003e\n{\n\tint blah = 0;\n\nprivate:\n\tvirtual void process(WidgetEvent \u0026e) const override\n\t{\n\t\t//blah is const\n\t\t//e.instance() returns const\n\t\t//you may change the event\n\t\t//you should NOT change the application state\n\t}\n\tvirtual void react(WidgetEvent const \u0026e) override\n\t{\n\t\t//blah is non-const\n\t\t//e.instance() returns non-const\n\t\t//you may NOT change the event\n\t\t//you should change the application state\n\t}\n};\n```\nTake note of the differing locations of the keyword `const`.\nFor processing events, the member function is `const`.\nFor reacting to events, the events themselves are `const`.\nDon't vioate the two-phase dichotomy - if you want to do evil things, just use any other event library ever.\n\n## Calling Events\nYou can invoke `call()` to have any event be both `process()`ed and `react()`ed to. Events are called for the least derived classes first before moving on to more specific handling for more derived classes and finally reaching the most derived class.\n\nExample:\n```cpp\nSomeEvent{/**/}.call();\n```\n\n## The predefined events\nFor your convenience, some types of events are defined for you.\nMost predefined events are abstract and require you to derive them with your own events, which you will see how do do in the next section.\n\n### `Event`\nWhen you `#include \"LB/events/Event.hpp\"` you get access to `LB::events::Event`, which is the enforced base class of all events.\n\n### `Cancellable`\nWhen you `#include \"LB/events/Cancellable.hpp\"` you get access to `LB::events::Cancellable`, which is the base class for all events that can be cancelled.\nIf an event is cancelled, invoking `react()` will have no effect.\nDuring the processing phase, call `cancelled(true)` to cancel reacting to the event, or `cancelled(false)` to override a previous processor's decision.\nYou can also call `cancelled()` with no parameters to see if the event will be reacted to or not.\n\n### `Exclusive`\nWhen you `#include \"LB/events/Exclusive.hpp\"` you get access to `LB::events::Exclusive`, which is the base class for all events which should have exactly either 0 or 1 reactors.\nDuring the processing phase, the last processor to `claim(reactor)` the event will ensure that the specified reactor is the only reactor which will be called for the event.\n\n### `Cloneable`\nWhen you `#include \"LB/events/Cloneable.hpp\"` you get access to `LB::events::Cloneable`, which is the base class for all events that can be cloned.\nWhy or when would you need to clone an event?\nThat's for you to decide.\n\n**Note** that you should consult the documentation for [`Cloneable` types](https://github.com/LB--/cloning) to learn how to properly derive this event.\n\n### `Construct\u003c\u003e` and `Destruct\u003c\u003e`\nWhen you `#include \"LB/events/RAII.hpp\"` you get access to `LB::events::Construct`, which is a template base class for classes that need to fire an event when they are constructed.\n`Construct` events intentionally ignore the inheritance tree for their particular class - as they are called from constructors, the more-derived class constructors have not been called yet.\nOnce the `Construct` event has ended, the more-derived class' constructor will call its own `Construct` event, and so on.\nBe aware, there's [an ugly part](#the-ugly-part).\n\nAlso when you `#include \"LB/events/RAII.hpp\"` you get access to `LB::events::Destruct` too, which is a template base class for classes that need to fire an event when they are destructed.\n`Destruct` events intentionally ignore the inheritance tree for their particular class - as they are called from destructors, the less-derived class destructors have not been called yet.\nOnce the `Destruct` event has ended, the less-derived class' destructor will call its own `Destruct` event, and so on. Be aware, there's [an ugly part](#the-ugly-part).\n\n## Implementing your own event type\nTo implement your own kind of event, you need to derive from `LB::events::Implementor\u003c\u003e`.\nThe first template parameter is your new event class, and the rest are all the parent event classes.\nFor example, to create a widget-specific event that can be cancelled:\n```cpp\n//In MyEvent.hpp\nnamespace MyNamespace\n{\n\tstruct MyEvent\n\t: LB::events::Implementor\n\t\u003c\n\t\tMyEvent,\n\t\tLB::events::Cancellable,\n\t\tMyNamespace::Widget::Event\n\t\u003e\n\t{\n\t\tMyEvent(Widget \u0026s)\n\t\t: s(s)\n\t\t{\n\t\t}\n\n\t\tvirtual Widget const \u0026instance() noexcept override\n\t\t{\n\t\t\treturn s;\n\t\t}\n\t\tvirtual Widget \u0026instance() const noexcept override //logical-const\n\t\t{\n\t\t\treturn s;\n\t\t}\n\n\tprivate:\n\t\tWidget \u0026s;\n\t};\n}\n\n//In MyEvent.cpp\nLB_EVENTS_EVENT(MyNamespace::MyEvent);\n//Yes, it's ugly, but it's required and cannot be inside any namespace.\n```\nThat's it!\nYour new event type is ready to be used.\n[Just be sure to reread the processing vs reacting section](#processing-vs-reacting) before adding member functions and data members.\nMembers which affect the state of the event itself should have normal const-correctness.\nMembers which affect the state of the application should have _inversed_ const-correctness.\n\n### Exception Guarantee\nBy default, exceptions can be thrown when processing or reacting to an event - only the minimal basic exception guarantee is made (the application will be in a consistent state, though not necessarily a predictable state).\nYou can enforce the `noexcept` exception guarantee on processing and reacting by adding a static member to your event class:\n```cpp\nstatic constexpr bool NOEXCEPT = true;\n```\nNow your event and all its derived events will have the `noexcept` guarantee.\n\n### The Ugly Part\nThe `LB::events::Implementor` class needs to store static data (`LB::events::Registrar`), but since it is a template, you have to manually define that static data for every instantiation of `Implementor`.\nYou saw how to do it in the `MyEvent` example above - it's the only reason you need a source file at all.\nThe even uglier part is that events which are templates require you to define the static data for each instantiation of them you generate.\n_Why is this even needed at all?_\nBecause **linkage in C++ sucks!**\nMacros are a necessary evil here, unfortunately.\n(Maybe one day when Modules get added to the C++ standard, this problem will go away.)\n\n## Event Recursion\nAlthough currently untested, event recursion should be fully supported.\nJust don't try to recursively react to an event while processing another event - that would violate the two-phase dichotomy.\n\n## Resolving Multiple Inheritance Conflicts\nFor the most part, multiple inheritance of events should work just fine.\nThe only case you may have issue with is when you try to inherit two or more events which override the same virtual function.\nWith normal multiple inheritance this would not be an issue as you would just override that member function in your class and be done with it, however C++ does not consider the fact that an abstract class need not override the ambiguous virtual function and so an error occurs while instantiating the `LB::events::Implementor` class.\n\nUntil a future standard of C++ eventually fixes this issue, you have to help out `Implementor` by providing a class that properly inherits all the parent classes you want:\n```cpp\ntemplate\u003ctypename... ParentT\u003e\nstruct MyCaseSpecificResolver\n: virtual ParentT...\n{\n\t//Override the conflicting virtual functions as pure virtual\n\tvirtual void do_stuff() noexcept override = 0;\n\n\t//These would also be considered conflicting\n\tvirtual void process() override = 0;\n\tvirtual void react() const override = 0;\n};\n```\nAfter that, use `Implementor` like this:\n```cpp\nstruct MyEvent\n: LB::events::Implementor\u003cMyEvent, MyCaseSpecificResolver\u003cParent1, Parent2, Parent3\u003e\u003e\n{\n\tvirtual void do_stuff() noexcept override //= 0;\n\t{\n\t\t//...\n\t}\n};\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flb--%2Fevents","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flb--%2Fevents","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flb--%2Fevents/lists"}