add threading functions to platform, make inputs async

This commit is contained in:
Matthew 2025-09-14 10:06:23 +10:00
parent 29a98de0e0
commit 9c6f08d2a0
2 changed files with 401 additions and 58 deletions

View File

@ -12,6 +12,8 @@ import std.stdio;
import core.memory;
import core.thread.osthread;
import core.time;
import core.volatile;
import core.atomic;
const WINDOW_EDGE_BUFFER = 50;
@ -45,16 +47,6 @@ enum Input
alias KBI = Input;
version(linux)
{
import core.sys.posix.dlfcn;
import core.sys.posix.sys.mman;
import core.sys.linux.sys.inotify;
import core.sys.linux.fcntl;
import core.sys.posix.unistd;
import core.stdc.string : strlen;
struct InputEvent
{
Input key;
@ -65,12 +57,249 @@ struct InputEvent
i32 rel_y;
}
struct Inputs
{
DLList!(InputEvent) list;
Arena arena;
}
Inputs*
GetEvents(PlatformWindow* window)
{
Lock(&window.input_mutex);
Inputs* inputs = &window.inputs[window.input_idx];
window.input_idx = (window.input_idx + 1) % 2;
ResetInputs(&window.inputs[window.input_idx]);
Unlock(&window.input_mutex);
return inputs;
}
Inputs*
GetInputs(PlatformWindow* window)
{
Lock(&window.input_mutex);
Inputs* inputs = window.inputs.ptr + window.input_idx;
return inputs;
}
void
ReturnInputs(PlatformWindow* window)
{
Unlock(&window.input_mutex);
}
enum SysMessageType
{
None,
Quit,
LockCursor,
UnlockCursor,
HideCursor,
ShowCursor,
}
alias SMT = SysMessageType;
struct Mut
{
bool locked;
}
struct TicketMut
{
u64 ticket;
u64 next_ticket;
}
Mut
CreateMut()
{
return Mut(locked: false);
}
bool
TryLock(Mut* mut)
{
return cas!(MemoryOrder.acq, MemoryOrder.raw)(&mut.locked, false, true);
}
void
Unlock(Mut* mut)
{
atomicStore!(MemoryOrder.rel, bool)(mut.locked, false);
}
TicketMut
CreateTicketMut()
{
return TicketMut(ticket: 0, next_ticket: 1);
}
void
Lock(TicketMut* mut)
{
u64 ticket = atomicFetchAdd!(MemoryOrder.acq, u64)(mut.ticket, 1);
while (ticket != volatileLoad(&mut.next_ticket)) {}
}
void
Unlock(TicketMut* mut)
{
atomicFetchAdd!(MemoryOrder.rel, u64)(mut.next_ticket, 1);
}
const DNode!(SysMessage) g_sys_message;
DNode!(SysMessage)* g_NIL_MSG;
struct SysMessage
{
SysMessageType type;
}
struct MessageQueue
{
DLList!(SysMessage) list;
DLList!(SysMessage) free_list;
TicketMut mut;
Arena arena;
}
MessageQueue
CreateMessageQueue()
{
if (g_NIL_MSG == null)
{
g_NIL_MSG = cast(DNode!(SysMessage)*)&g_sys_message;
}
MessageQueue queue = {
list: {
first: g_NIL_MSG,
last: g_NIL_MSG,
},
free_list: {
first: g_NIL_MSG,
last: g_NIL_MSG,
},
mut: CreateTicketMut(),
arena: CreateArena(MB(1)),
};
return queue;
}
pragma(inline) bool
Nil(DNode!(SysMessage)* msg)
{
return msg == null || msg == g_NIL_MSG;
}
DNode!(SysMessage)*
Pop(MessageQueue* queue)
{
Lock(&queue.mut);
DNode!(SysMessage)* node = DLLPop(&queue.list, g_NIL_MSG);
Unlock(&queue.mut);
return node;
}
void
Push(PlatformWindow* w, SysMessageType msg)
{
Lock(&w.msg_queue.mut);
DNode!(SysMessage)* node = g_NIL_MSG;
if (Nil(w.msg_queue.free_list.first))
{
node = Alloc!(DNode!(SysMessage))(&w.msg_queue.arena);
}
else
{
node = DLLPop(&w.msg_queue.free_list, g_NIL_MSG);
}
node.value.type = msg;
DLLPush(&w.msg_queue.list, node, g_NIL_MSG);
Unlock(&w.msg_queue.mut);
}
void
PushFree(PlatformWindow* w, DNode!(SysMessage)* node)
{
DLLPush(&w.msg_queue.list, node, g_NIL_MSG);
}
version(linux)
{
import core.sys.posix.dlfcn;
import core.sys.posix.sys.mman;
import core.sys.linux.sys.inotify;
import core.sys.linux.fcntl;
import core.sys.posix.unistd;
import core.sys.posix.pthread : PThread = pthread_t,
PThreadCond = pthread_cond_t,
PThreadMutex = pthread_mutex_t,
PThreadMutexInit = pthread_mutex_init,
PThreadCondInit = pthread_cond_init,
PThreadCreate = pthread_create,
PThreadMutexLock = pthread_mutex_lock,
PThreadCondWait = pthread_cond_wait,
PThreadMutexUnlock = pthread_mutex_unlock,
PThreadCondSignal = pthread_cond_signal,
PThreadExit = pthread_exit;
import core.stdc.string : strlen;
struct SysThread
{
PThread handle;
PThreadCond cond;
PThreadMutex mut;
}
alias PThreadProc = extern (C) void* function(void*);
SysThread
CreateThread(void* proc, void* param)
{
SysThread thread;
assert(!PThreadMutexInit(&thread.mut, null));
assert(!PThreadCondInit(&thread.cond, null));
assert(!PThreadCreate(&thread.handle, null, cast(PThreadProc)proc, param));
return thread;
}
void
Suspend(SysThread* thread)
{
PThreadMutexLock(&thread.mut);
PThreadCondWait(&thread.cond, &thread.mut);
PThreadMutexUnlock(&thread.mut);
}
void
Wake(SysThread* thread)
{
PThreadCondSignal(&thread.cond);
}
void
Kill()
{
PThreadExit(null);
}
void
ResetInputs(Inputs* inputs)
{
@ -118,7 +347,12 @@ struct PlatformWindow
i32 mouse_prev_y;
bool locked_cursor;
bool close;
Inputs inputs;
MessageQueue msg_queue;
u32 input_idx;
Inputs[2] inputs;
TicketMut input_mutex;
};
struct Library
@ -131,7 +365,16 @@ struct Function
void* ptr;
};
void CheckErr(PlatformWindow *window, xcb_void_cookie_t *cookie, xcb_generic_error_t *err, string msg)
pragma(inline) bool
NilErr(PlatformWindow* window, xcb_void_cookie_t* cookie, xcb_generic_error_t* err)
{
bool result = err == null;
pureFree(err);
return result;
}
pragma(inline) void
CheckErr(PlatformWindow *window, xcb_void_cookie_t *cookie, xcb_generic_error_t *err, string msg)
{
assert(err == null, msg);
pureFree(err);
@ -143,9 +386,12 @@ CreateWindow(string name, u16 width, u16 height)
PlatformWindow window = {
w: width,
h: height,
inputs: {
arena: CreateArena(MB(1)),
},
input_mutex: CreateTicketMut(),
msg_queue: CreateMessageQueue(),
inputs: [
{ arena: CreateArena(MB(1)) },
{ arena: CreateArena(MB(1)) },
],
};
assert(width > 0 && height > 0, "CreateWindow error: width and height must be above 0");
@ -257,15 +503,14 @@ CreateWindow(string name, u16 width, u16 height)
xcb_xfixes_query_version(window.conn, 4, 0);
LockCursor(&window);
UnlockCursor(&window);
return window;
};
void
bool
LockCursor(PlatformWindow* window)
{
bool result = window.locked_cursor;
if (!window.locked_cursor)
{
u32 counter = 0;
@ -282,18 +527,25 @@ LockCursor(PlatformWindow* window)
}
if (grab_reply.status == XCB_GRAB_STATUS_SUCCESS)
{
result = true;
break;
}
if (counter > 5)
{
break;
}
assert(counter < 5, "Unable to grab cursor");
counter += 1;
Thread.sleep(dur!("msecs")(50));
}
HideCursor(window);
window.locked_cursor = true;
window.locked_cursor = result;
}
return result;
}
void
@ -307,23 +559,20 @@ UnlockCursor(PlatformWindow* window)
}
}
// TODO: improve error handling for show/hide cursor
void
bool
HideCursor(PlatformWindow* window)
{
xcb_void_cookie_t hide_cursor_cookie = xcb_xfixes_hide_cursor_checked(window.conn, window.window);
xcb_generic_error_t *error = xcb_request_check(window.conn, hide_cursor_cookie);
CheckErr(window, &hide_cursor_cookie, error, "xcb_xfixes_hide_cursor_checked failure");
pureFree(error);
return NilErr(window, &hide_cursor_cookie, error);
}
void
bool
ShowCursor(PlatformWindow* window)
{
xcb_void_cookie_t show_cursor_cookie = xcb_xfixes_show_cursor_checked(window.conn, window.window);
xcb_generic_error_t *error = xcb_request_check(window.conn, show_cursor_cookie);
CheckErr(window, &show_cursor_cookie, error, "xcb_xfixes_show_cursor_checked failure");
pureFree(error);
return NilErr(window, &show_cursor_cookie, error);
}
void
@ -337,34 +586,91 @@ FlushEvents(PlatformWindow* window)
}
void
HandleEvents(PlatformWindow* window)
HandleEvents(void* window_ptr)
{
PlatformWindow* w = cast(PlatformWindow*)window_ptr;
DNode!(SysMessage)* sys_msg = g_NIL_MSG;
xcb_generic_event_t* e;
bool ignore_mouse_events = false;
Inputs* inputs = &window.inputs;
ResetInputs(inputs);
do
for(;;)
{
e = xcb_poll_for_event(window.conn);
if (w.close)
{
Kill();
}
sys_msg = Pop(&w.msg_queue);
if (!Nil(sys_msg))
{
SysMessage msg = sys_msg.value;
switch (msg.type)
{
case SMT.Quit:
{
w.close = true;
} break;
case SMT.LockCursor:
{
for(u64 i = 0; i < 5; i += 1)
{
if (LockCursor(w))
{
break;
}
}
} break;
case SMT.UnlockCursor:
{
UnlockCursor(w);
} break;
case SMT.HideCursor:
{
for (u64 i = 0; i < 5; i += 1)
{
if (HideCursor(w))
{
break;
}
}
} break;
case SMT.ShowCursor:
{
for(u64 i = 0; i < 5; i += 1)
{
if (ShowCursor(w))
{
break;
}
}
} break;
default: break;
}
}
e = xcb_poll_for_event(w.conn);
if (e)
{
Inputs* inputs = GetInputs(w);
switch (e.response_type & ~0x80)
{
case XCB_CLIENT_MESSAGE:
{
xcb_client_message_event_t* msg = cast(xcb_client_message_event_t*)e;
if (msg.window != window.window)
if (msg.window != w.window)
{
break;
}
if (msg.data.data32[0] == window.close_event)
if (msg.data.data32[0] == w.close_event)
{
window.close = true;
w.close = true;
}
} break;
case XCB_KEY_RELEASE:
@ -374,7 +680,7 @@ HandleEvents(PlatformWindow* window)
bool pressed = e.response_type == XCB_KEY_PRESS;
xcb_keycode_t code = keyboard_event.detail;
KeySym key_sym = XkbKeycodeToKeysym(window.display, cast(KeyCode)code, 0, 0);
KeySym key_sym = XkbKeycodeToKeysym(w.display, cast(KeyCode)code, 0, 0);
KBI input = ConvertInput(key_sym);
if (input != KBI.None)
@ -414,48 +720,50 @@ HandleEvents(PlatformWindow* window)
static bool first = true;
if (first)
{
window.mouse_prev_x = x;
window.mouse_prev_y = y;
w.mouse_prev_x = x;
w.mouse_prev_y = y;
first = false;
}
if (x > 0 || y > 0)
{
PushMotion(inputs, window.mouse_prev_x-x, window.mouse_prev_y-y, x, y);
PushMotion(inputs, w.mouse_prev_x-x, w.mouse_prev_y-y, x, y);
}
window.mouse_prev_x = x;
window.mouse_prev_y = y;
w.mouse_prev_x = x;
w.mouse_prev_y = y;
if (window.locked_cursor &&
(x < WINDOW_EDGE_BUFFER || y < WINDOW_EDGE_BUFFER || x > window.w - WINDOW_EDGE_BUFFER || y > window.h - WINDOW_EDGE_BUFFER))
if (w.locked_cursor &&
(x < WINDOW_EDGE_BUFFER || y < WINDOW_EDGE_BUFFER || x > w.w - WINDOW_EDGE_BUFFER || y > w.h - WINDOW_EDGE_BUFFER))
{
i16 new_x = cast(i16)(window.w / 2);
i16 new_y = cast(i16)(window.h / 2);
xcb_warp_pointer(window.conn, window.window, window.window, 0, 0, cast(i16)window.w, cast(i16)window.h, new_x, new_y);
window.mouse_prev_x = new_x;
window.mouse_prev_y = new_y;
i16 new_x = cast(i16)(w.w / 2);
i16 new_y = cast(i16)(w.h / 2);
xcb_warp_pointer(w.conn, w.window, w.window, 0, 0, cast(i16)w.w, cast(i16)w.h, new_x, new_y);
w.mouse_prev_x = new_x;
w.mouse_prev_y = new_y;
ignore_mouse_events = true;
}
} break;
case XCB_CONFIGURE_NOTIFY:
{
xcb_configure_notify_event_t* config_event = cast(xcb_configure_notify_event_t*)e;
if (window.w != config_event.width || window.h != config_event.height)
if (w.w != config_event.width || w.h != config_event.height)
{
window.w = config_event.width;
window.h = config_event.height;
w.w = config_event.width;
w.h = config_event.height;
}
} break;
default:
break;
}
ReturnInputs(w);
}
}
while (e);
}
Library LoadLibrary(string name)
Library
LoadLibrary(string name)
{
Library lib = {
ptr: null,
@ -466,7 +774,8 @@ Library LoadLibrary(string name)
return lib;
};
Function LoadFunction(Library lib, string name)
Function
LoadFunction(Library lib, string name)
{
Function fn = {
ptr: null,
@ -915,5 +1224,39 @@ struct WatchEvent
version(Windows)
{
import core.sys.windows.windows;
struct SysThread
{
HANDLE handle;
}
alias Win32ThreadProc = extern (C) u32 function(void*);
SysThread
CreateThread(void* proc, void* param)
{
SysThread thread;
CreateThread(null, 0, cast(Win32ThreadProc)proc, param, 0, null);
return thread;
}
void
Suspend(SysThread* thread)
{
SuspendThread(thread.handle);
}
void
Wake(SysThread* thread)
{
ResumeThread(thread.handle);
}
void
Kill()
{
ExitThread(0);
}
}

4
util.d
View File

@ -135,13 +135,13 @@ ConcatInPlace(T)(T* list, T* to_concat)
}
U*
DLLPop(T, U)(T* list, T* nil)
DLLPop(T, U)(T* list, U* nil)
{
U* node = list.first;
if (list.first == list.last)
{
list.first = list.last = list.nil;
list.first = list.last = nil;
}
else
{