Application Context Implementation #4
| @ -15,17 +15,17 @@ fn run(app: *sys.App, graphics: *sys.Graphics) anyerror!void { | ||||
|     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 buffer = try allocator.alloc(u8, file_size); | ||||
| 
 | ||||
|         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); | ||||
|     } | ||||
|  | ||||
							
								
								
									
										160
									
								
								src/ona/oar.zig
									
									
									
									
									
								
							
							
						
						
									
										160
									
								
								src/ona/oar.zig
									
									
									
									
									
								
							| @ -1,138 +1,116 @@ | ||||
| const core = @import("./core.zig"); | ||||
| const core = @import("core"); | ||||
| const sys = @import("./sys.zig"); | ||||
| 
 | ||||
| /// | ||||
| /// Metadata of an Oar archive entry. | ||||
| /// | ||||
| /// | ||||
| pub const Archive = struct { | ||||
|     file_system: *const sys.FileSystem, | ||||
|     archive_path: sys.Path, | ||||
| 
 | ||||
|     /// | ||||
|     /// | ||||
|     /// | ||||
|     const Header = extern struct { | ||||
|         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 { | ||||
| const Block = 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), | ||||
|     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(Entry); | ||||
|         const entry_size = @sizeOf(@This()); | ||||
| 
 | ||||
|             if (entry_size != 512) | ||||
|                 @compileError("Entry is " ++ | ||||
|                     std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes"); | ||||
|         if (entry_size != 512) @compileError("EntryBlock is greater than 512 bytes"); | ||||
|     } | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| /// | ||||
| /// | ||||
| pub const Entry = struct { | ||||
| 
					
					kayomn marked this conversation as resolved
					
						
						
							Outdated
						
					
				 | ||||
|     head: u64, | ||||
|     size: u64, | ||||
| 
 | ||||
|     /// | ||||
|     /// | ||||
|     /// | ||||
|     pub const OpenError = error { | ||||
|         FileNotFound, | ||||
|     pub const FindError = error { | ||||
| 
					
					kayomn marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						kayomn
						commented  Missing documentation comment. Missing documentation comment. | ||||
|         EntryNotFound, | ||||
|         UnsupportedArchive, | ||||
|         ArchiveUnsupported, | ||||
|     }; | ||||
| 
 | ||||
|     /// | ||||
|     /// | ||||
|     /// | ||||
|     pub fn open(archive: Archive, entry_path: Path) OpenError!EntryAccess { | ||||
|         const file_access = try archive.file_system.open(entry_path, .readonly); | ||||
| 
 | ||||
|         errdefer file_access.close(); | ||||
| 
 | ||||
|         var header = std.mem.zeroes(Header); | ||||
|     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. | ||||
|         var header = Header{}; | ||||
|         const header_size = @sizeOf(Header); | ||||
|         const io = core.io; | ||||
| 
 | ||||
|         // Validate header. | ||||
|         if ((try file_access.read(io.bytes(&header)) != header_size) or header | ||||
|             (!core.io.equals(u8, &header.signature, &Header.signature_magic)) or | ||||
|             (header.revision != revision) or | ||||
|             (header.entry_offset <= header_size)) return error.UnsupportedArchive; | ||||
| 
 | ||||
|         // Go to file table. | ||||
|         try file_access.seek(header.entry_offset); | ||||
|         if (((archive_file.read(0, io.bytesOf(&header)) catch | ||||
|             return error.ArchiveUnsupported) != header_size) or | ||||
|                 (!io.equals(u8, &header.signature, &signature_magic)) or | ||||
|                 (header.revision != revision_magic) or | ||||
|                 (header.entry_head <= header_size)) return error.ArchiveUnsupported; | ||||
| 
 | ||||
|         // Read file table. | ||||
|         var entry_buffer = std.mem.zeroes([8]Entry); | ||||
|         var bytes_read = try file_access.read(io.bytes(&entry_buffer)); | ||||
| 
 | ||||
|         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); | ||||
|         var head: usize = 0; | ||||
|         var tail: usize = (header.entry_count - 1); | ||||
|         const block_size = @sizeOf(Block); | ||||
| 
 | ||||
|         while (head <= tail) { | ||||
|             var block = Block{}; | ||||
|             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 x greater, ignore left half | ||||
|                 head = (midpoint + 1); | ||||
|             } else { | ||||
|                 // If x is smaller, ignore right half | ||||
|                 tail = (midpoint - 1); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         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 { | ||||
|         entry.file_access.close(); | ||||
|         return archive_file.read(entry.head + offset, | ||||
|             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'}; | ||||
|  | ||||
							
								
								
									
										465
									
								
								src/ona/sys.zig
									
									
									
									
									
								
							
							
						
						
									
										465
									
								
								src/ona/sys.zig
									
									
									
									
									
								
							| @ -3,7 +3,7 @@ const ext = @cImport({ | ||||
| }); | ||||
| 
 | ||||
| const core = @import("core"); | ||||
| const oar = @import("oar"); | ||||
| const oar = @import("./oar.zig"); | ||||
| 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) | ||||
| /// available to the application in a sandboxed environment. | ||||
| /// | ||||
| pub const FileSystem = union(enum) { | ||||
|     native: []const u8, | ||||
|     archive: Archive, | ||||
|     archive_file: *ReadableFile, | ||||
| 
 | ||||
|     /// | ||||
|     /// 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, | ||||
|     }; | ||||
| 
 | ||||
|     /// | ||||
|     /// [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. | ||||
|     /// | ||||
|     /// 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. | ||||
|     /// | ||||
|     pub fn open(file_system: *const FileSystem, path: Path, | ||||
|         mode: OpenMode) OpenError!core.io.FileAccess { | ||||
| 
 | ||||
|     pub fn openRead(file_system: *const FileSystem, path: Path) OpenError!*ReadableFile { | ||||
|         switch (file_system.*) { | ||||
|             .archive => |*archive| { | ||||
|                 if (mode != .readonly) return error.ModeUnsupported; | ||||
|             .archive_file => |archive_file| { | ||||
|                 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; | ||||
| 
 | ||||
|                 for (archive.entry_table) |*entry| if (entry.owner == null) { | ||||
|                     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); | ||||
|                 _ = entry; | ||||
|                 // TODO: Alloc file context. | ||||
| 
 | ||||
|                 return error.FileNotFound; | ||||
|             }, | ||||
| 
					
					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. | ||||
|                         }; | ||||
|                     } 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; | ||||
|             }, | ||||
| 
 | ||||
|             .native => |native| { | ||||
|                 if (native.len == 0) return error.FileNotFound; | ||||
| 
 | ||||
|                 const mem = std.mem; | ||||
|                 var path_buffer = mem.zeroes([4096]u8); | ||||
|                 const seperator_length = @boolToInt(native[native.len - 1] != oar.Path.seperator); | ||||
|                 var path_buffer = [_]u8{0} ** 4096; | ||||
|                 const seperator_length = @boolToInt(native[native.len - 1] != Path.seperator); | ||||
| 
 | ||||
|                 if ((native.len + seperator_length + path.length) >= | ||||
|                     path_buffer.len) return error.FileNotFound; | ||||
|                 if ((native.len + seperator_length + path.length) >= path_buffer.len) | ||||
|                     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. | ||||
|                     len], path.buffer[0 .. path.length]); | ||||
|                 if (seperator_length != 0) path_buffer[native.len] = Path.seperator; | ||||
| 
 | ||||
|                 const FileAccess = core.io.FileAccess; | ||||
| 
 | ||||
|                 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)); | ||||
|                 io.copy(u8, path_buffer[native.len .. path_buffer.len], | ||||
|                     path.buffer[0 .. path.length]); | ||||
| 
 | ||||
|                 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(); | ||||
| 
 | ||||
|                 return FileAccess{ | ||||
|                     .context = ext.SDL_RWFromFile(&path_buffer, switch (mode) { | ||||
|                         .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, | ||||
|                     }, | ||||
|                 }; | ||||
|                 return @ptrCast(*ReadableFile, ext.SDL_RWFromFile(&path_buffer, "rb") | ||||
|                     orelse return error.FileNotFound); | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
| @ -472,7 +322,102 @@ pub const Message = struct { | ||||
| /// | ||||
| /// 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. | ||||
| @ -504,32 +449,30 @@ pub fn display(comptime Error: anytype, | ||||
|         .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"); | ||||
|             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"); | ||||
|             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 { | ||||
|         .data = .{ | ||||
|             .archive_file = cwd.openRead(try Path.from(&.{"./data.oar"})) 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(); | ||||
|         app.data.archive_file.close(); | ||||
|         ext.SDL_DestroySemaphore(app.message_semaphore); | ||||
|         ext.SDL_DestroyMutex(app.message_mutex); | ||||
|     } | ||||
| @ -546,7 +489,7 @@ pub fn display(comptime Error: anytype, | ||||
|         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 | ||||
|             // https://wiki.libsdl.org/SDL_WaitThread for more information | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	
Missing documentation comment.