From 9c6f08d2a01f287270b712242245d0ae308ef8a1 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 14 Sep 2025 10:06:23 +1000 Subject: [PATCH] add threading functions to platform, make inputs async --- platform.d | 455 ++++++++++++++++++++++++++++++++++++++++++++++------- util.d | 4 +- 2 files changed, 401 insertions(+), 58 deletions(-) diff --git a/platform.d b/platform.d index 3b62e92..ab7b4be 100644 --- a/platform.d +++ b/platform.d @@ -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); +} } diff --git a/util.d b/util.d index d1166dd..778c2d3 100644 --- a/util.d +++ b/util.d @@ -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 {