Application Context Implementation #4

Closed
kayomn wants to merge 93 commits from event-loop-dev into main
1 changed files with 223 additions and 170 deletions
Showing only changes of commit 38211718e3 - Show all commits

View File

@ -61,12 +61,29 @@ pub const EventLoop = opaque {
/// Internal state of the event loop hidden from the API consumer. /// Internal state of the event loop hidden from the API consumer.
/// ///
const Implementation = struct { const Implementation = struct {
user_prefix: []const u8, user_prefix: []u8,
file_system_semaphore: *ext.SDL_sem, file_system_semaphore: *ext.SDL_sem,
file_system_mutex: *ext.SDL_mutex, file_system_mutex: *ext.SDL_mutex,
file_system_thread: *ext.SDL_Thread, file_system_thread: ?*ext.SDL_Thread,
file_system_messages: ?*FileSystemMessage = null, file_system_messages: ?*FileSystemMessage = null,
///
///
///
const InitError = error {
OutOfSemaphores,
OutOfMutexes,
OutOfMemory,
};
///
///
///
const StartError = error {
OutOfThreads,
AlreadyStarted,
};
/// ///
/// Casts `event_loop` to a [Implementation] reference. /// Casts `event_loop` to a [Implementation] reference.
/// ///
@ -76,6 +93,181 @@ pub const EventLoop = opaque {
fn cast(event_loop: *EventLoop) *Implementation { fn cast(event_loop: *EventLoop) *Implementation {
return @ptrCast(*Implementation, @alignCast(@alignOf(Implementation), event_loop)); 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 /// [LogKind.warning] represents a log message which is a warning about a issue that does not
/// break anything important but is not ideal. /// break anything important but is not ideal.
/// ///
pub const LogKind = enum(c_int) { pub const LogKind = enum(u32) {
info = ext.SDL_LOG_PRIORITY_INFO, info = ext.SDL_LOG_PRIORITY_INFO,
debug = ext.SDL_LOG_PRIORITY_DEBUG, debug = ext.SDL_LOG_PRIORITY_DEBUG,
warning = ext.SDL_LOG_PRIORITY_WARN, warning = ext.SDL_LOG_PRIORITY_WARN,
@ -146,28 +338,7 @@ pub const EventLoop = opaque {
.request = .{.close = .{.file_access = file_access}}, .request = .{.close = .{.file_access = file_access}},
}; };
suspend event_loop.enqueueFileSystemMessage(&file_system_message); suspend Implementation.cast(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);
} }
/// ///
@ -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; 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 /// Attempts to read the contents of the file referenced by `file_access` at the current file
/// cursor position into `buffer`. /// 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; return file_system_message.request.read_file.result;
} }
@ -337,7 +422,7 @@ pub const EventLoop = opaque {
.request = .{.tell_file = .{.file_access = file_access}}, .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; 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; 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(); 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 window = create_window: {
const pos = ext.SDL_WINDOWPOS_UNDEFINED; const pos = ext.SDL_WINDOWPOS_UNDEFINED;
var flags = @as(u32, 0); var flags = @as(u32, 0);
@ -583,39 +656,34 @@ pub fn runGraphics(comptime Errors: anytype, run: GraphicsRunner(Errors)) Errors
defer ext.SDL_DestroyRenderer(renderer); defer ext.SDL_DestroyRenderer(renderer);
var event_loop = EventLoop.Implementation{ var event_loop = EventLoop.Implementation.init() catch |err| {
.file_system_semaphore = ext.SDL_CreateSemaphore(0) orelse { switch (err) {
ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, error.OutOfMemory => ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION,
"Failed to create file-system work scheduler"); "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 { error.OutOfSemaphores => ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION,
ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to create file-system work scheduler"),
"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");
return error.InitFailure; return error.InitFailure;
}; };
defer { defer event_loop.deinit();
ext.SDL_DestroyThread(event_loop.file_system_thread);
ext.SDL_DestroyMutex(event_loop.file_system_mutex); event_loop.start() catch |err| {
ext.SDL_DestroySemaphore(event_loop.file_system_semaphore); 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{ var graphics_context = GraphicsContext.Implementation{
.event = .{ .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)); return run(@ptrCast(*EventLoop, &event_loop), @ptrCast(*GraphicsContext, &graphics_context));
} }