HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
group_ptr.hpp
1// Copyright Take Vos 2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "assert.hpp"
8#include "type_traits.hpp"
9#include "concepts.hpp"
10#include "cast.hpp"
11#include "unfair_mutex.hpp"
12#include <memory>
13#include <vector>
14#include <functional>
15#include <tuple>
16
17namespace hi::inline v1 {
18
19template<typename>
20class group_ptr;
21
36template<typename T, typename... Proto>
38
39template<typename T, typename... Args>
40class enable_group_ptr<T, void(Args...)> {
41public:
42#ifndef NDEBUG
44 {
45 hi_assert(_enable_group_ptr_owners.empty());
46 }
47#endif
48
53 void notify_group_ptr(Args const&...args) const noexcept
54 {
55 hilet owners_copy = [&] {
56 hilet lock = std::scoped_lock(_enable_group_ptr_mutex);
57 return _enable_group_ptr_owners;
58 }();
59
60 for (auto owner : owners_copy) {
61 hi_assert_not_null(owner);
62 if (owner->_notify) {
63 owner->_notify(args...);
64 }
65 }
66 }
67
68private:
69 using _enable_group_ptr_notify_proto = void(Args...);
70
71 std::vector<group_ptr<T> *> _enable_group_ptr_owners;
72 mutable unfair_mutex _enable_group_ptr_mutex;
73
74 [[nodiscard]] bool _enable_group_ptr_holds_invariant() const noexcept
75 {
76 hi_axiom(_enable_group_ptr_mutex.is_locked());
77
78 for (auto owner : _enable_group_ptr_owners) {
79 if (owner == nullptr or owner->_ptr == nullptr or owner->_ptr.get() != this or
80 owner->_ptr.use_count() < _enable_group_ptr_owners.size()) {
81 return false;
82 }
83 }
84 return true;
85 }
86
87 void _enable_group_ptr_add_owner(group_ptr<T> *owner) noexcept
88 {
89 hilet lock = std::scoped_lock(_enable_group_ptr_mutex);
90
91 _enable_group_ptr_owners.push_back(owner);
92 hi_axiom(_enable_group_ptr_holds_invariant());
93 }
94
95 void _enable_group_ptr_remove_owner(group_ptr<T> *owner) noexcept
96 {
97 hilet lock = std::scoped_lock(_enable_group_ptr_mutex);
98
99 hilet num_removed = std::erase(_enable_group_ptr_owners, owner);
100 hi_assert(num_removed == 1);
101 hi_axiom(_enable_group_ptr_holds_invariant());
102 }
103
110 void _enable_group_ptr_reseat(std::shared_ptr<enable_group_ptr> const& replacement) noexcept
111 {
112 hilet lock = std::scoped_lock(_enable_group_ptr_mutex);
113
114 hi_assert_not_null(replacement);
115 hi_assert_not_null(replacement.get());
116
117 while (not _enable_group_ptr_owners.empty()) {
118 auto *owner = _enable_group_ptr_owners.back();
119 _enable_group_ptr_owners.pop_back();
120 owner->_ptr = replacement;
121 owner->_ptr->_enable_group_ptr_add_owner(owner);
122 }
123 hi_axiom(_enable_group_ptr_holds_invariant());
124 }
125
126 friend class group_ptr<T>;
127};
128
129template<typename T>
130class enable_group_ptr<T> : public enable_group_ptr<T, void()> {
131};
132
145template<typename T>
147public:
148 using notify_proto = T::_enable_group_ptr_notify_proto;
149 using element_type = T;
150
153 virtual ~group_ptr()
154 {
155 if (_ptr) {
156 _ptr->_enable_group_ptr_remove_owner(this);
157 }
158 }
159
169 group_ptr(group_ptr const& other) noexcept : _ptr(other._ptr)
170 {
171 if (_ptr) {
172 _ptr->_enable_group_ptr_add_owner(this);
173 }
174 }
175
191 group_ptr& operator=(group_ptr const& other) noexcept
192 {
193 if (_ptr == other._ptr) {
194 return *this;
195
196 } else if (_ptr and other._ptr) {
197 // Make copy because reseat() will overwrite _ptr.
198 auto tmp = _ptr;
199 tmp->_enable_group_ptr_reseat(other._ptr);
200 return *this;
201
202 } else if (_ptr) {
203 _ptr->_enable_group_ptr_remove_owner(this);
204 _ptr = nullptr;
205 return *this;
206
207 } else {
208 _ptr = other._ptr;
209 _ptr->_enable_group_ptr_add_owner(this);
210 return *this;
211 }
212 }
213
224 group_ptr(group_ptr&& other) noexcept : _ptr(std::move(other._ptr))
225 {
226 if (_ptr) {
227 _ptr->_enable_group_ptr_remove_owner(&other);
228 _ptr->_enable_group_ptr_add_owner(this);
229 }
230 }
231
248 group_ptr& operator=(group_ptr&& other) noexcept
249 {
250 hi_return_on_self_assignment(other);
251
252 if (_ptr == other._ptr) {
253 other._ptr->_enable_group_ptr_remove_owner(&other);
254 other._ptr = nullptr;
255 return *this;
256
257 } else if (_ptr and other._ptr) {
258 other._ptr->_enable_group_ptr_remove_owner(&other);
259 // Make copy because reseat() will overwrite _ptr.
260 auto tmp = _ptr;
261 tmp->_enable_group_ptr_reseat(std::exchange(other._ptr, nullptr));
262 return *this;
263
264 } else if (_ptr) {
265 _ptr->_enable_group_ptr_remove_owner(this);
266 _ptr = nullptr;
267 return *this;
268
269 } else {
270 other._ptr->_enable_group_ptr_remove_owner(&other);
271 _ptr = std::exchange(other._ptr, nullptr);
272 _ptr->_enable_group_ptr_add_owner(this);
273 return *this;
274 }
275 }
276
279 constexpr group_ptr() noexcept = default;
280
283 constexpr group_ptr(std::nullptr_t) noexcept {}
284
291 void reset() noexcept
292 {
293 if (_ptr) {
294 _ptr->_enable_group_ptr_remove_owner(this);
295 _ptr = nullptr;
296 }
297 }
298
306 template<forward_of<std::shared_ptr<enable_group_ptr<T, notify_proto>>> Ptr>
307 group_ptr(Ptr&& ptr) noexcept : _ptr(std::forward<Ptr>(ptr))
308 {
309 if (_ptr) {
310 _ptr->_enable_group_ptr_add_owner(this);
311 }
312 }
313
322 template<forward_of<std::shared_ptr<enable_group_ptr<T, notify_proto>>> Ptr>
323 group_ptr& operator=(Ptr&& ptr) noexcept
324 {
325 return *this = group_ptr{std::forward<Ptr>(ptr)};
326 }
327
333 [[nodiscard]] T *get() const noexcept
334 {
335 return static_cast<T *>(_ptr.get());
336 }
337
343 [[nodiscard]] T *operator->() const noexcept
344 {
345 return get();
346 }
347
353 [[nodiscard]] T& operator*() const noexcept
354 {
355 return *get();
356 }
357
363 explicit operator bool() const noexcept
364 {
365 return to_bool(_ptr);
366 }
367
378 void subscribe(forward_of<notify_proto> auto&& func) noexcept
379 {
380 _notify = hi_forward(func);
381 }
382
385 void unsubscribe() noexcept
386 {
387 _notify = {};
388 }
389
390private:
392 std::function<notify_proto> _notify = {};
393
394 friend class enable_group_ptr<T, notify_proto>;
395};
396
397template<typename Context, typename Expected>
398struct is_forward_of<Context, group_ptr<Expected>> :
399 std::conditional_t<std::is_convertible_v<Context, group_ptr<Expected>>, std::true_type, std::false_type> {
400};
401
402} // namespace hi::inline v1
Utilities to assert and bound check.
#define hi_axiom(expression)
Specify an axiom; an expression that is true.
Definition assert.hpp:133
#define hi_assert_not_null(x)
Assert if an expression is not nullptr.
Definition assert.hpp:118
#define hi_assert(expression)
Assert if expression is true.
Definition assert.hpp:86
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
#define hi_forward(x)
Forward a value, based on the decltype of the value.
Definition utility.hpp:29
STL namespace.
DOXYGEN BUG.
Definition algorithm.hpp:15
A smart pointer which manages ownership as a group.
Definition group_ptr.hpp:146
group_ptr & operator=(group_ptr &&other) noexcept
Move assign from another group_ptr.
Definition group_ptr.hpp:248
group_ptr & operator=(Ptr &&ptr) noexcept
Construct a group_ptr from a shared_ptr.
Definition group_ptr.hpp:323
void reset() noexcept
Reset the group_ptr and make it empty.
Definition group_ptr.hpp:291
group_ptr(group_ptr const &other) noexcept
Copy construct from another group_ptr.
Definition group_ptr.hpp:169
virtual ~group_ptr()
Destroy this group_ptr.
Definition group_ptr.hpp:153
constexpr group_ptr() noexcept=default
Construct an empty group_ptr.
void unsubscribe() noexcept
Unsubscribe the callback function.
Definition group_ptr.hpp:385
T * operator->() const noexcept
Dereference to member of the object that is owned by this group_ptr.
Definition group_ptr.hpp:343
group_ptr & operator=(group_ptr const &other) noexcept
Copy assign from another group_ptr.
Definition group_ptr.hpp:191
T * get() const noexcept
Get the pointer to the object that this group_ptr owns.
Definition group_ptr.hpp:333
T & operator*() const noexcept
Dereference the object that is owned by this group_ptr.
Definition group_ptr.hpp:353
void subscribe(forward_of< notify_proto > auto &&func) noexcept
Subscribe a callback function.
Definition group_ptr.hpp:378
group_ptr(group_ptr &&other) noexcept
Move construct from another group_ptr.
Definition group_ptr.hpp:224
group_ptr(Ptr &&ptr) noexcept
Construct a group_ptr from a shared_ptr.
Definition group_ptr.hpp:307
Enable a class to be used in a group_ptr.
Definition group_ptr.hpp:37
void notify_group_ptr(Args const &...args) const noexcept
Call the callback which are registered with the owning group_ptrs.
Definition group_ptr.hpp:53
Is context a form of the expected type.
Definition type_traits.hpp:481
True if T is a forwarded type of Forward.
Definition concepts.hpp:130
T back(T... args)
T empty(T... args)
T lock(T... args)
T move(T... args)
T pop_back(T... args)
T push_back(T... args)
T size(T... args)