libgui 3.00
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, void * argv). OSCreateThread
27 // forwards argv untouched, so it doubles as the single opaque payload
28 // pointer for the thread
29 int WutThreadTrampoline(int argc, void * argv)
30 {
31 (void)argc;
32 WutThreadHandle * handle = static_cast<WutThreadHandle *>(argv);
33 handle->entry(handle->arg);
34 return 0;
35 }
36
37 int MapWutPriority(ThreadPriority priority)
38 {
39 switch (priority)
40 {
41 case ThreadPriority::Idle: return 31;
42 case ThreadPriority::Low: return 24;
43 case ThreadPriority::Normal: return 16;
44 case ThreadPriority::High: return 10;
45 case ThreadPriority::TimeCritical: return 3;
46 default: return 16;
47 }
48 }
49}
50
52{
53}
54
58
59bool WutThreadDriver::createThread(ThreadEntry entry, void * arg, uint32_t stackSize, ThreadPriority priority, void ** outHandle)
60{
61 WutThreadHandle * handle = new WutThreadHandle();
62 handle->entry = entry;
63 handle->arg = arg;
64
65 // OSThread control blocks must be allocated by the caller (8-byte
66 // aligned per wut's WUT_CHECK_SIZE/OSThread layout); the stack likewise
67 // needs a real aligned allocation since OSCreateThread takes the raw
68 // top-of-stack address, not a size hint.
69 handle->thread = static_cast<OSThread *>(memalign(8, sizeof(OSThread)));
70 if(!handle->thread)
71 {
72 delete handle;
73 return false;
74 }
75
76 uint32_t alignedStackSize = (stackSize + 31) & ~31u;
77 handle->stack = memalign(32, alignedStackSize);
78 if(!handle->stack)
79 {
80 free(handle->thread);
81 delete handle;
82 return false;
83 }
84
85 // Publish the handle to the caller's storage BEFORE resuming the new
86 // thread, same rationale as OgcThreadDriver::createThread - entry() may
87 // reference *outHandle itself as its first action.
88 *outHandle = handle;
89 uint8_t * stackTop = static_cast<uint8_t *>(handle->stack) + alignedStackSize;
90 int nativePriority = MapWutPriority(priority);
91
92 BOOL ok = OSCreateThread(handle->thread, WutThreadTrampoline, 0, handle,
93 stackTop, alignedStackSize, nativePriority, OS_THREAD_ATTRIB_AFFINITY_ANY);
94
95 if(!ok)
96 {
97 *outHandle = nullptr;
98 free(handle->stack);
99 free(handle->thread);
100 delete handle;
101 return false;
102 }
103
104 // OSCreateThread starts threads with a suspend count of 1 - they don't
105 // run until explicitly resumed.
106 OSResumeThread(handle->thread);
107
108 return true;
109}
110
112{
113 if(!thread)
114 return;
115
116 WutThreadHandle * handle = static_cast<WutThreadHandle *>(thread);
117 OSJoinThread(handle->thread, nullptr);
118 free(handle->stack);
119 free(handle->thread);
120 delete handle;
121}
122
124{
125 if(!thread)
126 return;
127
128 // OSCancelThread only requests cancellation - it takes effect the next
129 // time the target thread hits a cancellation point (implicitly tested
130 // in mutex/spinlock operations), so there's no way to know from here
131 // when the thread is actually done touching its stack/control block.
132 // Best effort, request the cancellation and intentionally leak the
133 // thread/stack allocation rather than risk freeing memory the thread
134 // may still be running on. Prefer signalling the thread to exit
135 // cooperatively and calling joinThread() instead, which does clean up
136 // properly.
137 WutThreadHandle * handle = static_cast<WutThreadHandle *>(thread);
138 OSCancelThread(handle->thread);
139 delete handle;
140}
141
143{
144 if(!thread)
145 return;
146
147 OSSuspendThread(static_cast<WutThreadHandle *>(thread)->thread);
148}
149
151{
152 if(!thread)
153 return;
154
155 OSResumeThread(static_cast<WutThreadHandle *>(thread)->thread);
156}
157
159{
160 if(!thread)
161 return false;
162
163 return OSIsThreadSuspended(static_cast<WutThreadHandle *>(thread)->thread) != FALSE;
164}
165
167{
168 OSMutex * mutex = new OSMutex();
169 OSInitMutex(mutex);
170 return mutex;
171}
172
174{
175 if(!mutex)
176 return;
177
178 delete static_cast<OSMutex *>(mutex);
179}
180
182{
183 if(!mutex)
184 return;
185
186 OSLockMutex(static_cast<OSMutex *>(mutex));
187}
188
190{
191 if(!mutex)
192 return;
193
194 OSUnlockMutex(static_cast<OSMutex *>(mutex));
195}
196
198{
199 OSCondition * cond = new OSCondition();
200 OSInitCond(cond);
201 return cond;
202}
203
205{
206 if(!cond)
207 return;
208
209 delete static_cast<OSCondition *>(cond);
210}
211
212void WutThreadDriver::waitCond(void * cond, void * mutex)
213{
214 if(!cond || !mutex)
215 return;
216
217 // OSWaitCond unlocks mutex, sleeps, and reacquires it before
218 // returning - same contract as ThreadDriver::waitCond.
219 OSWaitCond(static_cast<OSCondition *>(cond), static_cast<OSMutex *>(mutex));
220}
221
223{
224 if(!cond)
225 return;
226
227 // OSSignalCond already wakes every waiter (it's notify_all, not
228 // notify_one) - matches ThreadDriver::signalCond's contract exactly.
229 OSSignalCond(static_cast<OSCondition *>(cond));
230}
231
232
234{
235 OSSleepTicks(OSMillisecondsToTicks(ms));
236}
237
239{
240 // OSGetCurrentThread() returns the OSThread* of the calling thread -
241 // including the app's original/main thread, which (unlike threads
242 // created via createThread() above) was never itself heap-allocated
243 // by this driver. The pointer alone is a stable, comparable identity
244 // for as long as that thread exists, which is all ThreadId needs.
245 return reinterpret_cast<uintptr_t>(OSGetCurrentThread());
246}
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