Refactor SDL2 interface code
This commit is contained in:
		
							parent
							
								
									1a28dc2404
								
							
						
					
					
						commit
						32bb049f73
					
				| @ -6,16 +6,16 @@ const sys = @import("./sys.zig"); | ||||
| /// Starts the the game engine. | ||||
| /// | ||||
| pub fn main() anyerror!void { | ||||
|     return nosuspend await async sys.runGraphics(anyerror, run); | ||||
|     return nosuspend await async sys.display(anyerror, run); | ||||
| } | ||||
| 
 | ||||
| fn run(app: *sys.AppContext, graphics: *sys.GraphicsContext) anyerror!void { | ||||
| fn run(app: *sys.App, graphics: *sys.Graphics) anyerror!void { | ||||
|     var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | ||||
| 
 | ||||
|     defer _ = gpa.deinit(); | ||||
| 
 | ||||
|     { | ||||
|         var file_access = try app.data().open(try sys.Path.joined(&.{"ona.lua"}), .readonly); | ||||
|         var file_access = try app.data.open(try sys.Path.joined(&.{"ona.lua"}), .readonly); | ||||
| 
 | ||||
|         defer file_access.close(); | ||||
| 
 | ||||
| @ -27,7 +27,7 @@ fn run(app: *sys.AppContext, graphics: *sys.GraphicsContext) anyerror!void { | ||||
| 
 | ||||
|         if ((try file_access.read(buffer)) != file_size) return error.ScriptLoadFailure; | ||||
| 
 | ||||
|         sys.Log.debug.write(buffer); | ||||
|         app.log(.debug, buffer); | ||||
|     } | ||||
| 
 | ||||
|     while (graphics.poll()) |_| { | ||||
|  | ||||
| @ -1,5 +1,3 @@ | ||||
| const Archive = @import("./sys/Archive.zig"); | ||||
| 
 | ||||
| const ext = @cImport({ | ||||
|     @cInclude("SDL2/SDL.h"); | ||||
| }); | ||||
| @ -9,222 +7,43 @@ const ona = @import("ona"); | ||||
| const std = @import("std"); | ||||
| 
 | ||||
| /// | ||||
| /// A thread-safe platform abstraction over multiplexing system I/O processing and event handling. | ||||
| /// Thread-safe platform abstraction over multiplexing system I/O processing and event handling. | ||||
| /// | ||||
| pub const AppContext = opaque { | ||||
|     /// | ||||
|     /// Linked list of asynchronous messages chained together to be processed by the work processor. | ||||
|     /// | ||||
|     const Message = struct { | ||||
|         next: ?*Message = null, | ||||
| 
 | ||||
|         kind: union(enum) { | ||||
|             quit, | ||||
| 
 | ||||
|             task: struct { | ||||
|                 data: *anyopaque, | ||||
|                 action: fn (*anyopaque) void, | ||||
|                 frame: anyframe, | ||||
|             }, | ||||
|         }, | ||||
|     }; | ||||
| 
 | ||||
|     /// | ||||
|     /// Internal state of the event loop hidden from the API consumer. | ||||
|     /// | ||||
|     const Implementation = struct { | ||||
|         user_path_prefix: [*]u8, | ||||
|         data_file_system: FileSystem, | ||||
|         user_file_system: FileSystem, | ||||
| pub const App = struct { | ||||
|     message_chain: ?*Message = null, | ||||
|     message_semaphore: *ext.SDL_sem, | ||||
|     message_mutex: *ext.SDL_mutex, | ||||
|         message_thread: ?*ext.SDL_Thread, | ||||
|         messages: ?*Message = null, | ||||
|     data: FileSystem, | ||||
|     user: FileSystem, | ||||
| 
 | ||||
|     /// | ||||
|         /// [StartError.OutOfSemaphores] indicates that the process has no more semaphores available | ||||
|         /// to it for use, meaning an [Implementation] may not be initialized at this time. | ||||
|     /// Enqueues `message` to the message chain in `app`. | ||||
|     /// | ||||
|         /// [StartError.OutOfMutexes] indicates that the process has no more mutexes available to it | ||||
|         /// for use, meaning an [Implementation] may not be initialized at this time. | ||||
|         /// | ||||
|         /// [StartError.OutOfMemory] indicates that the process has no more memory available to it | ||||
|         /// for use, meaning an [Implementation] may not be initialized at this time. | ||||
|         /// | ||||
|         const InitError = error { | ||||
|             OutOfSemaphores, | ||||
|             OutOfMutexes, | ||||
|             OutOfMemory, | ||||
|         }; | ||||
| 
 | ||||
|         /// | ||||
|         /// [StartError.OutOfThreads] indicates that the process has no more threads available to it | ||||
|         /// to use, meaning that no asynchronous work may be started on an [Implementation] at this | ||||
|         /// time. | ||||
|         /// | ||||
|         /// [StartError.AlreadyStarted] is occurs when a request to start work processing happens on | ||||
|         /// an [Implementation] that is already processing work. | ||||
|         /// | ||||
|         const StartError = error { | ||||
|             OutOfThreads, | ||||
|             AlreadyStarted, | ||||
|         }; | ||||
| 
 | ||||
|         /// | ||||
|         /// Casts `app_context` to a [Implementation] reference. | ||||
|         /// | ||||
|         /// *Note* that if `app_context` does not have the same alignment as [Implementation], safety- | ||||
|         /// checked undefined behavior will occur. | ||||
|         /// | ||||
|         fn cast(app_context: *AppContext) *Implementation { | ||||
|             return @ptrCast(*Implementation, @alignCast(@alignOf(Implementation), app_context)); | ||||
|         } | ||||
| 
 | ||||
|         /// | ||||
|         /// Deinitializes the `implementation`, requesting any running asynchronous workload | ||||
|         /// processes quit and waits for them to do so before freeing any resources. | ||||
|         /// | ||||
|         fn deinit(implementation: *Implementation) void { | ||||
|             var message = Message{.kind = .quit}; | ||||
| 
 | ||||
|             implementation.enqueue(&message); | ||||
| 
 | ||||
|             { | ||||
|                 var status = @as(c_int, 0); | ||||
| 
 | ||||
|                 // SDL2 defines waiting on a null thread reference as a no-op. See | ||||
|                 // https://wiki.libsdl.org/SDL_WaitThread for more information | ||||
|                 ext.SDL_WaitThread(implementation.message_thread, &status); | ||||
| 
 | ||||
|                 if (status != 0) { | ||||
|                     // TODO: Error check this. | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             implementation.data_file_system.archive.index_cache.deinit(); | ||||
|             ext.SDL_free(implementation.user_path_prefix); | ||||
|             ext.SDL_DestroyMutex(implementation.message_mutex); | ||||
|             ext.SDL_DestroySemaphore(implementation.message_semaphore); | ||||
|         } | ||||
| 
 | ||||
|         /// | ||||
|         /// Enqueues `message` to the message processor of `implementation` to be processed at a | ||||
|         /// later, non-deterministic point in time. | ||||
|         /// | ||||
|         fn enqueue(implementation: *Implementation, message: *Message) void { | ||||
|     fn enqueue(app: *App, message: *Message) void { | ||||
|         { | ||||
|             // TODO: Error check these. | ||||
|                 _ = ext.SDL_LockMutex(implementation.message_mutex); | ||||
|             _ = ext.SDL_LockMutex(app.message_mutex); | ||||
| 
 | ||||
|                 defer _ = ext.SDL_UnlockMutex(implementation.message_mutex); | ||||
|             defer _ = ext.SDL_UnlockMutex(app.message_mutex); | ||||
| 
 | ||||
|                 if (implementation.messages) |messages| { | ||||
|                     messages.next = message; | ||||
|             if (app.message_chain) |message_chain| { | ||||
|                 message_chain.next = message; | ||||
|             } else { | ||||
|                     implementation.messages = message; | ||||
|                 app.message_chain = message; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // TODO: Error check this. | ||||
|             _ = ext.SDL_SemPost(implementation.message_semaphore); | ||||
|         _ = ext.SDL_SemPost(app.message_semaphore); | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|         /// Initializes a new [Implemenation] with `data_archive_file_access` as the data archive to | ||||
|         /// read from and `user_path_prefix` as the native writable user data directory. | ||||
|     /// Asynchronously executes `procedure` with `arguments` as an anonymous struct of its arguments | ||||
|     /// and `app` as its execution context. | ||||
|     /// | ||||
|         /// Returns the created [Implementation] value on success or [InitError] on failure. | ||||
|     /// Once the execution frame resumes, the value returned by executing `procedure` is returned. | ||||
|     /// | ||||
|         fn init(allocator: std.mem.Allocator, | ||||
|             data_archive_file_access: ona.io.FileAccess) InitError!Implementation { | ||||
| 
 | ||||
|             const user_path_prefix = ext.SDL_GetPrefPath("ona", "ona") orelse | ||||
|                 return error.OutOfMemory; | ||||
| 
 | ||||
|             return Implementation{ | ||||
|                 .user_file_system = .{.native = | ||||
|                     user_path_prefix[0 .. std.mem.len(user_path_prefix)]}, | ||||
| 
 | ||||
|                 .message_semaphore = ext.SDL_CreateSemaphore(0) orelse return error.OutOfSemaphores, | ||||
|                 .message_mutex = ext.SDL_CreateMutex() orelse return error.OutOfMutexes, | ||||
|                 .user_path_prefix = user_path_prefix, | ||||
| 
 | ||||
|                 .data_file_system = .{.archive = .{ | ||||
|                     .index_cache = try Archive.IndexCache.init(allocator), | ||||
|                     .file_access = data_archive_file_access, | ||||
|                 }}, | ||||
| 
 | ||||
|                 .message_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 processTasks(userdata: ?*anyopaque) callconv(.C) c_int { | ||||
|             const implementation = Implementation.cast( | ||||
|                 @ptrCast(*AppContext, userdata orelse unreachable)); | ||||
| 
 | ||||
|             while (true) { | ||||
|                 // TODO: Error check these. | ||||
|                 _ = ext.SDL_SemWait(implementation.message_semaphore); | ||||
|                 _ = ext.SDL_LockMutex(implementation.message_mutex); | ||||
| 
 | ||||
|                 defer _ = ext.SDL_UnlockMutex(implementation.message_mutex); | ||||
| 
 | ||||
|                 while (implementation.messages) |messages| { | ||||
|                     switch (messages.kind) { | ||||
|                         .quit => { | ||||
|                             return 0; | ||||
|                         }, | ||||
| 
 | ||||
|                         .task => |task| { | ||||
|                             task.action(task.data); | ||||
| 
 | ||||
|                             resume task.frame; | ||||
|                         }, | ||||
|                     } | ||||
| 
 | ||||
|                     implementation.messages = messages.next; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /// | ||||
|         /// Attempts to start the asynchronous worker thread of `implementation` if it hasn't been | ||||
|         /// already. | ||||
|         /// | ||||
|         /// [StartError] is returned on failure. | ||||
|         /// | ||||
|         fn start(implementation: *Implementation) StartError!void { | ||||
|             if (implementation.message_thread != null) return error.AlreadyStarted; | ||||
| 
 | ||||
|             implementation.message_thread = ext.SDL_CreateThread(processTasks, | ||||
|                 "File System Worker", implementation) orelse { | ||||
| 
 | ||||
|                 ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, | ||||
|                     "Failed to create file-system work processor"); | ||||
| 
 | ||||
|                 return error.OutOfThreads; | ||||
|             }; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     /// | ||||
|     /// Returns a reference to the currently loaded data file-system. | ||||
|     /// | ||||
|     pub fn data(app_context: *AppContext) *FileSystem { | ||||
|         return &Implementation.cast(app_context).data_file_system; | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// | ||||
|     /// | ||||
|     pub fn schedule(app_context: *AppContext, procedure: anytype, | ||||
|     pub fn schedule(app: *App, procedure: anytype, | ||||
|         arguments: anytype) ona.meta.FnReturn(@TypeOf(procedure)) { | ||||
| 
 | ||||
|         const Task = struct { | ||||
| @ -246,7 +65,7 @@ pub const AppContext = opaque { | ||||
|             .arguments = &arguments, | ||||
|         }; | ||||
| 
 | ||||
|         var message = AppContext.Message{ | ||||
|         var message = Message{ | ||||
|             .kind = .{.task = .{ | ||||
|                 .data = &task, | ||||
|                 .action = Task.process, | ||||
| @ -254,14 +73,22 @@ pub const AppContext = opaque { | ||||
|             }}, | ||||
|         }; | ||||
| 
 | ||||
|         suspend Implementation.cast(app_context).enqueue(&message); | ||||
|         suspend app.enqueue(&message); | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// Returns a reference to the currently loaded user file-system. | ||||
|     /// Asynchronously logs `info` with `logger` as the logging method and `app` as the execution | ||||
|     /// context. | ||||
|     /// | ||||
|     pub fn user(app_context: *AppContext) *FileSystem { | ||||
|         return &Implementation.cast(app_context).user_file_system; | ||||
|     pub fn log(app: *App, logger: Logger, info: []const u8) void { | ||||
|         var message = Message{ | ||||
|             .kind = .{.log = .{ | ||||
|                 .logger = logger, | ||||
|                 .info = info, | ||||
|             }}, | ||||
|         }; | ||||
| 
 | ||||
|         app.enqueue(&message); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| @ -274,17 +101,48 @@ pub const FileSystem = union(enum) { | ||||
|     archive: Archive, | ||||
| 
 | ||||
|     /// | ||||
|     /// With files typically being backed by a block device, they can produce a variety of | ||||
|     /// errors - from physical to virtual errors - these are all encapsulated by the API as | ||||
|     /// general [OpenError.FileNotFound] errors. | ||||
|     /// Archive file system information. | ||||
|     /// | ||||
|     const Archive = struct { | ||||
|         file_access: ona.io.FileAccess, | ||||
|         index_cache: IndexCache, | ||||
|         entry_table: [max_open_entries]Entry = std.mem.zeroes([max_open_entries]Entry), | ||||
| 
 | ||||
|         /// | ||||
|         /// Hard limit on the maximum number of entries open at once. | ||||
|         /// | ||||
|         const max_open_entries = 16; | ||||
| 
 | ||||
|         /// | ||||
|         /// Stateful extension of an [oar.Entry]. | ||||
|         /// | ||||
|         const Entry = struct { | ||||
|             owner: ?*ona.io.FileAccess, | ||||
|             cursor: u64, | ||||
|             header: oar.Entry, | ||||
|         }; | ||||
| 
 | ||||
|         /// | ||||
|         /// Table cache for associating [oar.Path] values with offsets to entries in a given file. | ||||
|         /// | ||||
|         const IndexCache = ona.table.Hashed(oar.Path, u64, .{ | ||||
|             .equals = oar.Path.equals, | ||||
|             .hash = oar.Path.hash, | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     /// | ||||
|     /// With files typically being backed by a block device, they can produce a variety of errors - | ||||
|     /// from physical to virtual errors - these are all encapsulated by the API as general | ||||
|     /// [OpenError.FileNotFound] errors. | ||||
|     /// | ||||
|     /// When a given [FileSystem] does not support a specified [OpenMode], | ||||
|     /// [OpenError.ModeUnsupported] is used to inform the consuming code that another [OpenMode] | ||||
|     /// should be tried or, if no mode other is suitable, that the resource is effectively | ||||
|     /// unavailable. | ||||
|     /// | ||||
|     /// If the number of known [FileAccess] handles has been exhausted, [OpenError.OutOfFiles] | ||||
|     /// is used to communicate this. | ||||
|     /// If the number of known [FileAccess] handles has been exhausted, [OpenError.OutOfFiles] is | ||||
|     /// used to communicate this. | ||||
|     /// | ||||
|     pub const OpenError = error { | ||||
|         FileNotFound, | ||||
| @ -380,12 +238,13 @@ pub const FileSystem = union(enum) { | ||||
|                         } | ||||
| 
 | ||||
|                         fn skip(context: *anyopaque, offset: i64) FileAccess.Error!void { | ||||
|                             const math = std.math; | ||||
|                             const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                             if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
|                             if (offset < 0) { | ||||
|                                 const math = std.math; | ||||
| 
 | ||||
|                                 archive_entry.cursor = math.max(0, | ||||
|                                     archive_entry.cursor - math.absCast(offset)); | ||||
|                             } else { | ||||
| @ -447,17 +306,18 @@ pub const FileSystem = union(enum) { | ||||
|             .native => |native| { | ||||
|                 if (native.len == 0) return error.FileNotFound; | ||||
| 
 | ||||
|                 var path_buffer = std.mem.zeroes([4096]u8); | ||||
|                 const mem = std.mem; | ||||
|                 var path_buffer = mem.zeroes([4096]u8); | ||||
|                 const seperator_length = @boolToInt(native[native.len - 1] != oar.Path.seperator); | ||||
| 
 | ||||
|                 if ((native.len + seperator_length + path.length) >= | ||||
|                     path_buffer.len) return error.FileNotFound; | ||||
| 
 | ||||
|                 std.mem.copy(u8, path_buffer[0 ..], native); | ||||
|                 mem.copy(u8, &path_buffer, native); | ||||
| 
 | ||||
|                 if (seperator_length != 0) path_buffer[native.len] = oar.Path.seperator; | ||||
| 
 | ||||
|                 std.mem.copy(u8, path_buffer[native.len .. path_buffer. | ||||
|                 mem.copy(u8, path_buffer[native.len .. path_buffer. | ||||
|                     len], path.buffer[0 .. path.length]); | ||||
| 
 | ||||
|                 const FileAccess = ona.io.FileAccess; | ||||
| @ -565,7 +425,7 @@ pub const FileSystem = union(enum) { | ||||
| /// | ||||
| /// | ||||
| /// | ||||
| pub const GraphicsContext = opaque { | ||||
| pub const Graphics = opaque { | ||||
|     /// | ||||
|     /// | ||||
|     /// | ||||
| @ -587,8 +447,8 @@ pub const GraphicsContext = opaque { | ||||
|     /// | ||||
|     /// | ||||
|     /// | ||||
|     pub fn poll(graphics_context: *GraphicsContext) ?*const Event { | ||||
|         _ = graphics_context; | ||||
|     pub fn poll(graphics: *Graphics) ?*const Event { | ||||
|         _ = graphics; | ||||
| 
 | ||||
|         return null; | ||||
|     } | ||||
| @ -596,41 +456,46 @@ pub const GraphicsContext = opaque { | ||||
|     /// | ||||
|     /// | ||||
|     /// | ||||
|     pub fn present(graphics_context: *GraphicsContext) void { | ||||
|     pub fn present(graphics: *Graphics) void { | ||||
|         // TODO: Implement; | ||||
|         _ = graphics_context; | ||||
|         _ = graphics; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| /// Returns a graphics runner that uses `Errors` as its error set. | ||||
| /// [Logger.info] logs information that isn't necessarily an error but indicates something useful to | ||||
| /// be logged. | ||||
| /// | ||||
| pub fn GraphicsRunner(comptime Errors: type) type { | ||||
|     return fn (*AppContext, *GraphicsContext) callconv(.Async) Errors!void; | ||||
| } | ||||
| 
 | ||||
| /// [Logger.debug] logs information only when the engine is in debug mode. | ||||
| /// | ||||
| /// [Log.info] represents a log message which is purely informative and does not indicate any kind | ||||
| /// of issue. | ||||
| /// [Logger.warning] logs information to indicate a non-critical error has occured. | ||||
| /// | ||||
| /// [Log.debug] represents a log message which is purely for debugging purposes and will only occurs | ||||
| /// in debug builds. | ||||
| /// | ||||
| /// [Log.warning] represents a log message which is a warning about a issue that does not break | ||||
| /// anything important but is not ideal. | ||||
| /// | ||||
| pub const Log = enum(u32) { | ||||
| pub const Logger = enum(u32) { | ||||
|     info = ext.SDL_LOG_PRIORITY_INFO, | ||||
|     debug = ext.SDL_LOG_PRIORITY_DEBUG, | ||||
|     warning = ext.SDL_LOG_PRIORITY_WARN, | ||||
| }; | ||||
| 
 | ||||
|     /// | ||||
|     /// Writes `utf8_message` as the log kind identified by `log`. | ||||
|     /// | ||||
|     pub fn write(log: Log, utf8_message: []const u8) void { | ||||
|         ext.SDL_LogMessage(ext.SDL_LOG_CATEGORY_APPLICATION, | ||||
|             @enumToInt(log), "%.*s", utf8_message.len, utf8_message.ptr); | ||||
|     } | ||||
| /// | ||||
| /// Linked list of asynchronous messages chained together to be processed by the work processor. | ||||
| /// | ||||
| pub const Message = struct { | ||||
|     next: ?*Message = null, | ||||
| 
 | ||||
|     kind: union(enum) { | ||||
|         quit, | ||||
| 
 | ||||
|         log: struct { | ||||
|             logger: Logger, | ||||
|             info: []const u8, | ||||
|         }, | ||||
| 
 | ||||
|         task: struct { | ||||
|             data: *anyopaque, | ||||
|             action: fn (*anyopaque) void, | ||||
|             frame: anyframe, | ||||
|         }, | ||||
|     }, | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| @ -639,13 +504,10 @@ pub const Log = enum(u32) { | ||||
| pub const Path = oar.Path; | ||||
| 
 | ||||
| /// | ||||
| /// [RunError.InitFailure] occurs if a necessary resource fails to be acquired or allocated. | ||||
| /// | ||||
| /// [RunError.AlreadyRunning] occurs if a runner has already been started. | ||||
| /// [RunError.InitFailure] occurs when the runtime fails to initialize. | ||||
| /// | ||||
| pub const RunError = error { | ||||
|     InitFailure, | ||||
|     AlreadyRunning, | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| @ -654,8 +516,76 @@ pub const RunError = error { | ||||
| /// Should an error from `run` occur, an `Error` is returned, otherwise a [RunError] is returned if | ||||
| /// the underlying runtime fails and is logged. | ||||
| /// | ||||
| pub fn runGraphics(comptime Error: anytype, | ||||
|     comptime run: GraphicsRunner(Error)) (RunError || Error)!void { | ||||
| pub fn display(comptime Error: anytype, | ||||
|     comptime run: fn (*App, *Graphics) callconv(.Async) Error!void) (RunError || Error)!void { | ||||
| 
 | ||||
|     var cwd = FileSystem{.native = "./"}; | ||||
|     const user_prefix = ext.SDL_GetPrefPath("ona", "ona") orelse return error.InitFailure; | ||||
| 
 | ||||
|     defer ext.SDL_free(user_prefix); | ||||
| 
 | ||||
|     var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | ||||
| 
 | ||||
|     defer if (gpa.deinit()) | ||||
|         ext.SDL_LogWarn(ext.SDL_LOG_CATEGORY_APPLICATION, "Runtime allocator leaked memory"); | ||||
| 
 | ||||
|     var app = App{ | ||||
|         .user = .{.native = std.mem.sliceTo(user_prefix, 0)}, | ||||
| 
 | ||||
|         .message_semaphore = ext.SDL_CreateSemaphore(0) orelse { | ||||
|             ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to create message semaphore"); | ||||
| 
 | ||||
|             return error.InitFailure; | ||||
|         }, | ||||
| 
 | ||||
|         .message_mutex = ext.SDL_CreateMutex() orelse { | ||||
|             ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to create message mutex"); | ||||
| 
 | ||||
|             return error.InitFailure; | ||||
|         }, | ||||
| 
 | ||||
|         .data = .{.archive = .{ | ||||
|             .index_cache = FileSystem.Archive.IndexCache.init(gpa.allocator()) catch | ||||
|                 return error.InitFailure, | ||||
| 
 | ||||
|             .file_access = cwd.open(try Path.joined(&.{"./data.oar"}), .readonly) catch { | ||||
|                 ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to load ./data.oar"); | ||||
| 
 | ||||
|                 return error.InitFailure; | ||||
|             }, | ||||
|         }}, | ||||
|     }; | ||||
| 
 | ||||
|     defer { | ||||
|         app.data.archive.file_access.close(); | ||||
|         app.data.archive.index_cache.deinit(); | ||||
|         ext.SDL_DestroySemaphore(app.message_semaphore); | ||||
|         ext.SDL_DestroyMutex(app.message_mutex); | ||||
|     } | ||||
| 
 | ||||
|     const message_thread = ext.SDL_CreateThread(processMessages, "Message Processor", &app) orelse { | ||||
|         ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to create message processor"); | ||||
| 
 | ||||
|         return error.InitFailure; | ||||
|     }; | ||||
| 
 | ||||
|     defer { | ||||
|         var message = Message{.kind = .quit}; | ||||
| 
 | ||||
|         app.enqueue(&message); | ||||
| 
 | ||||
|         { | ||||
|             var status = std.mem.zeroes(c_int); | ||||
| 
 | ||||
|             // SDL2 defines waiting on a null thread reference as a no-op. See | ||||
|             // https://wiki.libsdl.org/SDL_WaitThread for more information | ||||
|             ext.SDL_WaitThread(message_thread, &status); | ||||
| 
 | ||||
|             if (status != 0) { | ||||
|                 // TODO: Error check this. | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) { | ||||
|         ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize runtime"); | ||||
| @ -690,42 +620,47 @@ pub fn runGraphics(comptime Error: anytype, | ||||
| 
 | ||||
|     defer ext.SDL_DestroyRenderer(renderer); | ||||
| 
 | ||||
|     var cwd_file_system = FileSystem{.native ="./"}; | ||||
|     var data_access = try cwd_file_system.open(try Path.joined(&.{"./data.oar"}), .readonly); | ||||
| 
 | ||||
|     defer data_access.close(); | ||||
| 
 | ||||
|     var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | ||||
| 
 | ||||
|     defer _ = gpa.deinit(); | ||||
| 
 | ||||
|     var app_context = AppContext.Implementation.init(gpa.allocator(), data_access) catch |err| { | ||||
|         ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, switch (err) { | ||||
|             error.OutOfMemory => "Failed to allocate necessary memory", | ||||
|             error.OutOfMutexes => "Failed to create file-system work lock", | ||||
|             error.OutOfSemaphores => "Failed to create file-system work scheduler", | ||||
|         }); | ||||
| 
 | ||||
|         return error.InitFailure; | ||||
|     }; | ||||
| 
 | ||||
|     defer app_context.deinit(); | ||||
| 
 | ||||
|     app_context.start() catch |err| { | ||||
|         ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, switch (err) { | ||||
|             // Not possible for it to have already been started. | ||||
|             error.AlreadyStarted => unreachable, | ||||
|             error.OutOfThreads => "Failed to start file-system work processor", | ||||
|         }); | ||||
| 
 | ||||
|         return error.InitFailure; | ||||
|     }; | ||||
| 
 | ||||
|     var graphics_context = GraphicsContext.Implementation{ | ||||
|     var graphics = Graphics.Implementation{ | ||||
|         .event = .{ | ||||
| 
 | ||||
|         }, | ||||
|     }; | ||||
| 
 | ||||
|     return run(@ptrCast(*AppContext, &app_context), @ptrCast(*GraphicsContext, &graphics_context)); | ||||
|     return run(@ptrCast(*App, &app), @ptrCast(*Graphics, &graphics)); | ||||
| } | ||||
| 
 | ||||
| /// | ||||
| /// [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. | ||||
| /// | ||||
| pub fn processMessages(userdata: ?*anyopaque) callconv(.C) c_int { | ||||
|     const app = @ptrCast(*App, @alignCast(@alignOf(App), userdata orelse unreachable)); | ||||
| 
 | ||||
|     while (true) { | ||||
|         // TODO: Error check these. | ||||
|         _ = ext.SDL_SemWait(app.message_semaphore); | ||||
|         _ = ext.SDL_LockMutex(app.message_mutex); | ||||
| 
 | ||||
|         defer _ = ext.SDL_UnlockMutex(app.message_mutex); | ||||
| 
 | ||||
|         while (app.message_chain) |message| { | ||||
|             switch (message.kind) { | ||||
|                 .quit => return 0, | ||||
| 
 | ||||
|                 .log => |log| ext.SDL_LogMessage(ext.SDL_LOG_CATEGORY_APPLICATION, | ||||
|                     @enumToInt(log.logger), "%.*s", log.info.len, log.info.ptr), | ||||
| 
 | ||||
|                 .task => |task| { | ||||
|                     task.action(task.data); | ||||
| 
 | ||||
|                     resume task.frame; | ||||
|                 }, | ||||
|             } | ||||
| 
 | ||||
|             app.message_chain = message.next; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,29 +0,0 @@ | ||||
| const oar = @import("oar"); | ||||
| const ona = @import("ona"); | ||||
| const std = @import("std"); | ||||
| 
 | ||||
| file_access: ona.io.FileAccess, | ||||
| index_cache: IndexCache, | ||||
| entry_table: [max_open_entries]Entry = std.mem.zeroes([max_open_entries]Entry), | ||||
| 
 | ||||
| /// | ||||
| /// Hard limit on the maximum number of entries open at once. | ||||
| /// | ||||
| const max_open_entries = 16; | ||||
| 
 | ||||
| /// | ||||
| /// Stateful extension of an [oar.Entry]. | ||||
| /// | ||||
| pub const Entry = struct { | ||||
|     owner: ?*ona.io.FileAccess, | ||||
|     cursor: u64, | ||||
|     header: oar.Entry, | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| /// Table cache for associating [oar.Path] values with offsets to entries in a given file. | ||||
| /// | ||||
| pub const IndexCache = ona.table.Hashed(oar.Path, u64, .{ | ||||
|     .equals = oar.Path.equals, | ||||
|     .hash = oar.Path.hash, | ||||
| }); | ||||
| @ -30,7 +30,7 @@ pub const Entry = extern struct { | ||||
|         const origin = try file_access.queryCursor(); | ||||
| 
 | ||||
|         if (((try file_access.read(std.mem.asBytes(&entry))) != @sizeOf(Entry)) and | ||||
|             ona.io.equalsBytes(entry.signature[0 ..], signature_magic[0 ..])) { | ||||
|             ona.io.equalsBytes(&entry.signature, &signature_magic)) { | ||||
| 
 | ||||
|             try file_access.seek(origin); | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user