libgui 2.01
libgui is a GUI library for the GameCube, Wii, and Wii U
 
Loading...
Searching...
No Matches
WutThreadDriver.cpp
1/****************************************************************************
2 * Platform Abstraction Layer (WUT driver)
3 * Daryl Borth 2026
4 * WutThreadDriver.cpp
5 *
6 * Wraps wut/coreinit OSThread and OSMutex
7 ***************************************************************************/
8#include <coreinit/thread.h>
9#include <coreinit/mutex.h>
10#include <coreinit/time.h>
11#include <coreinit/condition.h>
12#include <malloc.h>
13
14#include "WutThreadDriver.h"
15
16namespace
17{
18 struct WutThreadHandle
19 {
20 OSThread * thread = nullptr;
21 void * stack = nullptr;
22 ThreadEntry entry = nullptr;
23 void * arg = nullptr;
24 };
25
26 // OSThreadEntryPointFn is int(*)(int argc, const char ** argv). There is
27 // no generic void* userdata slot in OSCreateThread - by convention (as
28 // used throughout wut homebrew) the raw argv pointer itself, not *argv,
29 // is repurposed to carry a single opaque payload pointer.
30 int WutThreadTrampoline(int argc, const char ** argv)
31 {
32 (void)argc;
33 WutThreadHandle * handle = reinterpret_cast<WutThreadHandle *>(argv);
34 handle->entry(handle->arg);
35 return 0;
36 }
37
38 int MapWutPriority(ThreadPriority priority)
39 {
40 switch (priority)
41 {
42 case ThreadPriority::Idle: return 31;
43 case ThreadPriority::Low: return 24;
44 case ThreadPriority::Normal: return 16;
45 case ThreadPriority::High: return 10;
46 case ThreadPriority::TimeCritical: return 3;
47 default: return 16;
48 }
49 }
50}
51
53{
54}
55
59
60bool WutThreadDriver::createThread(ThreadEntry entry, void * arg, uint32_t stackSize, ThreadPriority priority, void ** outHandle)
61{
62 WutThreadHandle * handle = new WutThreadHandle();
63 handle->entry = entry;
64 handle->arg = arg;
65
66 // OSThread control blocks must be allocated by the caller (8-byte
67 // aligned per wut's WUT_CHECK_SIZE/OSThread layout); the stack likewise
68 // needs a real aligned allocation since OSCreateThread takes the raw
69 // top-of-stack address, not a size hint.
70 handle->thread = static_cast<OSThread *>(memalign(8, sizeof(OSThread)));
71 if(!handle->thread)
72 {
73 delete handle;
74 return false;
75 }
76
77 uint32_t alignedStackSize = (stackSize + 31) & ~31u;
78 handle->stack = memalign(32, alignedStackSize);
79 if(!handle->stack)
80 {
81 free(handle->thread);
82 delete handle;
83 return false;
84 }
85
86 // Publish the handle to the caller's storage BEFORE resuming the new
87 // thread, same rationale as OgcThreadDriver::createThread - entry() may
88 // reference *outHandle itself as its first action.
89 *outHandle = handle;
90 uint8_t * stackTop = static_cast<uint8_t *>(handle->stack) + alignedStackSize;
91 int nativePriority = MapWutPriority(priority);
92
93 BOOL ok = OSCreateThread(handle->thread, WutThreadTrampoline, 0, reinterpret_cast<char *>(handle),
94 stackTop, alignedStackSize, nativePriority, OS_THREAD_ATTRIB_AFFINITY_ANY);
95
96 if(!ok)
97 {
98 *outHandle = nullptr;
99 free(handle->stack);
100 free(handle->thread);
101 delete handle;
102 return false;
103 }
104
105 // OSCreateThread starts threads with a suspend count of 1 - they don't
106 // run until explicitly resumed.
107 OSResumeThread(handle->thread);
108
109 return true;
110}
111
113{
114 if(!thread)
115 return;
116
117 WutThreadHandle * handle = static_cast<WutThreadHandle *>(thread);
118 OSJoinThread(handle->thread, nullptr);
119 free(handle->stack);
120 free(handle->thread);
121 delete handle;
122}
123
125{
126 if(!thread)
127 return;
128
129 // OSCancelThread only requests cancellation - it takes effect the next
130 // time the target thread hits a cancellation point (implicitly tested
131 // in mutex/spinlock operations), so there's no way to know from here
132 // when the thread is actually done touching its stack/control block.
133 // Best effort, request the cancellation and intentionally leak the
134 // thread/stack allocation rather than risk freeing memory the thread
135 // may still be running on. Prefer signalling the thread to exit
136 // cooperatively and calling joinThread() instead, which does clean up
137 // properly.
138 WutThreadHandle * handle = static_cast<WutThreadHandle *>(thread);
139 OSCancelThread(handle->thread);
140 delete handle;
141}
142
144{
145 if(!thread)
146 return;
147
148 OSSuspendThread(static_cast<WutThreadHandle *>(thread)->thread);
149}
150
152{
153 if(!thread)
154 return;
155
156 OSResumeThread(static_cast<WutThreadHandle *>(thread)->thread);
157}
158
160{
161 if(!thread)
162 return false;
163
164 return OSIsThreadSuspended(static_cast<WutThreadHandle *>(thread)->thread) != FALSE;
165}
166
168{
169 OSMutex * mutex = new OSMutex();
170 OSInitMutex(mutex);
171 return mutex;
172}
173
175{
176 if(!mutex)
177 return;
178
179 delete static_cast<OSMutex *>(mutex);
180}
181
183{
184 if(!mutex)
185 return;
186
187 OSLockMutex(static_cast<OSMutex *>(mutex));
188}
189
191{
192 if(!mutex)
193 return;
194
195 OSUnlockMutex(static_cast<OSMutex *>(mutex));
196}
197
199{
200 OSCondition * cond = new OSCondition();
201 OSInitCond(cond);
202 return cond;
203}
204
206{
207 if(!cond)
208 return;
209
210 delete static_cast<OSCondition *>(cond);
211}
212
213void WutThreadDriver::waitCond(void * cond, void * mutex)
214{
215 if(!cond || !mutex)
216 return;
217
218 // OSWaitCond unlocks mutex, sleeps, and reacquires it before
219 // returning - same contract as ThreadDriver::waitCond.
220 OSWaitCond(static_cast<OSCondition *>(cond), static_cast<OSMutex *>(mutex));
221}
222
224{
225 if(!cond)
226 return;
227
228 // OSSignalCond already wakes every waiter (it's notify_all, not
229 // notify_one) - matches ThreadDriver::signalCond's contract exactly.
230 OSSignalCond(static_cast<OSCondition *>(cond));
231}
232
233
235{
236 OSSleepTicks(OSMillisecondsToTicks(ms));
237}
238
240{
241 // OSGetCurrentThread() returns the OSThread* of the calling thread -
242 // including the app's original/main thread, which (unlike threads
243 // created via createThread() above) was never itself heap-allocated
244 // by this driver. The pointer alone is a stable, comparable identity
245 // for as long as that thread exists, which is all ThreadId needs.
246 return reinterpret_cast<uintptr_t>(OSGetCurrentThread());
247}
void waitCond(void *cond, void *mutex) override
Atomically unlocks mutex and blocks the calling thread until signalCond() is called,...
void sleepMilliseconds(uint32_t ms) override
Sleeps the calling thread for at least the given duration.
void shutdown() override
Shuts the threading backend down. Every thread must already have been joined.
void lockMutex(void *mutex) override
Blocks until the mutex is acquired.
void joinThread(void *thread) override
Blocks until the thread exits, then releases the handle.
void resumeThread(void *thread) override
Resumes a thread suspended with suspendThread().
void * createCond() override
Creates a condition variable.
void suspendThread(void *thread) override
Suspends a running thread until resumeThread() is called.
void * createMutex() override
Creates an unlocked mutex.
void destroyMutex(void *mutex) override
Destroys a mutex created by createMutex(). It must not be locked.
void signalCond(void *cond) override
Wakes every thread currently blocked in waitCond() on this condition variable.
void destroyCond(void *cond) override
Destroys a condition variable created by createCond(). No thread may be waiting on it.
void cancelThread(void *thread) override
Requests the thread stop running and releases the handle without blocking on it.
uintptr_t getCurrentThreadId() override
Returns an opaque, comparable identifier for the calling thread.
bool isThreadSuspended(void *thread) override
void unlockMutex(void *mutex) override
Releases a mutex held by the calling thread.
bool createThread(ThreadEntry entry, void *arg, uint32_t stackSize, ThreadPriority priority, void **outHandle) override
Creates and starts a thread running entry(arg).
void init() override
Initializes the threading backend.
ThreadPriority
Abstract thread priority, mapped to each platform's native priority scheme.
Definition Thread.h:18