VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
CallbackRegistry.hpp
Go to the documentation of this file.
1#pragma once
2
4#include <algorithm>
5#include <concepts>
6#include <cstddef>
7#include <cstdint>
8#include <memory>
9#include <utility>
10#include <vector>
11
12namespace VoltMod
13{
14
15/**
16 * @internal
17 * @brief Handle-keyed item store behind @ref Event, @ref Scheduler, and @ref GameEvents.
18 *
19 * Handles start at 1 and never repeat, so 0 means "no registration". Items run in the order they
20 * were added.
21 */
22template <class T>
24{
25public:
26 /** Store @p item and return its handle. */
28 {
29 const uint64_t id = _nextId++;
30 _entries.push_back(std::make_unique<Entry>(Entry{.Id = id, .Item = std::move(item)}));
31 ++_live;
32 return id;
33 }
34
35 /** @ref Add, returning a Subscription that removes the item when dropped. */
37 {
38 const uint64_t id = Add(std::move(item));
39 return Subscription([this, id] { Remove(id); });
40 }
41
42 /** Whether @p id was stored; an unknown id is ignored. */
44 {
45 const auto it = Locate(id);
46 if (it == _entries.end() || (*it)->Removed)
47 {
48 return false;
49 }
50
51 --_live;
52 if (_dispatchDepth > 0)
53 {
54 // It may be running; it goes when the outermost dispatch ends.
55 (*it)->Removed = true;
56 }
57 else
58 {
59 _entries.erase(it);
60 }
61 return true;
62 }
63
64 void Clear()
65 {
66 _live = 0;
67 if (_dispatchDepth == 0)
68 {
69 _entries.clear();
70 return;
71 }
72 for (auto& entry : _entries)
73 {
74 entry->Removed = true;
75 }
76 }
77
78 bool Empty() const { return _live == 0; }
79
80 size_t Size() const { return _live; }
81
82 /** Null once removed. */
84 {
85 const auto it = Locate(id);
86 return it != _entries.end() && !(*it)->Removed ? &(*it)->Item : nullptr;
87 }
88
89 /** Invoke @p fn on every item @p pred accepts. A callback may add or remove items, its own
90 * included, or dispatch again; items it adds first run on the next dispatch. */
91 void DispatchIf(std::predicate<const T&> auto&& pred, std::invocable<T&> auto&& fn)
92 {
93 const size_t count = _entries.size();
94 if (count == 0)
95 {
96 return;
97 }
98
99 DispatchScope scope{*this};
100 for (size_t i = 0; i < count; ++i)
101 {
102 // Heap-allocated, so a callback growing the vector leaves it in place.
103 Entry& entry = *_entries[i];
104 if (!entry.Removed && pred(entry.Item))
105 {
106 fn(entry.Item);
107 }
108 }
109 }
110
111 /** @ref DispatchIf over every stored item. */
112 void Dispatch(std::invocable<T&> auto&& fn)
113 {
114 DispatchIf([](const T&) { return true; }, std::forward<decltype(fn)>(fn));
115 }
116
117private:
118 struct Entry
119 {
120 uint64_t Id = 0;
121 T Item;
122 bool Removed = false;
123 };
124
125 /** Erases removed items once the outermost dispatch ends. */
126 struct DispatchScope
127 {
128 explicit DispatchScope(CallbackRegistry& registry) : Registry(registry) { ++Registry._dispatchDepth; }
129 ~DispatchScope()
130 {
131 // Removed entries outlive only a dispatch, so a size gap means some are pending.
132 if (--Registry._dispatchDepth == 0 && Registry._entries.size() != Registry._live)
133 {
134 std::erase_if(Registry._entries, [](const auto& entry) { return entry->Removed; });
135 }
136 }
137 DispatchScope(const DispatchScope&) = delete;
138 DispatchScope& operator=(const DispatchScope&) = delete;
139
140 CallbackRegistry& Registry;
141 };
142
143 /** Ids only grow, so the entries stay sorted by id. */
144 auto Locate(uint64_t id)
145 {
146 const auto it = std::ranges::lower_bound(_entries, id, {}, [](const auto& entry) { return entry->Id; });
147 return it != _entries.end() && (*it)->Id == id ? it : _entries.end();
148 }
149
150 std::vector<std::unique_ptr<Entry>> _entries;
151 size_t _live = 0;
152 int _dispatchDepth = 0;
153 uint64_t _nextId = 1;
154};
155
156} // namespace VoltMod
void DispatchIf(std::predicate< const T & > auto &&pred, std::invocable< T & > auto &&fn)
Subscription AddOwned(T item)
void Dispatch(std::invocable< T & > auto &&fn)
Owns one registration and releases it on destruction.
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56