Application Context Implementation #4
							
								
								
									
										393
									
								
								src/sys.zig
									
									
									
									
									
								
							
							
						
						
									
										393
									
								
								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)); | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user