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 "utility/module.hpp"
8#include "concurrency/module.hpp"
9#include <memory>
10#include <vector>
11#include <functional>
12#include <tuple>
13
14namespace hi::inline v1 {
15
16template<typename>
17class group_ptr;
18
33template<typename T, typename... Proto>
35
36template<typename T, typename... Args>
37class enable_group_ptr<T, void(Args...)> {
38public:
39#ifndef NDEBUG
41 {
42 hi_assert(_enable_group_ptr_owners.empty());
43 }
44#endif
45
50 void notify_group_ptr(Args const&...args) const noexcept
51 {
52 hilet owners_copy = [&] {
53 hilet lock = std::scoped_lock(_enable_group_ptr_mutex);
54 return _enable_group_ptr_owners;
55 }();
56
57 for (auto owner : owners_copy) {
58 hi_assert_not_null(owner);
59 if (owner->_notify) {
60 owner->_notify(args...);
61 }
62 }
63 }
64
65private:
66 using _enable_group_ptr_notify_proto = void(Args...);
67
68 std::vector<group_ptr<T> *> _enable_group_ptr_owners;
69 mutable unfair_mutex _enable_group_ptr_mutex;
70
71 [[nodiscard]] bool _enable_group_ptr_holds_invariant() const noexcept
72 {
73 hi_axiom(_enable_group_ptr_mutex.is_locked());
74
75 for (auto owner : _enable_group_ptr_owners) {
76 if (owner == nullptr or owner->_ptr == nullptr or owner->_ptr.get() != this or
77 owner->_ptr.use_count() < _enable_group_ptr_owners.size()) {
78 return false;
79 }
80 }
81 return true;
82 }
83
84 void _enable_group_ptr_add_owner(group_ptr<T> *owner) noexcept
85 {
86 hilet lock = std::scoped_lock(_enable_group_ptr_mutex);
87
88 _enable_group_ptr_owners.push_back(owner);
89 hi_axiom(_enable_group_ptr_holds_invariant());
90 }
91
92 void _enable_group_ptr_remove_owner(group_ptr<T> *owner) noexcept
93 {
94 hilet lock = std::scoped_lock(_enable_group_ptr_mutex);
95
96 hilet num_removed = std::erase(_enable_group_ptr_owners, owner);
97 hi_assert(num_removed == 1);
98 hi_axiom(_enable_group_ptr_holds_invariant());
99 }
100
107 void _enable_group_ptr_reseat(std::shared_ptr<enable_group_ptr> const& replacement) noexcept
108 {
109 hilet lock = std::scoped_lock(_enable_group_ptr_mutex);
110
111 hi_assert_not_null(replacement);
112 hi_assert_not_null(replacement.get());
113
114 while (not _enable_group_ptr_owners.empty()) {
115 auto *owner = _enable_group_ptr_owners.back();
116 _enable_group_ptr_owners.pop_back();
117 owner->_ptr = replacement;
118 owner->_ptr->_enable_group_ptr_add_owner(owner);
119 }
120 hi_axiom(_enable_group_ptr_holds_invariant());
121 }
122
123 friend class group_ptr<T>;
124};
125
126template<typename T>
127class enable_group_ptr<T> : public enable_group_ptr<T, void()> {
128};
129
142template<typename T>
144public:
145 using notify_proto = T::_enable_group_ptr_notify_proto;
146 using element_type = T;
147
150 virtual ~group_ptr()
151 {
152 if (_ptr) {
153 _ptr->_enable_group_ptr_remove_owner(this);
154 }
155 }
156
166 group_ptr(group_ptr const& other) noexcept : _ptr(other._ptr)
167 {
168 if (_ptr) {
169 _ptr->_enable_group_ptr_add_owner(this);
170 }
171 }
172
188 group_ptr& operator=(group_ptr const& other) noexcept
189 {
190 if (_ptr == other._ptr) {
191 return *this;
192
193 } else if (_ptr and other._ptr) {
194 // Make copy because reseat() will overwrite _ptr.
195 auto tmp = _ptr;
196 tmp->_enable_group_ptr_reseat(other._ptr);
197 return *this;
198
199 } else if (_ptr) {
200 _ptr->_enable_group_ptr_remove_owner(this);
201 _ptr = nullptr;
202 return *this;
203
204 } else {
205 _ptr = other._ptr;
206 _ptr->_enable_group_ptr_add_owner(this);
207 return *this;
208 }
209 }
210
221 group_ptr(group_ptr&& other) noexcept : _ptr(std::move(other._ptr))
222 {
223 if (_ptr) {
224 _ptr->_enable_group_ptr_remove_owner(&other);
225 _ptr->_enable_group_ptr_add_owner(this);
226 }
227 }
228
245 group_ptr& operator=(group_ptr&& other) noexcept
246 {
247 hi_return_on_self_assignment(other);
248
249 if (_ptr == other._ptr) {
250 other._ptr->_enable_group_ptr_remove_owner(&other);
251 other._ptr = nullptr;
252 return *this;
253
254 } else if (_ptr and other._ptr) {
255 other._ptr->_enable_group_ptr_remove_owner(&other);
256 // Make copy because reseat() will overwrite _ptr.
257 auto tmp = _ptr;
258 tmp->_enable_group_ptr_reseat(std::exchange(other._ptr, nullptr));
259 return *this;
260
261 } else if (_ptr) {
262 _ptr->_enable_group_ptr_remove_owner(this);
263 _ptr = nullptr;
264 return *this;
265
266 } else {
267 other._ptr->_enable_group_ptr_remove_owner(&other);
268 _ptr = std::exchange(other._ptr, nullptr);
269 _ptr->_enable_group_ptr_add_owner(this);
270 return *this;
271 }
272 }
273
276 constexpr group_ptr() noexcept = default;
277
280 constexpr group_ptr(std::nullptr_t) noexcept {}
281
288 void reset() noexcept
289 {
290 if (_ptr) {
291 _ptr->_enable_group_ptr_remove_owner(this);
292 _ptr = nullptr;
293 }
294 }
295
303 template<forward_of<std::shared_ptr<enable_group_ptr<T, notify_proto>>> Ptr>
304 group_ptr(Ptr&& ptr) noexcept : _ptr(std::forward<Ptr>(ptr))
305 {
306 if (_ptr) {
307 _ptr->_enable_group_ptr_add_owner(this);
308 }
309 }
310
319 template<forward_of<std::shared_ptr<enable_group_ptr<T, notify_proto>>> Ptr>
320 group_ptr& operator=(Ptr&& ptr) noexcept
321 {
322 return *this = group_ptr{std::forward<Ptr>(ptr)};
323 }
324
330 [[nodiscard]] T *get() const noexcept
331 {
332 return static_cast<T *>(_ptr.get());
333 }
334
340 [[nodiscard]] T *operator->() const noexcept
341 {
342 return get();
343 }
344
350 [[nodiscard]] T& operator*() const noexcept
351 {
352 return *get();
353 }
354
360 explicit operator bool() const noexcept
361 {
362 return to_bool(_ptr);
363 }
364
375 void subscribe(forward_of<notify_proto> auto&& func) noexcept
376 {
377 _notify = hi_forward(func);
378 }
379
382 void unsubscribe() noexcept
383 {
384 _notify = {};
385 }
386
387private:
389 std::function<notify_proto> _notify = {};
390
391 friend class enable_group_ptr<T, notify_proto>;
392};
393
394template<typename Context, typename Expected>
395struct is_forward_of<Context, group_ptr<Expected>> :
396 std::conditional_t<std::is_convertible_v<Context, group_ptr<Expected>>, std::true_type, std::false_type> {
397};
398
399} // namespace hi::inline v1
#define hi_assert(expression,...)
Assert if expression is true.
Definition assert.hpp:184
#define hi_axiom(expression,...)
Specify an axiom; an expression that is true.
Definition assert.hpp:238
#define hi_assert_not_null(x,...)
Assert if an expression is not nullptr.
Definition assert.hpp:223
#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:13
A smart pointer which manages ownership as a group.
Definition group_ptr.hpp:143
group_ptr & operator=(group_ptr &&other) noexcept
Move assign from another group_ptr.
Definition group_ptr.hpp:245
group_ptr & operator=(Ptr &&ptr) noexcept
Construct a group_ptr from a shared_ptr.
Definition group_ptr.hpp:320
void reset() noexcept
Reset the group_ptr and make it empty.
Definition group_ptr.hpp:288
group_ptr(group_ptr const &other) noexcept
Copy construct from another group_ptr.
Definition group_ptr.hpp:166
virtual ~group_ptr()
Destroy this group_ptr.
Definition group_ptr.hpp:150
constexpr group_ptr() noexcept=default
Construct an empty group_ptr.
void unsubscribe() noexcept
Unsubscribe the callback function.
Definition group_ptr.hpp:382
T * operator->() const noexcept
Dereference to member of the object that is owned by this group_ptr.
Definition group_ptr.hpp:340
group_ptr & operator=(group_ptr const &other) noexcept
Copy assign from another group_ptr.
Definition group_ptr.hpp:188
T * get() const noexcept
Get the pointer to the object that this group_ptr owns.
Definition group_ptr.hpp:330
T & operator*() const noexcept
Dereference the object that is owned by this group_ptr.
Definition group_ptr.hpp:350
void subscribe(forward_of< notify_proto > auto &&func) noexcept
Subscribe a callback function.
Definition group_ptr.hpp:375
group_ptr(group_ptr &&other) noexcept
Move construct from another group_ptr.
Definition group_ptr.hpp:221
group_ptr(Ptr &&ptr) noexcept
Construct a group_ptr from a shared_ptr.
Definition group_ptr.hpp:304
Enable a class to be used in a group_ptr.
Definition group_ptr.hpp:34
void notify_group_ptr(Args const &...args) const noexcept
Call the callback which are registered with the owning group_ptrs.
Definition group_ptr.hpp:50
Is context a form of the expected type.
Definition type_traits.hpp:531
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)