Application Context Implementation #4
| @ -15,17 +15,17 @@ fn run(app: *sys.App, graphics: *sys.Graphics) anyerror!void { | |||||||
|     defer _ = gpa.deinit(); |     defer _ = gpa.deinit(); | ||||||
| 
 | 
 | ||||||
|     { |     { | ||||||
|         var file_access = try app.data.open(try sys.Path.joined(&.{"ona.lua"}), .readonly); |         var file_reader = try app.data.openRead(try sys.Path.from(&.{"ona.lua"})); | ||||||
| 
 | 
 | ||||||
|         defer file_access.close(); |         defer file_reader.close(); | ||||||
| 
 | 
 | ||||||
|         const file_size = try file_access.queryLength(); |         const file_size = try file_reader.size(); | ||||||
|         const allocator = gpa.allocator(); |         const allocator = gpa.allocator(); | ||||||
|         const buffer = try allocator.alloc(u8, file_size); |         const buffer = try allocator.alloc(u8, file_size); | ||||||
| 
 | 
 | ||||||
|         defer allocator.free(buffer); |         defer allocator.free(buffer); | ||||||
| 
 | 
 | ||||||
|         if ((try file_access.read(buffer)) != file_size) return error.ScriptLoadFailure; |         if ((try file_reader.read(0, buffer)) != file_size) return error.ScriptLoadFailure; | ||||||
| 
 | 
 | ||||||
|         app.log(.debug, buffer); |         app.log(.debug, buffer); | ||||||
|     } |     } | ||||||
|  | |||||||
							
								
								
									
										168
									
								
								src/ona/oar.zig
									
									
									
									
									
								
							
							
						
						
									
										168
									
								
								src/ona/oar.zig
									
									
									
									
									
								
							| @ -1,138 +1,116 @@ | |||||||
| const core = @import("./core.zig"); | const core = @import("core"); | ||||||
| const sys = @import("./sys.zig"); | const sys = @import("./sys.zig"); | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
|  | /// Metadata of an Oar archive entry. | ||||||
|  | /// | ||||||
|  | const Block = extern struct { | ||||||
|  |     signature: [signature_magic.len]u8 = signature_magic, | ||||||
|  |     revision: u8 = 0, | ||||||
|  |     path: sys.Path = sys.Path.empty, | ||||||
|  |     data_size: u64 = 0, | ||||||
|  |     data_head: u64 = 0, | ||||||
|  |     padding: [232]u8 = [_]u8{0} ** 232, | ||||||
|  | 
 | ||||||
|  |     comptime { | ||||||
|  |         const entry_size = @sizeOf(@This()); | ||||||
|  | 
 | ||||||
|  |         if (entry_size != 512) @compileError("EntryBlock is greater than 512 bytes"); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| /// | /// | ||||||
| /// | /// | ||||||
| pub const Archive = struct { | /// | ||||||
|     file_system: *const sys.FileSystem, | pub const Entry = struct { | ||||||
| 
					
					kayomn marked this conversation as resolved
					
						
						
							Outdated
						
					
				 | |||||||
|     archive_path: sys.Path, |     head: u64, | ||||||
|  |     size: u64, | ||||||
| 
 | 
 | ||||||
|     /// |     /// | ||||||
|     /// |     /// | ||||||
|     /// |     /// | ||||||
|     const Header = extern struct { |     pub const FindError = error { | ||||||
| 
					
					kayomn marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						kayomn
						commented  Missing documentation comment. Missing documentation comment. | |||||||
|         signature: [signature_magic.len]u8, |  | ||||||
|         revision: u8, |  | ||||||
|         entry_offset: u64, |  | ||||||
|         padding: [500]u8 = std.mem.zeroes([500]u8), |  | ||||||
| 
 |  | ||||||
|         /// |  | ||||||
|         /// Magic identifier used to validate [Entry] data. |  | ||||||
|         /// |  | ||||||
|         const signature_magic = [3]u8{'o', 'a', 'r'}; |  | ||||||
| 
 |  | ||||||
|         comptime { |  | ||||||
|             const size = @sizeOf(@This()); |  | ||||||
| 
 |  | ||||||
|             if (size != 512) |  | ||||||
|                 @compileError("Header is " ++ |  | ||||||
|                     std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes"); |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     /// |  | ||||||
|     /// An entry block of an Oar archive file. |  | ||||||
|     /// |  | ||||||
|     /// Typically, following the block in memory is the file data it holds the meta-information for. |  | ||||||
|     /// |  | ||||||
|     const Entry = extern struct { |  | ||||||
|         signature: [signature_magic.len]u8 = signature_magic, |  | ||||||
|         revision: u8, |  | ||||||
|         path: Path, |  | ||||||
|         file_size: u64, |  | ||||||
|         absolute_offset: u64, |  | ||||||
|         padding: [232]u8 = std.mem.zeroes([232]u8), |  | ||||||
| 
 |  | ||||||
|         comptime { |  | ||||||
|             const entry_size = @sizeOf(Entry); |  | ||||||
| 
 |  | ||||||
|             if (entry_size != 512) |  | ||||||
|                 @compileError("Entry is " ++ |  | ||||||
|                     std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes"); |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     /// |  | ||||||
|     /// |  | ||||||
|     /// |  | ||||||
|     pub const OpenError = error { |  | ||||||
|         FileNotFound, |  | ||||||
|         EntryNotFound, |         EntryNotFound, | ||||||
|         UnsupportedArchive, |         ArchiveUnsupported, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     /// |     /// | ||||||
|     /// |     /// | ||||||
|     /// |     /// | ||||||
|     pub fn open(archive: Archive, entry_path: Path) OpenError!EntryAccess { |     pub fn find(archive_file: *sys.ReadableFile, entry_path: sys.Path) FindError!Entry { | ||||||
| 
					
					kayomn marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						kayomn
						commented  Missing documentation comment. Missing documentation comment. | |||||||
|         const file_access = try archive.file_system.open(entry_path, .readonly); |         var header = Header{}; | ||||||
| 
 |  | ||||||
|         errdefer file_access.close(); |  | ||||||
| 
 |  | ||||||
|         var header = std.mem.zeroes(Header); |  | ||||||
|         const header_size = @sizeOf(Header); |         const header_size = @sizeOf(Header); | ||||||
|         const io = core.io; |         const io = core.io; | ||||||
| 
 | 
 | ||||||
|         // Validate header. |         if (((archive_file.read(0, io.bytesOf(&header)) catch | ||||||
|         if ((try file_access.read(io.bytes(&header)) != header_size) or header |             return error.ArchiveUnsupported) != header_size) or | ||||||
|             (!core.io.equals(u8, &header.signature, &Header.signature_magic)) or |                 (!io.equals(u8, &header.signature, &signature_magic)) or | ||||||
|             (header.revision != revision) or |                 (header.revision != revision_magic) or | ||||||
|             (header.entry_offset <= header_size)) return error.UnsupportedArchive; |                 (header.entry_head <= header_size)) return error.ArchiveUnsupported; | ||||||
| 
 |  | ||||||
|         // Go to file table. |  | ||||||
|         try file_access.seek(header.entry_offset); |  | ||||||
| 
 | 
 | ||||||
|         // Read file table. |         // Read file table. | ||||||
|         var entry_buffer = std.mem.zeroes([8]Entry); |         var head: usize = 0; | ||||||
|         var bytes_read = try file_access.read(io.bytes(&entry_buffer)); |         var tail: usize = (header.entry_count - 1); | ||||||
| 
 |         const block_size = @sizeOf(Block); | ||||||
|         while (@mod(bytes_read, @sizeOf(Entry)) == 0) { |  | ||||||
|             for (entry_buffer[0 .. (bytes_read / @sizeOf(Entry))]) |entry| { |  | ||||||
|                 if (entry.path.equals(entry_path)) { |  | ||||||
|                     file_access.seek(entry.file_offset); |  | ||||||
| 
 |  | ||||||
|                     return Entry{ |  | ||||||
|                         .file_access = file_access, |  | ||||||
|                     }; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             bytes_read = try file_access.read(io.bytes(&entry_buffer)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         var head = std.mem.zeroes(usize); |  | ||||||
|         var tail = (header.file_count - 1); |  | ||||||
| 
 | 
 | ||||||
|         while (head <= tail) { |         while (head <= tail) { | ||||||
|  |             var block = Block{}; | ||||||
|             const midpoint = (head + (tail - head) / 2); |             const midpoint = (head + (tail - head) / 2); | ||||||
| 
 | 
 | ||||||
|             const comparison = entry_path.compare(arr[m])); |             if ((archive_file.read(header.entry_head + (block_size * midpoint), io.bytesOf(&block)) | ||||||
|  |                 catch return error.ArchiveUnsupported) != block_size) return error.EntryNotFound; | ||||||
| 
 | 
 | ||||||
|             if (comparison == 0) return midpoint; |             const comparison = entry_path.compare(block.path); | ||||||
|  | 
 | ||||||
|  |             if (comparison == 0) return Entry{ | ||||||
|  |                 .head = block.data_head, | ||||||
|  |                 .size = block.data_size, | ||||||
|  |             }; | ||||||
| 
 | 
 | ||||||
|             if (comparison > 0) { |             if (comparison > 0) { | ||||||
|                 // If x greater, ignore left half |  | ||||||
|                 head = (midpoint + 1); |                 head = (midpoint + 1); | ||||||
|             } else { |             } else { | ||||||
|                 // If x is smaller, ignore right half |  | ||||||
|                 tail = (midpoint - 1); |                 tail = (midpoint - 1); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return error.EntryNotFound; |         return error.EntryNotFound; | ||||||
|     } |     } | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| pub const EntryAccess = struct { |     /// | ||||||
|     file_access: FileAccess, |     /// | ||||||
|  |     /// | ||||||
|  |     pub fn read(entry: Entry, archive_file: *sys.ReadableFile, | ||||||
| 
					
					kayomn marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						kayomn
						commented  Missing documentation comment. Missing documentation comment. | |||||||
|  |         offset: u64, buffer: []u8) sys.FileError!usize { | ||||||
| 
 | 
 | ||||||
|     pub fn close(entry: Entry) void { |         return archive_file.read(entry.head + offset, | ||||||
|         entry.file_access.close(); |             buffer[0 .. core.math.min(usize, buffer.len, entry.size)]); | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| /// | /// | ||||||
| /// | /// | ||||||
| const revision = 0; | const Header = extern struct { | ||||||
| 
					
					kayomn marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						kayomn
						commented  Missing documentation comment. Missing documentation comment. | |||||||
|  |     signature: [signature_magic.len]u8 = signature_magic, | ||||||
|  |     revision: u8 = revision_magic, | ||||||
|  |     entry_count: u32 = 0, | ||||||
|  |     entry_head: u64 = 0, | ||||||
|  |     padding: [496]u8 = [_]u8{0} ** 496, | ||||||
|  | 
 | ||||||
|  |     comptime { | ||||||
|  |         const size = @sizeOf(@This()); | ||||||
|  | 
 | ||||||
|  |         if (size != 512) @compileError("Header is greater than 512 bytes"); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /// | ||||||
|  | /// | ||||||
|  | /// | ||||||
|  | const revision_magic = 0; | ||||||
| 
					
					kayomn marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						kayomn
						commented  Missing documentation comment. Missing documentation comment. | |||||||
|  | 
 | ||||||
|  | /// | ||||||
|  | /// Magic identifier used to validate [Entry] data. | ||||||
|  | /// | ||||||
|  | const signature_magic = [3]u8{'o', 'a', 'r'}; | ||||||
|  | |||||||
							
								
								
									
										467
									
								
								src/ona/sys.zig
									
									
									
									
									
								
							
							
						
						
									
										467
									
								
								src/ona/sys.zig
									
									
									
									
									
								
							| @ -3,7 +3,7 @@ const ext = @cImport({ | |||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const core = @import("core"); | const core = @import("core"); | ||||||
| const oar = @import("oar"); | const oar = @import("./oar.zig"); | ||||||
| const std = @import("std"); | const std = @import("std"); | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| @ -92,13 +92,93 @@ pub const App = struct { | |||||||
|     } |     } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /// | ||||||
|  | /// | ||||||
|  | /// | ||||||
|  | pub const ReadableFile = opaque { | ||||||
| 
					
					kayomn marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						kayomn
						commented  Missing documentation comment. Missing documentation comment. | |||||||
|  |     /// | ||||||
|  |     /// | ||||||
|  |     /// | ||||||
|  |     pub fn close(readable_file: *ReadableFile) void { | ||||||
| 
					
					kayomn marked this conversation as resolved
					
				 
				
					
						kayomn
						commented  Missing documentation comment. Missing documentation comment. | |||||||
|  |         if (ext.SDL_RWclose(readable_file.rwOpsCast()) != 0) | ||||||
|  |             return ext.SDL_LogWarn(ext.SDL_LOG_CATEGORY_APPLICATION, | ||||||
|  |                 "Attempt to close an invalid file reference"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// | ||||||
|  |     /// | ||||||
|  |     /// | ||||||
|  |     pub fn read(readable_file: *ReadableFile, offset: u64, buffer: []u8) FileError!u64 { | ||||||
| 
					
					kayomn marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						kayomn
						commented  Missing documentation comment. Missing documentation comment. | |||||||
|  |         const rw_ops = readable_file.rwOpsCast(); | ||||||
|  | 
 | ||||||
|  |         { | ||||||
|  |             ext.SDL_ClearError(); | ||||||
|  | 
 | ||||||
|  |             var sought = core.math.min(u64, offset, core.math.maxInt(i64)); | ||||||
|  | 
 | ||||||
|  |             if (ext.SDL_RWseek(rw_ops, @intCast(i64, sought), ext.RW_SEEK_SET) < 0) | ||||||
|  |                 return error.FileInaccessible; | ||||||
|  | 
 | ||||||
|  |             var to_seek = offset - sought; | ||||||
|  | 
 | ||||||
|  |             while (to_seek != 0) { | ||||||
|  |                 sought = core.math.min(u64, to_seek, core.math.maxInt(i64)); | ||||||
|  | 
 | ||||||
|  |                 ext.SDL_ClearError(); | ||||||
|  | 
 | ||||||
|  |                 if (ext.SDL_RWseek(rw_ops, @intCast(i64, sought), ext.RW_SEEK_CUR) < 0) | ||||||
|  |                     return error.FileInaccessible; | ||||||
|  | 
 | ||||||
|  |                 to_seek -= sought; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         ext.SDL_ClearError(); | ||||||
|  | 
 | ||||||
|  |         const buffer_read = ext.SDL_RWread(rw_ops, buffer.ptr, @sizeOf(u8), buffer.len); | ||||||
|  | 
 | ||||||
|  |         if ((buffer_read == 0) and (ext.SDL_GetError() != null)) | ||||||
|  |             return error.FileInaccessible; | ||||||
|  | 
 | ||||||
|  |         return buffer_read; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// | ||||||
|  |     /// | ||||||
|  |     /// | ||||||
|  |     pub fn rwOpsCast(readable_file: *ReadableFile) *ext.SDL_RWops { | ||||||
|  |         return @ptrCast(*ext.SDL_RWops, @alignCast(@alignOf(ext.SDL_RWops), readable_file)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| 
					
					kayomn marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						kayomn
						commented  Missing documentation comment. Missing documentation comment. 
				
					
						kayomn
						commented  Also, should this be public? Also, should this be public? | |||||||
|  |     /// | ||||||
|  |     /// | ||||||
|  |     /// | ||||||
|  |     pub fn size(readable_file: *ReadableFile) FileError!u64 { | ||||||
|  |         ext.SDL_ClearError(); | ||||||
|  | 
 | ||||||
|  |         const byte_size = ext.SDL_RWsize(readable_file.rwOpsCast()); | ||||||
| 
					
					kayomn marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						kayomn
						commented  Missing documentation comment. Missing documentation comment. | |||||||
|  | 
 | ||||||
|  |         if (byte_size < 0) return error.FileInaccessible; | ||||||
|  | 
 | ||||||
|  |         return @intCast(u64, byte_size); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /// | ||||||
|  | /// [Error.FileInaccessible] is a generic catch-all for a [FileAccess] reference no longer pointing | ||||||
|  | /// to a file or the file becomming invalid for whatever reason. | ||||||
|  | /// | ||||||
|  | pub const FileError = error { | ||||||
|  |     FileInaccessible, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| /// | /// | ||||||
| /// Platform-agnostic mechanism for working with an abstraction of the underlying file-system(s) | /// Platform-agnostic mechanism for working with an abstraction of the underlying file-system(s) | ||||||
| /// available to the application in a sandboxed environment. | /// available to the application in a sandboxed environment. | ||||||
| /// | /// | ||||||
| pub const FileSystem = union(enum) { | pub const FileSystem = union(enum) { | ||||||
|     native: []const u8, |     native: []const u8, | ||||||
|     archive: Archive, |     archive_file: *ReadableFile, | ||||||
| 
 | 
 | ||||||
|     /// |     /// | ||||||
|     /// With files typically being backed by a block device, they can produce a variety of errors - |     /// With files typically being backed by a block device, they can produce a variety of errors - | ||||||
| @ -119,275 +199,45 @@ pub const FileSystem = union(enum) { | |||||||
|         OutOfFiles, |         OutOfFiles, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     /// |  | ||||||
|     /// [OpenMode.readonly] indicates that an existing file is opened in a read-only state, |  | ||||||
|     /// disallowing write access. |  | ||||||
|     /// |  | ||||||
|     /// [OpenMode.overwrite] indicates that an empty file has been created or an existing file has |  | ||||||
|     /// been completely overwritten into. |  | ||||||
|     /// |  | ||||||
|     /// [OpenMode.append] indicates that an existing file that has been opened for reading from and |  | ||||||
|     /// writing to on the end of existing data. |  | ||||||
|     /// |  | ||||||
|     pub const OpenMode = enum { |  | ||||||
|         readonly, |  | ||||||
|         overwrite, |  | ||||||
|         append, |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     /// |     /// | ||||||
|     /// Attempts to open the file identified by `path` with `mode` as the mode for opening the file. |     /// Attempts to open the file identified by `path` with `mode` as the mode for opening the file. | ||||||
|     /// |     /// | ||||||
|     /// Returns a [FileAccess] reference that provides access to the file referenced by `path`or a |     /// Returns a [ReadableFile] reference that provides access to the file referenced by `path`or a | ||||||
|     /// [OpenError] if it failed. |     /// [OpenError] if it failed. | ||||||
|     /// |     /// | ||||||
|     pub fn open(file_system: *const FileSystem, path: Path, |     pub fn openRead(file_system: *const FileSystem, path: Path) OpenError!*ReadableFile { | ||||||
|         mode: OpenMode) OpenError!core.io.FileAccess { |  | ||||||
| 
 |  | ||||||
|         switch (file_system.*) { |         switch (file_system.*) { | ||||||
|             .archive => |*archive| { |             .archive_file => |archive_file| { | ||||||
|                 if (mode != .readonly) return error.ModeUnsupported; |                 const entry = oar.Entry.find(archive_file, path) catch return error.FileNotFound; | ||||||
| 
					
					kayomn marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						kayomn
						commented  Out of date documentation comment. Out of date documentation comment. | |||||||
| 
 | 
 | ||||||
|                 const FileAccess = core.io.FileAccess; |                 _ = entry; | ||||||
|  |                 // TODO: Alloc file context. | ||||||
| 
 | 
 | ||||||
|                 for (archive.entry_table) |*entry| if (entry.owner == null) { |                 return error.FileNotFound; | ||||||
|                     const Implementation = struct { |  | ||||||
|                         fn archiveEntryCast(context: *anyopaque) *Archive.Entry { |  | ||||||
|                             return @ptrCast(*Archive.Entry, @alignCast( |  | ||||||
|                                 @alignOf(Archive.Entry), context)); |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         fn close(context: *anyopaque) void { |  | ||||||
|                             archiveEntryCast(context).owner = null; |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         fn queryCursor(context: *anyopaque) FileAccess.Error!u64 { |  | ||||||
|                             const archive_entry = archiveEntryCast(context); |  | ||||||
| 
 |  | ||||||
|                             if (archive_entry.owner == null) return error.FileInaccessible; |  | ||||||
| 
 |  | ||||||
|                             return archive_entry.cursor; |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         fn queryLength(context: *anyopaque) FileAccess.Error!u64 { |  | ||||||
|                             const archive_entry = archiveEntryCast(context); |  | ||||||
| 
 |  | ||||||
|                             if (archive_entry.owner == null) return error.FileInaccessible; |  | ||||||
| 
 |  | ||||||
|                             return archive_entry.header.file_size; |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         fn read(context: *anyopaque, buffer: []u8) FileAccess.Error!usize { |  | ||||||
|                             const archive_entry = archiveEntryCast(context); |  | ||||||
| 
 |  | ||||||
|                             const file_access = archive_entry.owner orelse |  | ||||||
|                                 return error.FileInaccessible; |  | ||||||
| 
 |  | ||||||
|                             if (archive_entry.cursor >= archive_entry.header.file_size) |  | ||||||
|                                 return error.FileInaccessible; |  | ||||||
| 
 |  | ||||||
|                             try file_access.seek(archive_entry.header.absolute_offset); |  | ||||||
| 
 |  | ||||||
|                             return file_access.read(buffer[0 .. std.math.min( |  | ||||||
|                                 buffer.len, archive_entry.header.file_size)]); |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         fn seek(context: *anyopaque, cursor: usize) FileAccess.Error!void { |  | ||||||
|                             const archive_entry = archiveEntryCast(context); |  | ||||||
| 
 |  | ||||||
|                             if (archive_entry.owner == null) return error.FileInaccessible; |  | ||||||
| 
 |  | ||||||
|                             archive_entry.cursor = cursor; |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         fn seekToEnd(context: *anyopaque) FileAccess.Error!void { |  | ||||||
|                             const archive_entry = archiveEntryCast(context); |  | ||||||
| 
 |  | ||||||
|                             if (archive_entry.owner == null) return error.FileInaccessible; |  | ||||||
| 
 |  | ||||||
|                             archive_entry.cursor = archive_entry.header.file_size; |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         fn skip(context: *anyopaque, offset: i64) FileAccess.Error!void { |  | ||||||
|                             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 { |  | ||||||
|                                 archive_entry.cursor += @intCast(u64, offset); |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     }; |  | ||||||
| 
 |  | ||||||
|                     const Header = oar.Entry; |  | ||||||
| 
 |  | ||||||
|                     if (archive.index_cache.lookup(path)) |index| { |  | ||||||
|                         archive.file_access.seek(index) catch return error.FileNotFound; |  | ||||||
| 
 |  | ||||||
|                         entry.* = .{ |  | ||||||
|                             .owner = &archive.file_access, |  | ||||||
|                             .cursor = 0, |  | ||||||
| 
 |  | ||||||
|                             .header = (Header.next(archive.file_access) catch |  | ||||||
|                                 return error.FileNotFound) orelse { |  | ||||||
| 
 |  | ||||||
|                                 // Remove cannot fail if lookup succeeded. |  | ||||||
|                                 std.debug.assert(archive.index_cache.remove(path) != null); |  | ||||||
| 
 |  | ||||||
|                                 return error.FileNotFound; |  | ||||||
|                             }, |  | ||||||
|                         }; |  | ||||||
|                     } else { |  | ||||||
|                         while (Header.next(archive.file_access) catch |  | ||||||
|                             return error.FileNotFound) |entry_header| { |  | ||||||
| 
 |  | ||||||
|                             if (entry.header.path.equals(path)) entry.* = .{ |  | ||||||
|                                 .owner = &archive.file_access, |  | ||||||
|                                 .cursor = 0, |  | ||||||
|                                 .header = entry_header, |  | ||||||
|                             }; |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         return error.FileNotFound; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     return FileAccess{ |  | ||||||
|                         .context = entry, |  | ||||||
| 
 |  | ||||||
|                         .implementation = &.{ |  | ||||||
|                             .close = Implementation.close, |  | ||||||
|                             .queryCursor = Implementation.queryCursor, |  | ||||||
|                             .queryLength = Implementation.queryLength, |  | ||||||
|                             .read = Implementation.read, |  | ||||||
|                             .seek = Implementation.seek, |  | ||||||
|                             .seekToEnd = Implementation.seekToEnd, |  | ||||||
|                             .skip = Implementation.skip, |  | ||||||
|                         }, |  | ||||||
|                     }; |  | ||||||
|                 }; |  | ||||||
| 
 |  | ||||||
|                 return error.OutOfFiles; |  | ||||||
|             }, |             }, | ||||||
| 
					
					kayomn marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						kayomn
						commented  This should be resolved as part of the PR. This should be resolved as part of the PR. | |||||||
| 
 | 
 | ||||||
|             .native => |native| { |             .native => |native| { | ||||||
|                 if (native.len == 0) return error.FileNotFound; |                 if (native.len == 0) return error.FileNotFound; | ||||||
| 
 | 
 | ||||||
|                 const mem = std.mem; |                 var path_buffer = [_]u8{0} ** 4096; | ||||||
|                 var path_buffer = mem.zeroes([4096]u8); |                 const seperator_length = @boolToInt(native[native.len - 1] != 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) | ||||||
|                     path_buffer.len) return error.FileNotFound; |                     return error.FileNotFound; | ||||||
| 
 | 
 | ||||||
|                 mem.copy(u8, &path_buffer, native); |                 const io = core.io; | ||||||
| 
 | 
 | ||||||
|                 if (seperator_length != 0) path_buffer[native.len] = oar.Path.seperator; |                 io.copy(u8, &path_buffer, native); | ||||||
| 
 | 
 | ||||||
|                 mem.copy(u8, path_buffer[native.len .. path_buffer. |                 if (seperator_length != 0) path_buffer[native.len] = Path.seperator; | ||||||
|                     len], path.buffer[0 .. path.length]); |  | ||||||
| 
 | 
 | ||||||
|                 const FileAccess = core.io.FileAccess; |                 io.copy(u8, path_buffer[native.len .. path_buffer.len], | ||||||
| 
 |                     path.buffer[0 .. path.length]); | ||||||
|                 const Implementation = struct { |  | ||||||
|                     fn rwOpsCast(context: *anyopaque) *ext.SDL_RWops { |  | ||||||
|                         return @ptrCast(*ext.SDL_RWops, @alignCast( |  | ||||||
|                             @alignOf(ext.SDL_RWops), context)); |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     fn close(context: *anyopaque) void { |  | ||||||
|                         ext.SDL_ClearError(); |  | ||||||
| 
 |  | ||||||
|                         if (ext.SDL_RWclose(rwOpsCast(context)) != 0) |  | ||||||
|                             ext.SDL_LogWarn(ext.SDL_LOG_CATEGORY_APPLICATION, ext.SDL_GetError()); |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     fn queryCursor(context: *anyopaque) FileAccess.Error!u64 { |  | ||||||
|                         ext.SDL_ClearError(); |  | ||||||
| 
 |  | ||||||
|                         const sought = ext.SDL_RWtell(rwOpsCast(context)); |  | ||||||
| 
 |  | ||||||
|                         if (sought < 0) return error.FileInaccessible; |  | ||||||
| 
 |  | ||||||
|                         return @intCast(u64, sought); |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     fn queryLength(context: *anyopaque) FileAccess.Error!u64 { |  | ||||||
|                         ext.SDL_ClearError(); |  | ||||||
| 
 |  | ||||||
|                         const sought = ext.SDL_RWsize(rwOpsCast(context)); |  | ||||||
| 
 |  | ||||||
|                         if (sought < 0) return error.FileInaccessible; |  | ||||||
| 
 |  | ||||||
|                         return @intCast(u64, sought); |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     fn read(context: *anyopaque, buffer: []u8) FileAccess.Error!usize { |  | ||||||
|                         ext.SDL_ClearError(); |  | ||||||
| 
 |  | ||||||
|                         const buffer_read = ext.SDL_RWread(rwOpsCast( |  | ||||||
|                             context), buffer.ptr, @sizeOf(u8), buffer.len); |  | ||||||
| 
 |  | ||||||
|                         if ((buffer_read == 0) and (ext.SDL_GetError() != null)) |  | ||||||
|                             return error.FileInaccessible; |  | ||||||
| 
 |  | ||||||
|                         return buffer_read; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     fn seek(context: *anyopaque, cursor: usize) FileAccess.Error!void { |  | ||||||
|                         var to_seek = cursor; |  | ||||||
| 
 |  | ||||||
|                         while (to_seek != 0) { |  | ||||||
|                             const math = std.math; |  | ||||||
|                             const sought = math.min(to_seek, math.maxInt(i64)); |  | ||||||
| 
 |  | ||||||
|                             ext.SDL_ClearError(); |  | ||||||
| 
 |  | ||||||
|                             if (ext.SDL_RWseek(rwOpsCast(context), @intCast(i64, sought), |  | ||||||
|                                 ext.RW_SEEK_CUR) < 0) return error.FileInaccessible; |  | ||||||
| 
 |  | ||||||
|                             to_seek -= sought; |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     fn seekToEnd(context: *anyopaque) FileAccess.Error!void { |  | ||||||
|                         ext.SDL_ClearError(); |  | ||||||
| 
 |  | ||||||
|                         if (ext.SDL_RWseek(rwOpsCast(context), 0, ext.RW_SEEK_END) < 0) |  | ||||||
|                             return error.FileInaccessible; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     fn skip(context: *anyopaque, offset: i64) FileAccess.Error!void { |  | ||||||
|                         ext.SDL_ClearError(); |  | ||||||
| 
 |  | ||||||
|                         if (ext.SDL_RWseek(rwOpsCast(context), offset, ext.RW_SEEK_SET) < 0) |  | ||||||
|                             return error.FileInaccessible; |  | ||||||
|                     } |  | ||||||
|                 }; |  | ||||||
| 
 | 
 | ||||||
|                 ext.SDL_ClearError(); |                 ext.SDL_ClearError(); | ||||||
| 
 | 
 | ||||||
|                 return FileAccess{ |                 return @ptrCast(*ReadableFile, ext.SDL_RWFromFile(&path_buffer, "rb") | ||||||
|                     .context = ext.SDL_RWFromFile(&path_buffer, switch (mode) { |                     orelse return error.FileNotFound); | ||||||
|                         .readonly => "rb", |  | ||||||
|                         .overwrite => "wb", |  | ||||||
|                         .append => "ab", |  | ||||||
|                     }) orelse return error.FileNotFound, |  | ||||||
| 
 |  | ||||||
|                     .implementation = &.{ |  | ||||||
|                         .close = Implementation.close, |  | ||||||
|                         .queryCursor = Implementation.queryCursor, |  | ||||||
|                         .queryLength = Implementation.queryLength, |  | ||||||
|                         .read = Implementation.read, |  | ||||||
|                         .seek = Implementation.seek, |  | ||||||
|                         .seekToEnd = Implementation.seekToEnd, |  | ||||||
|                         .skip = Implementation.skip, |  | ||||||
|                     }, |  | ||||||
|                 }; |  | ||||||
|             }, |             }, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -472,7 +322,102 @@ pub const Message = struct { | |||||||
| /// | /// | ||||||
| /// Path to a file on a [FileSystem]. | /// Path to a file on a [FileSystem]. | ||||||
| /// | /// | ||||||
| pub const Path = oar.Path; | pub const Path = extern struct { | ||||||
|  |     buffer: [255]u8, | ||||||
|  |     length: u8, | ||||||
|  | 
 | ||||||
|  |     /// | ||||||
|  |     /// [Error.TooLong] occurs when creating a path that is greater than the maximum path size **in | ||||||
|  |     /// bytes**. | ||||||
|  |     /// | ||||||
|  |     pub const FromError = error { | ||||||
|  |         TooLong, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     /// | ||||||
|  |     /// An empty [Path] with a length of `0`. | ||||||
|  |     /// | ||||||
|  |     pub const empty = Path{ | ||||||
|  |         .buffer = [_]u8{0} ** 255, | ||||||
|  |         .length = 0, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     /// | ||||||
|  |     /// | ||||||
|  |     /// | ||||||
|  |     pub fn compare(this: Path, that: Path) isize { | ||||||
|  |         return core.io.compareBytes(this.buffer[0 ..this.length], that.buffer[0 .. that.length]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| 
					
					kayomn marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						kayomn
						commented  Missing documentation comment. Missing documentation comment. | |||||||
|  |     /// | ||||||
|  |     /// Returns `true` if `this` is equal to `that`, otherwise `false`. | ||||||
|  |     /// | ||||||
|  |     pub fn equals(this: Path, that: Path) bool { | ||||||
|  |         return core.io.equals(u8, this.buffer[0 ..this.length], that.buffer[0 .. that.length]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// | ||||||
|  |     /// Attempts to create a [Path] with the path components in `sequences` as a fully qualified | ||||||
|  |     /// path from root. | ||||||
|  |     /// | ||||||
|  |     /// A [Path] value is returned containing the fully qualified path from the file-system root or | ||||||
|  |     /// a [FromError] if it could not be created. | ||||||
|  |     /// | ||||||
|  |     pub fn from(sequences: []const []const u8) FromError!Path { | ||||||
|  |         var path = empty; | ||||||
|  | 
 | ||||||
|  |         if (sequences.len != 0) { | ||||||
|  |             const last_sequence_index = sequences.len - 1; | ||||||
|  | 
 | ||||||
|  |             for (sequences) |sequence, index| if (sequence.len != 0) { | ||||||
|  |                 var components = core.io.Spliterator(u8){ | ||||||
|  |                     .source = sequence, | ||||||
|  |                     .delimiter = "/", | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|  |                 while (components.next()) |component| if (component.len != 0) { | ||||||
|  |                     for (component) |byte| { | ||||||
|  |                         if (path.length == max) return error.TooLong; | ||||||
|  |                         path.buffer[path.length] = byte; | ||||||
|  |                         path.length += 1; | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     if (components.hasNext()) { | ||||||
|  |                         if (path.length == max) return error.TooLong; | ||||||
|  |                         path.buffer[path.length] = '/'; | ||||||
|  |                         path.length += 1; | ||||||
|  |                     } | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|  |                 if (index < last_sequence_index) { | ||||||
|  |                     if (path.length == max) return error.TooLong; | ||||||
|  | 
 | ||||||
|  |                     path.buffer[path.length] = '/'; | ||||||
|  |                     path.length += 1; | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return path; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// | ||||||
|  |     /// Returns the hash of the text in `path`. | ||||||
|  |     /// | ||||||
|  |     pub fn hash(path: Path) usize { | ||||||
|  |         return core.io.hashBytes(path.buffer[0 .. path.length]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// | ||||||
|  |     /// Maximum number of **bytes** in a [Path]. | ||||||
|  |     /// | ||||||
|  |     pub const max = 255; | ||||||
|  | 
 | ||||||
|  |     /// | ||||||
|  |     /// Textual separator between components of a [Path]. | ||||||
|  |     /// | ||||||
|  |     pub const seperator = '/'; | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| /// | /// | ||||||
| /// [RunError.InitFailure] occurs when the runtime fails to initialize. | /// [RunError.InitFailure] occurs when the runtime fails to initialize. | ||||||
| @ -504,32 +449,30 @@ pub fn display(comptime Error: anytype, | |||||||
|         .user = .{.native = std.mem.sliceTo(user_prefix, 0)}, |         .user = .{.native = std.mem.sliceTo(user_prefix, 0)}, | ||||||
| 
 | 
 | ||||||
|         .message_semaphore = ext.SDL_CreateSemaphore(0) orelse { |         .message_semaphore = ext.SDL_CreateSemaphore(0) orelse { | ||||||
|             ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to create message semaphore"); |             ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, | ||||||
|  |                 "Failed to create message semaphore"); | ||||||
| 
 | 
 | ||||||
|             return error.InitFailure; |             return error.InitFailure; | ||||||
|         }, |         }, | ||||||
| 
 | 
 | ||||||
|         .message_mutex = ext.SDL_CreateMutex() orelse { |         .message_mutex = ext.SDL_CreateMutex() orelse { | ||||||
|             ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to create message mutex"); |             ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, | ||||||
|  |                 "Failed to create message mutex"); | ||||||
| 
 | 
 | ||||||
|             return error.InitFailure; |             return error.InitFailure; | ||||||
|         }, |         }, | ||||||
| 
 | 
 | ||||||
|         .data = .{.archive = .{ |         .data = .{ | ||||||
|             .index_cache = FileSystem.Archive.IndexCache.init(gpa.allocator()) catch |             .archive_file = cwd.openRead(try Path.from(&.{"./data.oar"})) 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"); |                 ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to load ./data.oar"); | ||||||
| 
 | 
 | ||||||
|                 return error.InitFailure; |                 return error.InitFailure; | ||||||
|             }, |             }, | ||||||
|         }}, |         }, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     defer { |     defer { | ||||||
|         app.data.archive.file_access.close(); |         app.data.archive_file.close(); | ||||||
|         app.data.archive.index_cache.deinit(); |  | ||||||
|         ext.SDL_DestroySemaphore(app.message_semaphore); |         ext.SDL_DestroySemaphore(app.message_semaphore); | ||||||
|         ext.SDL_DestroyMutex(app.message_mutex); |         ext.SDL_DestroyMutex(app.message_mutex); | ||||||
|     } |     } | ||||||
| @ -546,7 +489,7 @@ pub fn display(comptime Error: anytype, | |||||||
|         app.enqueue(&message); |         app.enqueue(&message); | ||||||
| 
 | 
 | ||||||
|         { |         { | ||||||
|             var status = std.mem.zeroes(c_int); |             var status: c_int = 0; | ||||||
| 
 | 
 | ||||||
|             // SDL2 defines waiting on a null thread reference as a no-op. See |             // SDL2 defines waiting on a null thread reference as a no-op. See | ||||||
|             // https://wiki.libsdl.org/SDL_WaitThread for more information |             // https://wiki.libsdl.org/SDL_WaitThread for more information | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	
Missing documentation comment.