1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2017-2020, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17/* synchronization helper classes */
18
19#pragma once
20
21#include "C4Windows.h"
22
23#include <atomic>
24#include <array>
25#include <limits>
26#include <mutex>
27#include <chrono>
28#include <condition_variable>
29
30#ifndef _WIN32
31#include <ranges>
32#include <span>
33
34#include <poll.h>
35#endif
36
37namespace StdSync
38{
39 static inline constexpr auto Infinite = std::numeric_limits<std::uint32_t>::max();
40
41#ifndef _WIN32
42 template<typename T> requires std::ranges::contiguous_range<T> && std::ranges::sized_range<T>
43 int Poll(T &&fds, const std::uint32_t timeout) noexcept
44 {
45 const auto clampedTimeout = std::clamp(val: static_cast<int>(timeout), lo: static_cast<int>(Infinite), hi: std::numeric_limits<int>::max());
46
47 for (;;)
48 {
49 const int result{poll(std::ranges::data(fds), static_cast<nfds_t>(std::ranges::size(fds)), clampedTimeout)};
50
51 if (result == -1 && (errno == EINTR || errno == EAGAIN || errno == ENOMEM))
52 {
53 continue;
54 }
55
56 return result;
57 }
58 }
59#endif
60}
61
62class CStdCSec
63{
64public:
65 virtual ~CStdCSec() = default;
66
67protected:
68 std::recursive_mutex mutex;
69
70public:
71 virtual void Enter() { mutex.lock(); }
72 virtual void Leave() { mutex.unlock(); }
73};
74
75class CStdEvent
76{
77public:
78 CStdEvent(bool initialState = false);
79 ~CStdEvent();
80
81#ifdef _WIN32
82private:
83 CStdEvent(bool initialState, bool manualReset);
84#endif
85
86public:
87 void Set();
88 void Reset();
89 bool WaitFor(std::uint32_t milliseconds);
90
91#ifdef _WIN32
92 HANDLE GetEvent() const { return event; }
93
94public:
95 static CStdEvent AutoReset(bool initialState = false);
96#else
97 int GetFD() const noexcept { return fd[0].load(m: std::memory_order_acquire); }
98#endif
99
100private:
101#ifdef _WIN32
102 HANDLE event;
103#else
104 std::array<std::atomic_int, 2> fd;
105#endif
106};
107
108class CStdLock
109{
110public:
111 CStdLock(CStdCSec *pSec) : sec(pSec)
112 {
113 sec->Enter();
114 }
115
116 ~CStdLock()
117 {
118 Clear();
119 }
120
121protected:
122 CStdCSec *sec;
123
124public:
125 void Clear()
126 {
127 if (sec)
128 {
129 sec->Leave();
130 }
131
132 sec = nullptr;
133 }
134};
135
136class CStdCSecExCallback
137{
138public:
139 // is called with CSec exlusive locked!
140 virtual void OnShareFree(class CStdCSecEx *pCSec) = 0;
141 virtual ~CStdCSecExCallback() {}
142};
143
144class CStdCSecEx : public CStdCSec
145{
146public:
147 CStdCSecEx()
148 : lShareCnt(0), ShareFreeEvent(false), pCallbClass(nullptr) {}
149 CStdCSecEx(CStdCSecExCallback *pCallb)
150 : lShareCnt(0), ShareFreeEvent(false), pCallbClass(pCallb) {}
151 ~CStdCSecEx() {}
152
153protected:
154 // share counter
155 long lShareCnt;
156 // event: exclusive access permitted
157 CStdEvent ShareFreeEvent;
158 // callback
159 CStdCSecExCallback *pCallbClass;
160
161public:
162 // (cycles forever if shared locked by calling thread!)
163 void Enter() override
164 {
165 // lock
166 CStdCSec::Enter();
167 // wait for share-free
168 while (lShareCnt)
169 {
170 // reset event
171 ShareFreeEvent.Reset();
172 // leave section for waiting
173 CStdCSec::Leave();
174 // wait
175 ShareFreeEvent.WaitFor(milliseconds: StdSync::Infinite);
176 // reenter section
177 CStdCSec::Enter();
178 }
179 }
180
181 void Leave() override
182 {
183 // set event
184 ShareFreeEvent.Set();
185 // unlock
186 CStdCSec::Leave();
187 }
188
189 void EnterShared()
190 {
191 // lock
192 CStdCSec::Enter();
193 // add share
194 lShareCnt++;
195 // unlock
196 CStdCSec::Leave();
197 }
198
199 void LeaveShared()
200 {
201 // lock
202 CStdCSec::Enter();
203 // remove share
204 if (!--lShareCnt)
205 {
206 // do callback
207 if (pCallbClass)
208 pCallbClass->OnShareFree(pCSec: this);
209 // set event
210 ShareFreeEvent.Set();
211 }
212 // unlock
213 CStdCSec::Leave();
214 }
215};
216
217class CStdShareLock
218{
219public:
220 CStdShareLock(CStdCSecEx *pSec) : sec(pSec)
221 {
222 sec->EnterShared();
223 }
224
225 ~CStdShareLock()
226 {
227 Clear();
228 }
229
230protected:
231 CStdCSecEx *sec;
232
233public:
234 void Clear()
235 {
236 if (sec)
237 {
238 sec->LeaveShared();
239 }
240
241 sec = nullptr;
242 }
243};
244