1 | /**************************************************************************/ |
2 | /* safe_refcount.h */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #ifndef SAFE_REFCOUNT_H |
32 | #define SAFE_REFCOUNT_H |
33 | |
34 | #include "core/typedefs.h" |
35 | |
36 | #ifdef DEV_ENABLED |
37 | #include "core/error/error_macros.h" |
38 | #endif |
39 | |
40 | #include <atomic> |
41 | #include <type_traits> |
42 | |
43 | // Design goals for these classes: |
44 | // - No automatic conversions or arithmetic operators, |
45 | // to keep explicit the use of atomics everywhere. |
46 | // - Using acquire-release semantics, even to set the first value. |
47 | // The first value may be set relaxedly in many cases, but adding the distinction |
48 | // between relaxed and unrelaxed operation to the interface would make it needlessly |
49 | // flexible. There's negligible waste in having release semantics for the initial |
50 | // value and, as an important benefit, you can be sure the value is properly synchronized |
51 | // even with threads that are already running. |
52 | |
53 | // These are used in very specific areas of the engine where it's critical that these guarantees are held |
54 | #define SAFE_NUMERIC_TYPE_PUN_GUARANTEES(m_type) \ |
55 | static_assert(sizeof(SafeNumeric<m_type>) == sizeof(m_type)); \ |
56 | static_assert(alignof(SafeNumeric<m_type>) == alignof(m_type)); \ |
57 | static_assert(std::is_trivially_destructible<std::atomic<m_type>>::value); |
58 | #define SAFE_FLAG_TYPE_PUN_GUARANTEES \ |
59 | static_assert(sizeof(SafeFlag) == sizeof(bool)); \ |
60 | static_assert(alignof(SafeFlag) == alignof(bool)); |
61 | |
62 | template <class T> |
63 | class SafeNumeric { |
64 | std::atomic<T> value; |
65 | |
66 | static_assert(std::atomic<T>::is_always_lock_free); |
67 | |
68 | public: |
69 | _ALWAYS_INLINE_ void set(T p_value) { |
70 | value.store(p_value, std::memory_order_release); |
71 | } |
72 | |
73 | _ALWAYS_INLINE_ T get() const { |
74 | return value.load(std::memory_order_acquire); |
75 | } |
76 | |
77 | _ALWAYS_INLINE_ T increment() { |
78 | return value.fetch_add(1, std::memory_order_acq_rel) + 1; |
79 | } |
80 | |
81 | // Returns the original value instead of the new one |
82 | _ALWAYS_INLINE_ T postincrement() { |
83 | return value.fetch_add(1, std::memory_order_acq_rel); |
84 | } |
85 | |
86 | _ALWAYS_INLINE_ T decrement() { |
87 | return value.fetch_sub(1, std::memory_order_acq_rel) - 1; |
88 | } |
89 | |
90 | // Returns the original value instead of the new one |
91 | _ALWAYS_INLINE_ T postdecrement() { |
92 | return value.fetch_sub(1, std::memory_order_acq_rel); |
93 | } |
94 | |
95 | _ALWAYS_INLINE_ T add(T p_value) { |
96 | return value.fetch_add(p_value, std::memory_order_acq_rel) + p_value; |
97 | } |
98 | |
99 | // Returns the original value instead of the new one |
100 | _ALWAYS_INLINE_ T postadd(T p_value) { |
101 | return value.fetch_add(p_value, std::memory_order_acq_rel); |
102 | } |
103 | |
104 | _ALWAYS_INLINE_ T sub(T p_value) { |
105 | return value.fetch_sub(p_value, std::memory_order_acq_rel) - p_value; |
106 | } |
107 | |
108 | _ALWAYS_INLINE_ T bit_or(T p_value) { |
109 | return value.fetch_or(p_value, std::memory_order_acq_rel); |
110 | } |
111 | _ALWAYS_INLINE_ T bit_and(T p_value) { |
112 | return value.fetch_and(p_value, std::memory_order_acq_rel); |
113 | } |
114 | |
115 | _ALWAYS_INLINE_ T bit_xor(T p_value) { |
116 | return value.fetch_xor(p_value, std::memory_order_acq_rel); |
117 | } |
118 | |
119 | // Returns the original value instead of the new one |
120 | _ALWAYS_INLINE_ T postsub(T p_value) { |
121 | return value.fetch_sub(p_value, std::memory_order_acq_rel); |
122 | } |
123 | |
124 | _ALWAYS_INLINE_ T exchange_if_greater(T p_value) { |
125 | while (true) { |
126 | T tmp = value.load(std::memory_order_acquire); |
127 | if (tmp >= p_value) { |
128 | return tmp; // already greater, or equal |
129 | } |
130 | |
131 | if (value.compare_exchange_weak(tmp, p_value, std::memory_order_acq_rel)) { |
132 | return p_value; |
133 | } |
134 | } |
135 | } |
136 | |
137 | _ALWAYS_INLINE_ T conditional_increment() { |
138 | while (true) { |
139 | T c = value.load(std::memory_order_acquire); |
140 | if (c == 0) { |
141 | return 0; |
142 | } |
143 | if (value.compare_exchange_weak(c, c + 1, std::memory_order_acq_rel)) { |
144 | return c + 1; |
145 | } |
146 | } |
147 | } |
148 | |
149 | _ALWAYS_INLINE_ explicit SafeNumeric<T>(T p_value = static_cast<T>(0)) { |
150 | set(p_value); |
151 | } |
152 | }; |
153 | |
154 | class SafeFlag { |
155 | std::atomic_bool flag; |
156 | |
157 | static_assert(std::atomic_bool::is_always_lock_free); |
158 | |
159 | public: |
160 | _ALWAYS_INLINE_ bool is_set() const { |
161 | return flag.load(std::memory_order_acquire); |
162 | } |
163 | |
164 | _ALWAYS_INLINE_ void set() { |
165 | flag.store(true, std::memory_order_release); |
166 | } |
167 | |
168 | _ALWAYS_INLINE_ void clear() { |
169 | flag.store(false, std::memory_order_release); |
170 | } |
171 | |
172 | _ALWAYS_INLINE_ void set_to(bool p_value) { |
173 | flag.store(p_value, std::memory_order_release); |
174 | } |
175 | |
176 | _ALWAYS_INLINE_ explicit SafeFlag(bool p_value = false) { |
177 | set_to(p_value); |
178 | } |
179 | }; |
180 | |
181 | class SafeRefCount { |
182 | SafeNumeric<uint32_t> count; |
183 | |
184 | #ifdef DEV_ENABLED |
185 | _ALWAYS_INLINE_ void _check_unref_sanity() { |
186 | // This won't catch every misuse, but it's better than nothing. |
187 | CRASH_COND_MSG(count.get() == 0, |
188 | "Trying to unreference a SafeRefCount which is already zero is wrong and a symptom of it being misused.\n" |
189 | "Upon a SafeRefCount reaching zero any object whose lifetime is tied to it, as well as the ref count itself, must be destroyed.\n" |
190 | "Moreover, to guarantee that, no multiple threads should be racing to do the final unreferencing to zero." ); |
191 | } |
192 | #endif |
193 | |
194 | public: |
195 | _ALWAYS_INLINE_ bool ref() { // true on success |
196 | return count.conditional_increment() != 0; |
197 | } |
198 | |
199 | _ALWAYS_INLINE_ uint32_t refval() { // none-zero on success |
200 | return count.conditional_increment(); |
201 | } |
202 | |
203 | _ALWAYS_INLINE_ bool unref() { // true if must be disposed of |
204 | #ifdef DEV_ENABLED |
205 | _check_unref_sanity(); |
206 | #endif |
207 | return count.decrement() == 0; |
208 | } |
209 | |
210 | _ALWAYS_INLINE_ uint32_t unrefval() { // 0 if must be disposed of |
211 | #ifdef DEV_ENABLED |
212 | _check_unref_sanity(); |
213 | #endif |
214 | return count.decrement(); |
215 | } |
216 | |
217 | _ALWAYS_INLINE_ uint32_t get() const { |
218 | return count.get(); |
219 | } |
220 | |
221 | _ALWAYS_INLINE_ void init(uint32_t p_value = 1) { |
222 | count.set(p_value); |
223 | } |
224 | }; |
225 | |
226 | #endif // SAFE_REFCOUNT_H |
227 | |