1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2022, 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#include "C4Windows.h"
18
19#include "StdSync.h"
20
21#ifndef _WIN32
22#include <cstring>
23#include <format>
24#include <limits>
25#include <stdexcept>
26
27#include <fcntl.h>
28#include <unistd.h>
29#endif
30
31#ifdef _WIN32
32
33CStdEvent::CStdEvent(const bool initialState)
34 : CStdEvent{initialState, true}
35{
36}
37
38CStdEvent::CStdEvent(const bool initialState, const bool manualReset)
39 : event{CreateEvent(nullptr, manualReset, initialState, nullptr)}
40{
41 if (!event)
42 {
43 throw std::runtime_error{"CreateEvent failed"};
44 }
45}
46
47CStdEvent::~CStdEvent()
48{
49 CloseHandle(event);
50}
51
52void CStdEvent::Set()
53{
54 if (!SetEvent(event))
55 {
56 throw std::runtime_error{"SetEvent failed"};
57 }
58}
59
60void CStdEvent::Reset()
61{
62 if (!(ResetEvent(event)))
63 {
64 throw std::runtime_error{"ResetEvent failed"};
65 }
66}
67
68bool CStdEvent::WaitFor(const std::uint32_t milliseconds)
69{
70 switch (WaitForSingleObject(event, milliseconds))
71 {
72 case WAIT_OBJECT_0:
73 return true;
74
75 case WAIT_TIMEOUT:
76 return false;
77
78 default:
79 throw std::runtime_error{"WaitForSingleObject failed"};
80 }
81}
82
83CStdEvent CStdEvent::AutoReset(const bool initialState)
84{
85 return {initialState, false};
86}
87
88#else
89
90[[noreturn]] static void ThrowError(const char *const message)
91{
92 throw std::runtime_error{std::format(fmt: "{}: {}", args: message, args: std::strerror(errno))};
93}
94
95static void ThrowIfFailed(const bool result, const char *const message)
96{
97 if (!result)
98 {
99 ThrowError(message);
100 }
101}
102
103CStdEvent::CStdEvent(const bool initialState)
104{
105 int pipeFD[2];
106 ThrowIfFailed(result: pipe(pipedes: pipeFD) != -1, message: "pipe failed");
107
108 ThrowIfFailed(result: fcntl(fd: pipeFD[0], F_SETFL, fcntl(fd: pipeFD[0], F_GETFL) | O_NONBLOCK) != -1, message: "fcntl failed");
109 ThrowIfFailed(result: fcntl(fd: pipeFD[1], F_SETFL, fcntl(fd: pipeFD[1], F_GETFL) | O_NONBLOCK) != -1, message: "fcntl failed");
110
111 fd[0].store(i: pipeFD[0], m: std::memory_order_release);
112 fd[1].store(i: pipeFD[1], m: std::memory_order_release);
113
114 if (initialState)
115 {
116 Set();
117 }
118}
119
120CStdEvent::~CStdEvent()
121{
122 close(fd: fd[0].load(m: std::memory_order_acquire));
123 close(fd: fd[1].load(m: std::memory_order_acquire));
124}
125
126void CStdEvent::Set()
127{
128 for (char c{42}; write(fd: fd[1].load(m: std::memory_order_acquire), buf: &c, n: 1) == -1; )
129 {
130 switch (errno)
131 {
132 case EINTR:
133 continue;
134
135 case EAGAIN:
136 return;
137
138 default:
139 ThrowError(message: "write failed");
140 }
141 }
142}
143
144void CStdEvent::Reset()
145{
146 for (char c; ;)
147 {
148 if (read(fd: fd[0].load(m: std::memory_order_acquire), buf: &c, nbytes: 1) == -1)
149 {
150 switch (errno)
151 {
152 case EINTR:
153 continue;
154
155 case EAGAIN:
156 return;
157
158 default:
159 ThrowError(message: "read failed");
160 }
161 }
162 }
163}
164
165bool CStdEvent::WaitFor(const std::uint32_t milliseconds)
166{
167 std::array<pollfd, 1> pollFD{pollfd{.fd = fd[0].load(m: std::memory_order_acquire), .events = POLLIN}};
168
169 switch (StdSync::Poll(fds&: pollFD, timeout: milliseconds))
170 {
171 case -1:
172 ThrowError(message: "poll failed");
173
174 case 0:
175 return false;
176
177 default:
178 return true;
179 }
180}
181
182#endif
183