diff --git a/src/sys.zig b/src/sys.zig index f583974..8d41844 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -61,12 +61,29 @@ pub const EventLoop = opaque { /// Internal state of the event loop hidden from the API consumer. /// const Implementation = struct { - user_prefix: []const u8, + user_prefix: []u8, file_system_semaphore: *ext.SDL_sem, file_system_mutex: *ext.SDL_mutex, - file_system_thread: *ext.SDL_Thread, + file_system_thread: ?*ext.SDL_Thread, file_system_messages: ?*FileSystemMessage = null, + /// + /// + /// + const InitError = error { + OutOfSemaphores, + OutOfMutexes, + OutOfMemory, + }; + + /// + /// + /// + const StartError = error { + OutOfThreads, + AlreadyStarted, + }; + /// /// Casts `event_loop` to a [Implementation] reference. /// @@ -76,6 +93,181 @@ pub const EventLoop = opaque { fn cast(event_loop: *EventLoop) *Implementation { return @ptrCast(*Implementation, @alignCast(@alignOf(Implementation), event_loop)); } + + /// + /// + /// + fn deinit(implementation: *Implementation) void { + var message = FileSystemMessage{ + .frame = @frame(), + .request = .exit, + }; + + implementation.enqueueFileSystemMessage(&message); + + { + var status = @as(c_int, 0); + + // SDL2 defines waiting on a null thread reference as a no-op. + // https://wiki.libsdl.org/SDL_WaitThread + ext.SDL_WaitThread(implementation.file_system_thread, &status); + + if (status != 0) { + // TODO: Error check this. + } + } + + ext.SDL_free(implementation.user_prefix.ptr); + ext.SDL_DestroyMutex(implementation.file_system_mutex); + ext.SDL_DestroySemaphore(implementation.file_system_semaphore); + } + + /// + /// Enqueues `message` to the file system message processor of `implementation` to be + /// processed at a later, non-deterministic point. + /// + fn enqueueFileSystemMessage(implementation: *Implementation, + message: *FileSystemMessage) void { + + // TODO: Error check this. + _ = ext.SDL_LockMutex(implementation.file_system_mutex); + + if (implementation.file_system_messages) |messages| { + messages.next = message; + } else { + implementation.file_system_messages = message; + } + + // TODO: Error check these. + _ = ext.SDL_UnlockMutex(implementation.file_system_mutex); + _ = ext.SDL_SemPost(implementation.file_system_semaphore); + } + + /// + /// + /// + fn init() InitError!Implementation { + return Implementation{ + .user_prefix = create_pref_path: { + const path = ext.SDL_GetPrefPath("ona", "ona") orelse { + ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, + "Failed to load user path"); + + return error.OutOfMemory; + }; + + break: create_pref_path path[0 .. std.mem.len(path)]; + }, + + .file_system_semaphore = ext.SDL_CreateSemaphore(0) + orelse return error.OutOfSemaphores, + + .file_system_mutex = ext.SDL_CreateMutex() orelse return error.OutOfMutexes, + .file_system_thread = null, + }; + } + + /// + /// [FileSystemMessage] processing function used by a dedicated worker thread, where `data` + /// is a type-erased reference to a [EventLoop]. + /// + /// The processor returns `0` if it exited normally or any other value if an erroneous exit + /// occured. + /// + fn processFileSystemMessages(data: ?*anyopaque) callconv(.C) c_int { + const implementation = Implementation.cast(@ptrCast(*EventLoop, data orelse unreachable)); + + while (true) { + while (implementation.file_system_messages) |messages| { + switch (messages.request) { + .exit => return 0, + + .log => |*log_request| ext.SDL_LogMessage(ext.SDL_LOG_CATEGORY_APPLICATION, + @enumToInt(log_request.kind), log_request.message.ptr), + + .open => |*open_request| { + switch (open_request.file_system_path.file_system) { + .data => { + // TODO: Implement + open_request.result = error.NotFound; + }, + + .user => { + var path_buffer = std.mem.zeroes([4096]u8); + var path = stack.Fixed(u8){.buffer = path_buffer[0 .. ]}; + + path.pushAll(implementation.user_prefix) catch { + open_request.result = error.NotFound; + + continue; + }; + + if (!open_request.file_system_path.write(path.writer())) { + open_request.result = error.NotFound; + + continue; + } + + if (ext.SDL_RWFromFile(&path_buffer, switch (open_request.mode) { + .readonly => "rb", + .overwrite => "wb", + .append => "ab", + })) |rw_ops| { + open_request.result = @ptrCast(*FileAccess, rw_ops); + } else { + open_request.result = error.NotFound; + } + }, + } + }, + + .close => |*close_request| { + // TODO: Use this result somehow. + _ = ext.SDL_RWclose(@ptrCast(*ext.SDL_RWops, @alignCast( + @alignOf(ext.SDL_RWops), close_request.file_access))); + }, + + .read_file => |read_request| { + // TODO: Implement. + _ = read_request; + }, + + .seek_file => |seek_request| { + // TODO: Implement. + _ = seek_request; + }, + + .tell_file => |tell_request| { + // TODO: Implement. + _ = tell_request; + }, + } + + resume messages.frame; + + implementation.file_system_messages = messages.next; + } + + // TODO: Error check this. + _ = ext.SDL_SemWait(implementation.file_system_semaphore); + } + } + + /// + /// + /// + fn start(implementation: *Implementation) StartError!void { + if (implementation.file_system_thread != null) return error.AlreadyStarted; + + implementation.file_system_thread = ext.SDL_CreateThread(processFileSystemMessages, + "File System Worker", implementation) orelse { + + ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, + "Failed to create file-system work processor"); + + return error.OutOfThreads; + }; + } }; /// @@ -88,7 +280,7 @@ pub const EventLoop = opaque { /// [LogKind.warning] represents a log message which is a warning about a issue that does not /// break anything important but is not ideal. /// - pub const LogKind = enum(c_int) { + pub const LogKind = enum(u32) { info = ext.SDL_LOG_PRIORITY_INFO, debug = ext.SDL_LOG_PRIORITY_DEBUG, warning = ext.SDL_LOG_PRIORITY_WARN, @@ -146,28 +338,7 @@ pub const EventLoop = opaque { .request = .{.close = .{.file_access = file_access}}, }; - suspend event_loop.enqueueFileSystemMessage(&file_system_message); - } - - /// - /// Enqueues `message` to the file system message processor to be processed at a later, non- - /// deterministic point. - /// - fn enqueueFileSystemMessage(event_loop: *EventLoop, message: *FileSystemMessage) void { - const implementation = Implementation.cast(event_loop); - - // TODO: Error check this. - _ = ext.SDL_LockMutex(implementation.file_system_mutex); - - if (implementation.file_system_messages) |messages| { - messages.next = message; - } else { - implementation.file_system_messages = message; - } - - // TODO: Error check these. - _ = ext.SDL_UnlockMutex(implementation.file_system_mutex); - _ = ext.SDL_SemPost(implementation.file_system_semaphore); + suspend Implementation.cast(event_loop).enqueueFileSystemMessage(&file_system_message); } /// @@ -185,7 +356,7 @@ pub const EventLoop = opaque { }}, }; - suspend event_loop.enqueueFileSystemMessage(&file_system_message); + suspend Implementation.cast(event_loop).enqueueFileSystemMessage(&file_system_message); } /// @@ -210,97 +381,11 @@ pub const EventLoop = opaque { }}, }; - suspend event_loop.enqueueFileSystemMessage(&file_system_message); + suspend Implementation.cast(event_loop).enqueueFileSystemMessage(&file_system_message); return file_system_message.request.open.result; } - /// - /// [FileSystemMessage] processing function used by a dedicated worker thread, where `data` is - /// a type-erased reference to a [EventLoop]. - /// - /// The processor returns `0` if it exited normally or any other value if an erroneous exit - /// occured. - /// - fn processFileSystemMessages(data: ?*anyopaque) callconv(.C) c_int { - const implementation = Implementation.cast(@ptrCast(*EventLoop, data orelse unreachable)); - - while (true) { - while (implementation.file_system_messages) |messages| { - switch (messages.request) { - .exit => return 0, - - .log => |*log_request| ext.SDL_LogMessage(ext.SDL_LOG_CATEGORY_APPLICATION, - @enumToInt(log_request.priority), log_request.message), - - .open => |*open_request| { - switch (open_request.path.file_system) { - .data => { - // TODO: Implement - open_request.result = error.NotFound; - }, - - .user => { - var path_buffer = std.mem.zeroes([4096]u8); - var path = stack.Fixed(u8){.buffer = path_buffer[0 .. ]}; - - path.pushAll(implementation.user_prefix) catch { - open_request.result = error.BadFileSystem; - - continue; - }; - - if (!open_request.path.write(path.writer())) { - open_request.result = error.NotFound; - - continue; - } - - if (ext.SDL_RWFromFile(&path_buffer, switch (open_request.mode) { - .readonly => "rb", - .overwrite => "wb", - .append => "ab", - })) |rw_ops| { - open_request.result = @ptrCast(*FileAccess, rw_ops); - } else { - open_request.result = error.NotFound; - } - }, - } - }, - - .close => |*close_request| { - // TODO: Use this result somehow. - _ = ext.SDL_RWclose(@ptrCast(*ext.SDL_RWops, @alignCast( - @alignOf(ext.SDL_RWops), close_request.file_access))); - }, - - .read_file => |read_request| { - // TODO: Implement. - _ = read_request; - }, - - .seek_file => |seek_request| { - // TODO: Implement. - _ = seek_request; - }, - - .tell_file => |tell_request| { - // TODO: Implement. - _ = tell_request; - }, - } - - resume messages.frame; - - implementation.file_system_messages = messages.next; - } - - // TODO: Error check this. - _ = ext.SDL_SemWait(implementation.file_system_semaphore); - } - } - /// /// Attempts to read the contents of the file referenced by `file_access` at the current file /// cursor position into `buffer`. @@ -320,7 +405,7 @@ pub const EventLoop = opaque { }}, }; - suspend event_loop.enqueueFileSystemMessage(&file_system_message); + suspend Implementation.cast(event_loop).enqueueFileSystemMessage(&file_system_message); return file_system_message.request.read_file.result; } @@ -337,7 +422,7 @@ pub const EventLoop = opaque { .request = .{.tell_file = .{.file_access = file_access}}, }; - suspend event_loop.enqueueFileSystemMessage(&file_system_message); + suspend Implementation.cast(event_loop).enqueueFileSystemMessage(&file_system_message); return file_system_message.request.tell_file.result; } @@ -361,7 +446,7 @@ pub const EventLoop = opaque { }, }; - suspend event_loop.enqueueFileSystemMessage(&file_system_message); + suspend Implementation.cast(event_loop).enqueueFileSystemMessage(&file_system_message); return file_system_message.request.seek_file.result; } @@ -546,18 +631,6 @@ pub fn runGraphics(comptime Errors: anytype, run: GraphicsRunner(Errors)) Errors defer ext.SDL_Quit(); - const pref_path = create_pref_path: { - const path = ext.SDL_GetPrefPath("ona", "ona") orelse { - ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to load user path"); - - return error.InitFailure; - }; - - break: create_pref_path path[0 .. std.mem.len(path)]; - }; - - defer ext.SDL_free(pref_path.ptr); - const window = create_window: { const pos = ext.SDL_WINDOWPOS_UNDEFINED; var flags = @as(u32, 0); @@ -583,39 +656,34 @@ pub fn runGraphics(comptime Errors: anytype, run: GraphicsRunner(Errors)) Errors defer ext.SDL_DestroyRenderer(renderer); - var event_loop = EventLoop.Implementation{ - .file_system_semaphore = ext.SDL_CreateSemaphore(0) orelse { - ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, - "Failed to create file-system work scheduler"); + var event_loop = EventLoop.Implementation.init() catch |err| { + switch (err) { + error.OutOfMemory => ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, + "Failed to allocate necessary memory"), - return error.InitFailure; - }, + error.OutOfMutexes => ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, + "Failed to create file-system work lock"), - .file_system_mutex = ext.SDL_CreateMutex() orelse { - ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, - "Failed to create file-system work lock"); - - return error.InitFailure; - }, - - .file_system_thread = unreachable, - .user_prefix = pref_path, - }; - - event_loop.file_system_thread = ext.SDL_CreateThread( - EventLoop.processFileSystemMessages, "File System Worker", &event_loop) orelse { - - ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, - "Failed to create file-system work processor"); + error.OutOfSemaphores => ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, + "Failed to create file-system work scheduler"), + } return error.InitFailure; }; - defer { - ext.SDL_DestroyThread(event_loop.file_system_thread); - ext.SDL_DestroyMutex(event_loop.file_system_mutex); - ext.SDL_DestroySemaphore(event_loop.file_system_semaphore); - } + defer event_loop.deinit(); + + event_loop.start() catch |err| { + switch (err) { + // Not possible for it to have already been started. + error.AlreadyStarted => unreachable, + + error.OutOfThreads => ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, + "Failed to start file-system work processor"), + } + + return error.InitFailure; + }; var graphics_context = GraphicsContext.Implementation{ .event = .{ @@ -623,20 +691,5 @@ pub fn runGraphics(comptime Errors: anytype, run: GraphicsRunner(Errors)) Errors }, }; - var message = EventLoop.FileSystemMessage{ - .frame = @frame(), - .request = .exit, - }; - - @ptrCast(*EventLoop, event_loop).enqueueFileSystemMessage(&message); - - var status = @as(c_int, 0); - - ext.SDL_WaitThread(event_loop.file_system_thread, &status); - - if (status != 0) { - // TODO: Error check this. - } - return run(@ptrCast(*EventLoop, &event_loop), @ptrCast(*GraphicsContext, &graphics_context)); }