VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
CallbackList.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cstddef>
5#include <cstdint>
6#include <vector>
7
8namespace VoltMod
9{
10
11/**
12 * @internal
13 * @brief One host event's callbacks, safe to edit from inside a dispatch.
14 *
15 * Unlike Core's CallbackRegistry: plain function pointers across the module boundary, ordered by
16 * plugin load position then subscription order, and a pass stops at the first that returns true.
17 * During a pass a removal only marks and an addition waits aside; both land when the outermost pass ends.
18 */
19template <class Fn>
21{
22public:
24 {
25 const Entry entry{.Token = token, .Order = order, .Call = callback, .Context = context};
26 if (_depth > 0)
27 {
28 _pending.push_back(entry);
29 }
30 else
31 {
32 Insert(entry);
33 }
34 }
35
36 /** Whether nothing is subscribed, counting additions still waiting aside. */
37 bool Empty() const
38 {
39 return _pending.empty() && std::ranges::all_of(_entries, &Entry::Removed);
40 }
41
42 /** Whether @p token was taken on this event. */
44 {
45 for (Entry& entry : _entries)
46 {
47 if (entry.Token != token || entry.Removed)
48 {
49 continue;
50 }
51
52 entry.Removed = true;
53 if (_depth == 0)
54 {
55 ApplyPending();
56 }
57 return true;
58 }
59
60 const auto waiting = std::ranges::find(_pending, token, &Entry::Token);
61 if (waiting == _pending.end())
62 {
63 return false;
64 }
65
66 _pending.erase(waiting);
67 return true;
68 }
69
70 /** Invoke @p visit(callback, context) until one returns true; returns whether one did. */
71 template <class Visit>
73 {
74 return Walk([&](const Entry& entry) { return visit(entry.Call, entry.Context); });
75 }
76
77 /** Invoke @p visit(callback, context) over one plugin's callbacks: those added with @p order. */
78 template <class Visit>
80 {
81 Walk([&](const Entry& entry) {
82 if (entry.Order == order)
83 {
84 visit(entry.Call, entry.Context);
85 }
86 return false;
87 });
88 }
89
90private:
91 struct Entry
92 {
93 uint64_t Token = 0;
94 uint64_t Order = 0; ///< the owning plugin's load position
95 Fn Call = nullptr;
96 void* Context = nullptr;
97 bool Removed = false;
98 };
99
100 template <class Visit>
101 bool Walk(Visit&& visit)
102 {
103 ++_depth;
104 bool stopped = false;
105 // Nothing is inserted or erased while the depth is up, so the count and the references hold.
106 const size_t count = _entries.size();
107 for (size_t i = 0; i < count && !stopped; ++i)
108 {
109 const Entry& entry = _entries[i];
110 if (!entry.Removed)
111 {
113 }
114 }
115 if (--_depth == 0)
116 {
117 ApplyPending();
118 }
119 return stopped;
120 }
121
122 void Insert(const Entry& entry)
123 {
124 const auto at = std::ranges::find_if(_entries, [&](const Entry& held) { return held.Order > entry.Order; });
125 _entries.insert(at, entry);
126 }
127
128 void ApplyPending()
129 {
130 std::erase_if(_entries, [](const Entry& entry) { return entry.Removed; });
131 for (const Entry& entry : _pending)
132 {
133 Insert(entry);
134 }
135 _pending.clear();
136 }
137
138 std::vector<Entry> _entries;
139 std::vector<Entry> _pending;
140 int _depth = 0;
141};
142
143} // namespace VoltMod
bool Remove(uint64_t token)
void DispatchTo(uint64_t order, Visit &&visit)
bool Dispatch(Visit &&visit)
void Add(uint64_t token, uint64_t order, Fn callback, void *context)
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56