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. | /// Starts the the game engine. | ||||||
| /// | /// | ||||||
| pub fn main() anyerror!void { | 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(.{}){}; |     var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | ||||||
| 
 | 
 | ||||||
|     defer _ = gpa.deinit(); |     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(); |         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; |         if ((try file_access.read(buffer)) != file_size) return error.ScriptLoadFailure; | ||||||
| 
 | 
 | ||||||
|         sys.Log.debug.write(buffer); |         app.log(.debug, buffer); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     while (graphics.poll()) |_| { |     while (graphics.poll()) |_| { | ||||||
|  | |||||||
| @ -1,5 +1,3 @@ | |||||||
| const Archive = @import("./sys/Archive.zig"); |  | ||||||
| 
 |  | ||||||
| const ext = @cImport({ | const ext = @cImport({ | ||||||
|     @cInclude("SDL2/SDL.h"); |     @cInclude("SDL2/SDL.h"); | ||||||
| }); | }); | ||||||
| @ -9,222 +7,43 @@ const ona = @import("ona"); | |||||||
| const std = @import("std"); | 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 { | pub const App = struct { | ||||||
|     /// |     message_chain: ?*Message = null, | ||||||
|     /// 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, |  | ||||||
|     message_semaphore: *ext.SDL_sem, |     message_semaphore: *ext.SDL_sem, | ||||||
|     message_mutex: *ext.SDL_mutex, |     message_mutex: *ext.SDL_mutex, | ||||||
|         message_thread: ?*ext.SDL_Thread, |     data: FileSystem, | ||||||
|         messages: ?*Message = null, |     user: FileSystem, | ||||||
| 
 | 
 | ||||||
|     /// |     /// | ||||||
|         /// [StartError.OutOfSemaphores] indicates that the process has no more semaphores available |     /// Enqueues `message` to the message chain in `app`. | ||||||
|         /// to it for use, meaning an [Implementation] may not be initialized at this time. |  | ||||||
|     /// |     /// | ||||||
|         /// [StartError.OutOfMutexes] indicates that the process has no more mutexes available to it |     fn enqueue(app: *App, message: *Message) void { | ||||||
|         /// 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 { |  | ||||||
|         { |         { | ||||||
|             // TODO: Error check these. |             // 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| { |             if (app.message_chain) |message_chain| { | ||||||
|                     messages.next = message; |                 message_chain.next = message; | ||||||
|             } else { |             } else { | ||||||
|                     implementation.messages = message; |                 app.message_chain = message; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // TODO: Error check this. |         // 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 |     /// Asynchronously executes `procedure` with `arguments` as an anonymous struct of its arguments | ||||||
|         /// read from and `user_path_prefix` as the native writable user data directory. |     /// 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, |     pub fn schedule(app: *App, procedure: anytype, | ||||||
|             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, |  | ||||||
|         arguments: anytype) ona.meta.FnReturn(@TypeOf(procedure)) { |         arguments: anytype) ona.meta.FnReturn(@TypeOf(procedure)) { | ||||||
| 
 | 
 | ||||||
|         const Task = struct { |         const Task = struct { | ||||||
| @ -246,7 +65,7 @@ pub const AppContext = opaque { | |||||||
|             .arguments = &arguments, |             .arguments = &arguments, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         var message = AppContext.Message{ |         var message = Message{ | ||||||
|             .kind = .{.task = .{ |             .kind = .{.task = .{ | ||||||
|                 .data = &task, |                 .data = &task, | ||||||
|                 .action = Task.process, |                 .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 { |     pub fn log(app: *App, logger: Logger, info: []const u8) void { | ||||||
|         return &Implementation.cast(app_context).user_file_system; |         var message = Message{ | ||||||
|  |             .kind = .{.log = .{ | ||||||
|  |                 .logger = logger, | ||||||
|  |                 .info = info, | ||||||
|  |             }}, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         app.enqueue(&message); | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -274,17 +101,48 @@ pub const FileSystem = union(enum) { | |||||||
|     archive: Archive, |     archive: Archive, | ||||||
| 
 | 
 | ||||||
|     /// |     /// | ||||||
|     /// With files typically being backed by a block device, they can produce a variety of |     /// Archive file system information. | ||||||
|     /// errors - from physical to virtual errors - these are all encapsulated by the API as |     /// | ||||||
|     /// general [OpenError.FileNotFound] errors. |     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], |     /// When a given [FileSystem] does not support a specified [OpenMode], | ||||||
|     /// [OpenError.ModeUnsupported] is used to inform the consuming code that another [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 |     /// should be tried or, if no mode other is suitable, that the resource is effectively | ||||||
|     /// unavailable. |     /// unavailable. | ||||||
|     /// |     /// | ||||||
|     /// If the number of known [FileAccess] handles has been exhausted, [OpenError.OutOfFiles] |     /// If the number of known [FileAccess] handles has been exhausted, [OpenError.OutOfFiles] is | ||||||
|     /// is used to communicate this. |     /// used to communicate this. | ||||||
|     /// |     /// | ||||||
|     pub const OpenError = error { |     pub const OpenError = error { | ||||||
|         FileNotFound, |         FileNotFound, | ||||||
| @ -380,12 +238,13 @@ pub const FileSystem = union(enum) { | |||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|                         fn skip(context: *anyopaque, offset: i64) FileAccess.Error!void { |                         fn skip(context: *anyopaque, offset: i64) FileAccess.Error!void { | ||||||
|                             const math = std.math; |  | ||||||
|                             const archive_entry = archiveEntryCast(context); |                             const archive_entry = archiveEntryCast(context); | ||||||
| 
 | 
 | ||||||
|                             if (archive_entry.owner == null) return error.FileInaccessible; |                             if (archive_entry.owner == null) return error.FileInaccessible; | ||||||
| 
 | 
 | ||||||
|                             if (offset < 0) { |                             if (offset < 0) { | ||||||
|  |                                 const math = std.math; | ||||||
|  | 
 | ||||||
|                                 archive_entry.cursor = math.max(0, |                                 archive_entry.cursor = math.max(0, | ||||||
|                                     archive_entry.cursor - math.absCast(offset)); |                                     archive_entry.cursor - math.absCast(offset)); | ||||||
|                             } else { |                             } else { | ||||||
| @ -447,17 +306,18 @@ pub const FileSystem = union(enum) { | |||||||
|             .native => |native| { |             .native => |native| { | ||||||
|                 if (native.len == 0) return error.FileNotFound; |                 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); |                 const seperator_length = @boolToInt(native[native.len - 1] != oar.Path.seperator); | ||||||
| 
 | 
 | ||||||
|                 if ((native.len + seperator_length + path.length) >= |                 if ((native.len + seperator_length + path.length) >= | ||||||
|                     path_buffer.len) return error.FileNotFound; |                     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; |                 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]); |                     len], path.buffer[0 .. path.length]); | ||||||
| 
 | 
 | ||||||
|                 const FileAccess = ona.io.FileAccess; |                 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 { |     pub fn poll(graphics: *Graphics) ?*const Event { | ||||||
|         _ = graphics_context; |         _ = graphics; | ||||||
| 
 | 
 | ||||||
|         return null; |         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; |         // 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 { | /// [Logger.debug] logs information only when the engine is in debug mode. | ||||||
|     return fn (*AppContext, *GraphicsContext) callconv(.Async) Errors!void; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /// | /// | ||||||
| /// [Log.info] represents a log message which is purely informative and does not indicate any kind | /// [Logger.warning] logs information to indicate a non-critical error has occured. | ||||||
| /// of issue. |  | ||||||
| /// | /// | ||||||
| /// [Log.debug] represents a log message which is purely for debugging purposes and will only occurs | pub const Logger = enum(u32) { | ||||||
| /// 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) { |  | ||||||
|     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, | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
|     /// | /// | ||||||
|     /// Writes `utf8_message` as the log kind identified by `log`. | /// Linked list of asynchronous messages chained together to be processed by the work processor. | ||||||
|     /// | /// | ||||||
|     pub fn write(log: Log, utf8_message: []const u8) void { | pub const Message = struct { | ||||||
|         ext.SDL_LogMessage(ext.SDL_LOG_CATEGORY_APPLICATION, |     next: ?*Message = null, | ||||||
|             @enumToInt(log), "%.*s", utf8_message.len, utf8_message.ptr); | 
 | ||||||
|     } |     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; | pub const Path = oar.Path; | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| /// [RunError.InitFailure] occurs if a necessary resource fails to be acquired or allocated. | /// [RunError.InitFailure] occurs when the runtime fails to initialize. | ||||||
| /// |  | ||||||
| /// [RunError.AlreadyRunning] occurs if a runner has already been started. |  | ||||||
| /// | /// | ||||||
| pub const RunError = error { | pub const RunError = error { | ||||||
|     InitFailure, |     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 | /// Should an error from `run` occur, an `Error` is returned, otherwise a [RunError] is returned if | ||||||
| /// the underlying runtime fails and is logged. | /// the underlying runtime fails and is logged. | ||||||
| /// | /// | ||||||
| pub fn runGraphics(comptime Error: anytype, | pub fn display(comptime Error: anytype, | ||||||
|     comptime run: GraphicsRunner(Error)) (RunError || Error)!void { |     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) { |     if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) { | ||||||
|         ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize runtime"); |         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); |     defer ext.SDL_DestroyRenderer(renderer); | ||||||
| 
 | 
 | ||||||
|     var cwd_file_system = FileSystem{.native ="./"}; |     var graphics = Graphics.Implementation{ | ||||||
|     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{ |  | ||||||
|         .event = .{ |         .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(); |         const origin = try file_access.queryCursor(); | ||||||
| 
 | 
 | ||||||
|         if (((try file_access.read(std.mem.asBytes(&entry))) != @sizeOf(Entry)) and |         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); |             try file_access.seek(origin); | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user