Split archive-specific logic into own module
This commit is contained in:
		
							parent
							
								
									5f4e4cc811
								
							
						
					
					
						commit
						2792f27473
					
				
							
								
								
									
										130
									
								
								src/oar.zig
									
									
									
									
									
								
							
							
						
						
									
										130
									
								
								src/oar.zig
									
									
									
									
									
								
							| @ -1,29 +1,16 @@ | ||||
| const std = @import("std"); | ||||
| const sys = @import("./sys.zig"); | ||||
| const table = @import("./table.zig"); | ||||
| 
 | ||||
| /// | ||||
| /// An entry block of an Oar archive file. | ||||
| /// | ||||
| /// Typically, following this block in memory is the file data it holds the meta-information for. | ||||
| /// | ||||
| pub const Entry = extern struct { | ||||
|     signature: [3]u8 = signature_magic, | ||||
|     revision: u8, | ||||
|     name_buffer: [255]u8 = std.mem.zeroes([255]u8), | ||||
|     name_length: u8 = 0, | ||||
|     file_size: u64, | ||||
|     file_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 Archive = struct { | ||||
|     file_access: sys.FileAccess, | ||||
|     index_cache: IndexCache, | ||||
| 
 | ||||
|     /// | ||||
|     /// [FindError.EntryNotFound] happens when an entry could not be found. | ||||
|     /// [OpenError.EntryNotFound] happens when an entry could not be found. | ||||
|     /// | ||||
|     pub const FindError = sys.FileAccess.Error || error { | ||||
|         EntryNotFound, | ||||
| @ -32,15 +19,110 @@ pub const Entry = extern struct { | ||||
|     /// | ||||
|     /// | ||||
|     /// | ||||
|     pub fn find(file_access: sys.FileAccess, entry_name: []const u8) FindError!Entry { | ||||
|         _ = file_access; | ||||
|         _ = entry_name; | ||||
|     pub const InitError = std.mem.Allocator.Error; | ||||
| 
 | ||||
|         return error.EntryNotFound; | ||||
|     /// | ||||
|     /// | ||||
|     /// | ||||
|     const IndexCache = table.Hashed([]const u8, Entry.Header, table.string_context); | ||||
| 
 | ||||
|     /// | ||||
|     /// Finds an entry matching `entry_path` in `archive`. | ||||
|     /// | ||||
|     /// The found [Entry] value is returned or a [FindError] if it failed to be found. | ||||
|     /// | ||||
|     pub fn find(archive: *Archive, entry_path: []const u8) FindError!Entry { | ||||
|         return Entry{ | ||||
|             .header = find_header: { | ||||
|                 if (archive.index_cache.lookup(entry_path)) |entry_header| | ||||
|                     break: find_header entry_header.*; | ||||
| 
 | ||||
|                 // Start from beginning of archive. | ||||
|                 try archive.file_access.seek(0); | ||||
| 
 | ||||
|                 var entry_header = Entry.Header{ | ||||
|                     .revision = 0, | ||||
|                     .file_size = 0, | ||||
|                     .file_offset = 0 | ||||
|                 }; | ||||
| 
 | ||||
|                 const read_buffer = std.mem.asBytes(&entry_header); | ||||
| 
 | ||||
|                 // Read first entry. | ||||
|                 while ((try archive.file_access.read(read_buffer)) == @sizeOf(Entry.Header)) { | ||||
|                     if (std.mem.eql(u8, entry_path, entry_header. | ||||
|                         name_buffer[0 .. entry_header.name_length])) { | ||||
| 
 | ||||
|                         // If caching fails... oh well... | ||||
|                         archive.index_cache.insert(entry_path, entry_header) catch {}; | ||||
| 
 | ||||
|                         break: find_header entry_header; | ||||
|                     } | ||||
| 
 | ||||
|                     // Move over file data following the entry. | ||||
|                     var to_skip = entry_header.file_size; | ||||
| 
 | ||||
|                     while (to_skip != 0) { | ||||
|                         const skipped = std.math.min(to_skip, std.math.maxInt(i64)); | ||||
| 
 | ||||
|                         try archive.file_access.skip(@intCast(i64, skipped)); | ||||
| 
 | ||||
|                         to_skip -= skipped; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 return error.EntryNotFound; | ||||
|             }, | ||||
| 
 | ||||
|             .owner = &archive.file_access, | ||||
|             .cursor = 0, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// Magic identifier used to validate [Entry] data. | ||||
|     /// | ||||
|     const signature_magic = [3]u8{'o', 'a', 'r'}; | ||||
|     /// | ||||
|     pub fn init(allocator: std.mem.Allocator, archive_file_access: sys.FileAccess) InitError!Archive { | ||||
|         return Archive{ | ||||
|             .index_cache = try IndexCache.init(allocator), | ||||
|             .file_access = archive_file_access, | ||||
|         }; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| /// Handles the state of an opened archive entry. | ||||
| /// | ||||
| pub const Entry = struct { | ||||
|     owner: ?*sys.FileAccess, | ||||
|     cursor: u64, | ||||
|     header: Header, | ||||
| 
 | ||||
|     /// | ||||
|     /// An entry block of an Oar archive file. | ||||
|     /// | ||||
|     /// Typically, following the block in memory is the file data it holds the meta-information for. | ||||
|     /// | ||||
|     pub const Header = extern struct { | ||||
|         signature: [signature_magic.len]u8 = signature_magic, | ||||
|         revision: u8, | ||||
|         name_buffer: [255]u8 = std.mem.zeroes([255]u8), | ||||
|         name_length: u8 = 0, | ||||
|         file_size: u64, | ||||
|         file_offset: u64, | ||||
|         padding: [232]u8 = std.mem.zeroes([232]u8), | ||||
| 
 | ||||
|         comptime { | ||||
|             const entry_size = @sizeOf(Header); | ||||
| 
 | ||||
|             if (entry_size != 512) | ||||
|                 @compileError("Entry is " ++ | ||||
|                     std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes"); | ||||
|         } | ||||
| 
 | ||||
|         /// | ||||
|         /// Magic identifier used to validate [Entry] data. | ||||
|         /// | ||||
|         pub const signature_magic = [3]u8{'o', 'a', 'r'}; | ||||
|     }; | ||||
| }; | ||||
|  | ||||
							
								
								
									
										160
									
								
								src/sys.zig
									
									
									
									
									
								
							
							
						
						
									
										160
									
								
								src/sys.zig
									
									
									
									
									
								
							| @ -8,7 +8,6 @@ const meta = @import("./meta.zig"); | ||||
| const oar = @import("./oar.zig"); | ||||
| const stack = @import("./stack.zig"); | ||||
| const std = @import("std"); | ||||
| const table = @import("./table.zig"); | ||||
| 
 | ||||
| /// | ||||
| /// A thread-safe platform abstraction over multiplexing system I/O processing and event handling. | ||||
| @ -131,28 +130,25 @@ pub const AppContext = opaque { | ||||
|         } | ||||
| 
 | ||||
|         /// | ||||
|         /// Initializes a new [Implemenation] with `data_archive_file_access` as the data archive to | ||||
|         /// read from and `user_path_prefix` as the native writable user data directory. | ||||
|         /// Initializes a new [Implemenation] with `data_access` as the data archive to read from | ||||
|         /// and `user_path_prefix` as the native writable user data directory. | ||||
|         /// | ||||
|         /// Returns the created [Implementation] value on success or [InitError] on failure. | ||||
|         /// | ||||
|         fn init(allocator: std.mem.Allocator, | ||||
|             data_archive_file_access: FileAccess) InitError!Implementation { | ||||
| 
 | ||||
|         fn init(allocator: std.mem.Allocator, data_access: FileAccess) InitError!Implementation { | ||||
|             const user_path_prefix = ext.SDL_GetPrefPath("ona", "ona") orelse | ||||
|                 return error.OutOfMemory; | ||||
| 
 | ||||
|             return Implementation{ | ||||
|                 .user_file_system = .{.native = .{.path_prefix = | ||||
|                     user_path_prefix[0 .. std.mem.len(user_path_prefix)]}}, | ||||
|                 .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 = .{ | ||||
|                     .file_access = data_archive_file_access, | ||||
|                     .index_cache = try FileSystem.ArchiveIndexCache.init(allocator), | ||||
|                     .instance = try oar.Archive.init(allocator, data_access), | ||||
|                 }}, | ||||
| 
 | ||||
|                 .message_thread = null, | ||||
| @ -283,6 +279,7 @@ pub const FileAccess = struct { | ||||
|         read: fn (*anyopaque, []u8) Error!usize, | ||||
|         seek: fn (*anyopaque, u64) Error!void, | ||||
|         seekToEnd: fn (*anyopaque) Error!void, | ||||
|         skip: fn (*anyopaque, i64) Error!void, | ||||
|     }; | ||||
| 
 | ||||
|     /// | ||||
| @ -343,14 +340,22 @@ pub const FileAccess = struct { | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// Attempts to seek `file_access` to the end of the file while using `app_context` as the execution | ||||
|     /// context. | ||||
|     /// Attempts to seek `file_access` to the end of the file. | ||||
|     /// | ||||
|     /// Returns [Error] on failure. | ||||
|     /// | ||||
|     pub fn seekToEnd(file_access: *FileAccess) Error!void { | ||||
|         return file_access.implementation.seekToEnd(file_access.context); | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// Attempts to seek `file_access` by `offset` from the current file position. | ||||
|     /// | ||||
|     /// Returns [Error] on failure; | ||||
|     /// | ||||
|     pub fn skip(file_access: *FileAccess, offset: i64) Error!void { | ||||
|         return file_access.implementation.skip(file_access.context, offset); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| @ -358,31 +363,15 @@ pub const FileAccess = struct { | ||||
| /// available to the application in a sandboxed environment. | ||||
| /// | ||||
| pub const FileSystem = union(enum) { | ||||
|     native: struct { | ||||
|         path_prefix: []const u8, | ||||
|     }, | ||||
|     native: []const u8, | ||||
| 
 | ||||
|     archive: struct { | ||||
|         file_access: FileAccess, | ||||
|         index_cache: ArchiveIndexCache, | ||||
|         instance: oar.Archive, | ||||
|         entry_table: [max_open_entries]oar.Entry = std.mem.zeroes([max_open_entries]oar.Entry), | ||||
| 
 | ||||
|         entry_table: [max_open_entries]ArchiveEntry = | ||||
|             std.mem.zeroes([max_open_entries]ArchiveEntry), | ||||
| 
 | ||||
|         const max_open_entries = 16; | ||||
|         pub const max_open_entries = 16; | ||||
|     }, | ||||
| 
 | ||||
|     /// | ||||
|     /// Handles the state of an opened archive entry. | ||||
|     /// | ||||
|     const ArchiveEntry = struct { | ||||
|         using: ?*FileAccess, | ||||
|         header: oar.Entry, | ||||
|         cursor: u64, | ||||
|     }; | ||||
| 
 | ||||
|     const ArchiveIndexCache = table.Hashed([]const u8, oar.Entry, table.string_context); | ||||
| 
 | ||||
|     /// | ||||
|     /// Platform-agnostic mechanism for referencing files and directories on a [FileSystem]. | ||||
|     /// | ||||
| @ -462,57 +451,37 @@ pub const FileSystem = union(enum) { | ||||
|                 .archive => |*archive| { | ||||
|                     if (mode != .readonly) return error.ModeUnsupported; | ||||
| 
 | ||||
|                     for (archive.entry_table) |*entry| if (entry.using == null) { | ||||
|                         const entry_path = path.buffer[0 .. path.length]; | ||||
| 
 | ||||
|                         entry.* = .{ | ||||
|                             .header = find_header: { | ||||
|                                 if (archive.index_cache.lookup(entry_path)) |header| | ||||
|                                     break: find_header header.*; | ||||
| 
 | ||||
|                                 const header = oar.Entry.find(archive.file_access, | ||||
|                                     entry_path) catch return error.FileNotFound; | ||||
| 
 | ||||
|                                 // If caching fails... oh well... | ||||
|                                 archive.index_cache.insert(entry_path, header) catch {}; | ||||
| 
 | ||||
|                                 break: find_header header; | ||||
|                             }, | ||||
| 
 | ||||
|                             .using = &archive.file_access, | ||||
|                             .cursor = 0, | ||||
|                         }; | ||||
| 
 | ||||
|                     for (archive.entry_table) |*entry| if (entry.owner == null) { | ||||
|                         const Implementation = struct { | ||||
|                             fn archiveEntryCast(context: *anyopaque) *ArchiveEntry { | ||||
|                                 return @ptrCast(*ArchiveEntry, @alignCast( | ||||
|                                     @alignOf(ArchiveEntry), context)); | ||||
|                             fn close(context: *anyopaque) void { | ||||
|                                 entryCast(context).owner = null; | ||||
|                             } | ||||
| 
 | ||||
|                             fn close(context: *anyopaque) void { | ||||
|                                 archiveEntryCast(context).using = null; | ||||
|                             fn entryCast(context: *anyopaque) *oar.Entry { | ||||
|                                 return @ptrCast(*oar.Entry, @alignCast( | ||||
|                                     @alignOf(oar.Entry), context)); | ||||
|                             } | ||||
| 
 | ||||
|                             fn queryCursor(context: *anyopaque) FileAccess.Error!u64 { | ||||
|                                 const archive_entry = archiveEntryCast(context); | ||||
|                                 const archive_entry = entryCast(context); | ||||
| 
 | ||||
|                                 if (archive_entry.using == null) return error.FileInaccessible; | ||||
|                                 if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
|                                 return archive_entry.cursor; | ||||
|                             } | ||||
| 
 | ||||
|                             fn queryLength(context: *anyopaque) FileAccess.Error!u64 { | ||||
|                                 const archive_entry = archiveEntryCast(context); | ||||
|                                 const archive_entry = entryCast(context); | ||||
| 
 | ||||
|                                 if (archive_entry.using == null) return error.FileInaccessible; | ||||
|                                 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 archive_entry = entryCast(context); | ||||
| 
 | ||||
|                                 const file_access = archive_entry.using orelse | ||||
|                                 const file_access = archive_entry.owner orelse | ||||
|                                     return error.FileInaccessible; | ||||
| 
 | ||||
|                                 if (archive_entry.cursor >= archive_entry.header.file_size) | ||||
| @ -525,22 +494,42 @@ pub const FileSystem = union(enum) { | ||||
|                             } | ||||
| 
 | ||||
|                             fn seek(context: *anyopaque, cursor: usize) FileAccess.Error!void { | ||||
|                                 const archive_entry = archiveEntryCast(context); | ||||
|                                 const archive_entry = entryCast(context); | ||||
| 
 | ||||
|                                 if (archive_entry.using == null) return error.FileInaccessible; | ||||
|                                 if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
|                                 archive_entry.cursor = cursor; | ||||
|                             } | ||||
| 
 | ||||
|                             fn seekToEnd(context: *anyopaque) FileAccess.Error!void { | ||||
|                                 const archive_entry = archiveEntryCast(context); | ||||
|                                 const archive_entry = entryCast(context); | ||||
| 
 | ||||
|                                 if (archive_entry.using == null) return error.FileInaccessible; | ||||
|                                 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 math = std.math; | ||||
|                                 const archive_entry = entryCast(context); | ||||
| 
 | ||||
|                                 if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
|                                 if (offset < 0) { | ||||
|                                     archive_entry.cursor = math.max(0, | ||||
|                                         archive_entry.cursor - math.absCast(offset)); | ||||
|                                 } else { | ||||
|                                     archive_entry.cursor += @intCast(u64, offset); | ||||
|                                 } | ||||
|                             } | ||||
|                         }; | ||||
| 
 | ||||
|                         entry.* = archive.instance.find(path.buffer[0 .. path.length]) catch |err| | ||||
|                             return switch (err) { | ||||
|                                 error.FileInaccessible => error.FileNotFound, | ||||
|                                 error.EntryNotFound => error.FileNotFound, | ||||
|                             }; | ||||
| 
 | ||||
|                         return FileAccess{ | ||||
|                             .context = entry, | ||||
| 
 | ||||
| @ -551,6 +540,7 @@ pub const FileSystem = union(enum) { | ||||
|                                 .read = Implementation.read, | ||||
|                                 .seek = Implementation.seek, | ||||
|                                 .seekToEnd = Implementation.seekToEnd, | ||||
|                                 .skip = Implementation.skip, | ||||
|                             }, | ||||
|                         }; | ||||
|                     }; | ||||
| @ -559,22 +549,19 @@ pub const FileSystem = union(enum) { | ||||
|                 }, | ||||
| 
 | ||||
|                 .native => |native| { | ||||
|                     if (native.path_prefix.len == 0) return error.FileNotFound; | ||||
|                     if (native.len == 0) return error.FileNotFound; | ||||
| 
 | ||||
|                     var path_buffer = std.mem.zeroes([4096]u8); | ||||
|                     const seperator_length = @boolToInt(native[native.len - 1] != seperator); | ||||
| 
 | ||||
|                     const seperator_length = @boolToInt(native.path_prefix[ | ||||
|                         native.path_prefix.len - 1] != seperator); | ||||
| 
 | ||||
|                     if ((native.path_prefix.len + seperator_length + path.length) >= | ||||
|                     if ((native.len + seperator_length + path.length) >= | ||||
|                         path_buffer.len) return error.FileNotFound; | ||||
| 
 | ||||
|                     std.mem.copy(u8, path_buffer[0 ..], native.path_prefix); | ||||
|                     std.mem.copy(u8, path_buffer[0 ..], native); | ||||
| 
 | ||||
|                     if (seperator_length != 0) | ||||
|                         path_buffer[native.path_prefix.len] = seperator; | ||||
|                     if (seperator_length != 0) path_buffer[native.len] = seperator; | ||||
| 
 | ||||
|                     std.mem.copy(u8, path_buffer[native.path_prefix.len .. | ||||
|                     std.mem.copy(u8, path_buffer[native.len .. | ||||
|                         path_buffer.len], path.buffer[0 .. path.length]); | ||||
| 
 | ||||
|                     ext.SDL_ClearError(); | ||||
| @ -629,15 +616,14 @@ pub const FileSystem = union(enum) { | ||||
| 
 | ||||
|                             while (to_seek != 0) { | ||||
|                                 const math = std.math; | ||||
|                                 const sought = @intCast(i64, math.min(to_seek, math.maxInt(i64))); | ||||
|                                 const sought = math.min(to_seek, math.maxInt(i64)); | ||||
| 
 | ||||
|                                 ext.SDL_ClearError(); | ||||
| 
 | ||||
|                                 if (ext.SDL_RWseek(rwOpsCast(context), sought, ext.RW_SEEK_CUR) < 0) | ||||
|                                     return error.FileInaccessible; | ||||
|                                 if (ext.SDL_RWseek(rwOpsCast(context), @intCast(i64, sought), | ||||
|                                     ext.RW_SEEK_CUR) < 0) return error.FileInaccessible; | ||||
| 
 | ||||
|                                 // Cannot be less than zero because it is derived from `read`. | ||||
|                                 to_seek -= @intCast(u64, sought); | ||||
|                                 to_seek -= sought; | ||||
|                             } | ||||
|                         } | ||||
| 
 | ||||
| @ -647,6 +633,13 @@ pub const FileSystem = union(enum) { | ||||
|                             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; | ||||
|                         } | ||||
|                     }; | ||||
| 
 | ||||
|                     return FileAccess{ | ||||
| @ -663,6 +656,7 @@ pub const FileSystem = union(enum) { | ||||
|                             .read = Implementation.read, | ||||
|                             .seek = Implementation.seek, | ||||
|                             .seekToEnd = Implementation.seekToEnd, | ||||
|                             .skip = Implementation.skip, | ||||
|                         }, | ||||
|                     }; | ||||
|                 }, | ||||
| @ -855,7 +849,7 @@ pub fn runGraphics(comptime Error: anytype, | ||||
| 
 | ||||
|     defer ext.SDL_DestroyRenderer(renderer); | ||||
| 
 | ||||
|     var cwd_file_system = FileSystem{.native =.{.path_prefix = "./"}}; | ||||
|     var cwd_file_system = FileSystem{.native ="./"}; | ||||
|     var data_access = try (try cwd_file_system.joinedPath(&.{"./data.oar"})).open(.readonly); | ||||
| 
 | ||||
|     defer data_access.close(); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user