Application Context Implementation #4
| @ -99,7 +99,7 @@ pub const AppContext = opaque { | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             implementation.data_file_system.archive.instance.deint(); | ||||
|             implementation.data_file_system.archive.index_cache.deinit(); | ||||
|             ext.SDL_free(implementation.user_path_prefix); | ||||
|             ext.SDL_DestroyMutex(implementation.message_mutex); | ||||
|             ext.SDL_DestroySemaphore(implementation.message_semaphore); | ||||
| @ -148,7 +148,8 @@ pub const AppContext = opaque { | ||||
|                 .user_path_prefix = user_path_prefix, | ||||
| 
 | ||||
|                 .data_file_system = .{.archive = .{ | ||||
|                     .instance = try oar.Archive.init(allocator, data_archive_file_access), | ||||
|                     .index_cache = try FileSystem.ArchiveIndexCache.init(allocator), | ||||
|                     .file_access = data_archive_file_access, | ||||
|                 }}, | ||||
| 
 | ||||
|                 .message_thread = null, | ||||
| @ -270,12 +271,32 @@ pub const FileSystem = union(enum) { | ||||
|     native: []const u8, | ||||
| 
 | ||||
|     archive: struct { | ||||
|         instance: oar.Archive, | ||||
|         entry_table: [max_open_entries]oar.Entry = std.mem.zeroes([max_open_entries]oar.Entry), | ||||
|         file_access: ona.io.FileAccess, | ||||
|         index_cache: ArchiveIndexCache, | ||||
| 
 | ||||
|         pub const max_open_entries = 16; | ||||
|         entry_table: [max_open_entries]ArchiveEntry = | ||||
|             std.mem.zeroes([max_open_entries]ArchiveEntry), | ||||
| 
 | ||||
|         const max_open_entries = 16; | ||||
|     }, | ||||
| 
 | ||||
|     /// | ||||
|     /// | ||||
|     /// | ||||
|     const ArchiveEntry = struct { | ||||
|         owner: ?*ona.io.FileAccess, | ||||
|         cursor: u64, | ||||
|         header: oar.Entry, | ||||
|     }; | ||||
| 
 | ||||
|     /// | ||||
|     /// | ||||
|     /// | ||||
|     const ArchiveIndexCache = ona.table.Hashed(oar.Path, u64, .{ | ||||
|         .equals = oar.Path.equals, | ||||
|         .hash = oar.Path.hash, | ||||
|     }); | ||||
| 
 | ||||
|     /// | ||||
|     /// Platform-agnostic mechanism for referencing files and directories on a [FileSystem]. | ||||
|     /// | ||||
| @ -358,17 +379,17 @@ pub const FileSystem = union(enum) { | ||||
| 
 | ||||
|                     for (archive.entry_table) |*entry| if (entry.owner == null) { | ||||
|                         const Implementation = struct { | ||||
|                             fn close(context: *anyopaque) void { | ||||
|                                 entryCast(context).owner = null; | ||||
|                             fn archiveEntryCast(context: *anyopaque) *ArchiveEntry { | ||||
|                                 return @ptrCast(*ArchiveEntry, @alignCast( | ||||
|                                     @alignOf(ArchiveEntry), context)); | ||||
|                             } | ||||
| 
 | ||||
|                             fn entryCast(context: *anyopaque) *oar.Entry { | ||||
|                                 return @ptrCast(*oar.Entry, @alignCast( | ||||
|                                     @alignOf(oar.Entry), context)); | ||||
|                             fn close(context: *anyopaque) void { | ||||
|                                 archiveEntryCast(context).owner = null; | ||||
|                             } | ||||
| 
 | ||||
|                             fn queryCursor(context: *anyopaque) FileAccess.Error!u64 { | ||||
|                                 const archive_entry = entryCast(context); | ||||
|                                 const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                                 if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
| @ -376,7 +397,7 @@ pub const FileSystem = union(enum) { | ||||
|                             } | ||||
| 
 | ||||
|                             fn queryLength(context: *anyopaque) FileAccess.Error!u64 { | ||||
|                                 const archive_entry = entryCast(context); | ||||
|                                 const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                                 if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
| @ -384,7 +405,7 @@ pub const FileSystem = union(enum) { | ||||
|                             } | ||||
| 
 | ||||
|                             fn read(context: *anyopaque, buffer: []u8) FileAccess.Error!usize { | ||||
|                                 const archive_entry = entryCast(context); | ||||
|                                 const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                                 const file_access = archive_entry.owner orelse | ||||
|                                     return error.FileInaccessible; | ||||
| @ -399,7 +420,7 @@ pub const FileSystem = union(enum) { | ||||
|                             } | ||||
| 
 | ||||
|                             fn seek(context: *anyopaque, cursor: usize) FileAccess.Error!void { | ||||
|                                 const archive_entry = entryCast(context); | ||||
|                                 const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                                 if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
| @ -407,7 +428,7 @@ pub const FileSystem = union(enum) { | ||||
|                             } | ||||
| 
 | ||||
|                             fn seekToEnd(context: *anyopaque) FileAccess.Error!void { | ||||
|                                 const archive_entry = entryCast(context); | ||||
|                                 const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                                 if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
| @ -416,7 +437,7 @@ pub const FileSystem = union(enum) { | ||||
| 
 | ||||
|                             fn skip(context: *anyopaque, offset: i64) FileAccess.Error!void { | ||||
|                                 const math = std.math; | ||||
|                                 const archive_entry = entryCast(context); | ||||
|                                 const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                                 if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
| @ -429,10 +450,32 @@ pub const FileSystem = union(enum) { | ||||
|                             } | ||||
|                         }; | ||||
| 
 | ||||
|                         entry.* = archive.instance.find(path.path) catch |err| return switch (err) { | ||||
|                             error.FileInaccessible => error.FileNotFound, | ||||
|                             error.EntryNotFound => error.FileNotFound, | ||||
|                         if (archive.index_cache.lookup(path.path)) |index| { | ||||
|                             archive.file_access.seek(index) catch return error.FileNotFound; | ||||
| 
 | ||||
|                             entry.* = .{ | ||||
|                                 .owner = &archive.file_access, | ||||
|                                 .cursor = 0, | ||||
| 
 | ||||
|                                 .header = (oar.Entry.next(archive.file_access) catch return error.FileNotFound) orelse { | ||||
|                                     // Remove cannot fail if lookup succeeded. | ||||
|                                     std.debug.assert(archive.index_cache.remove(path.path) != null); | ||||
| 
 | ||||
|                                     return error.FileNotFound; | ||||
|                                 }, | ||||
|                             }; | ||||
|                         } else { | ||||
|                             while (oar.Entry.next(archive.file_access) catch return error.FileNotFound) |entry_header| { | ||||
|                                 if (entry.header.path.equals(path.path)) | ||||
|                                     entry.* = .{ | ||||
|                                         .owner = &archive.file_access, | ||||
|                                         .cursor = 0, | ||||
|                                         .header = entry_header, | ||||
|                                     }; | ||||
|                             } | ||||
| 
 | ||||
|                             return error.FileNotFound; | ||||
|                         } | ||||
| 
 | ||||
|                         return FileAccess{ | ||||
|                             .context = entry, | ||||
|  | ||||
							
								
								
									
										156
									
								
								src/oar/main.zig
									
									
									
									
									
								
							
							
						
						
									
										156
									
								
								src/oar/main.zig
									
									
									
									
									
								
							| @ -2,140 +2,11 @@ const ona = @import("ona"); | ||||
| const std = @import("std"); | ||||
| 
 | ||||
| /// | ||||
| /// Thin file-wrapper and in-memory layout cache of an OAR archive file. | ||||
| /// An entry block of an Oar archive file. | ||||
| /// | ||||
| pub const Archive = struct { | ||||
|     file_access: ona.io.FileAccess, | ||||
|     index_cache: IndexCache, | ||||
| 
 | ||||
|     /// | ||||
|     /// [OpenError.EntryNotFound] happens when an entry could not be found. | ||||
|     /// | ||||
|     pub const FindError = ona.io.FileAccess.Error || error { | ||||
|         EntryNotFound, | ||||
|     }; | ||||
| 
 | ||||
|     /// | ||||
|     /// See [std.mem.Allocator.Error]. | ||||
|     /// | ||||
|     pub const InitError = std.mem.Allocator.Error; | ||||
| 
 | ||||
|     /// | ||||
|     /// In-memory archive layout cache. | ||||
|     /// | ||||
|     /// As the archive is queried via [find], the cache is lazily assembled with the absolute | ||||
|     /// offsets of each queried file. | ||||
|     /// | ||||
|     const IndexCache = ona.table.Hashed(Path, u64, .{ | ||||
|         .equals = Path.equals, | ||||
|         .hash = Path.hash, | ||||
|     }); | ||||
| 
 | ||||
|     /// | ||||
|     /// Deinitializes the index cache of `archive`, freeing all associated memory. | ||||
|     /// | ||||
|     /// **Note** that this does nothing to the [FileAccess] value that was provided as part of | ||||
|     /// [init]. | ||||
|     /// | ||||
|     pub fn deint(archive: *Archive) void { | ||||
|         archive.index_cache.deinit(); | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// 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: Path) FindError!Entry { | ||||
|         return Entry{ | ||||
|             .header = find_header: { | ||||
|                 var header = Entry.Header{ | ||||
|                     .revision = 0, | ||||
|                     .path = Path.empty, | ||||
|                     .file_size = 0, | ||||
|                     .absolute_offset = 0 | ||||
|                 }; | ||||
| 
 | ||||
|                 const header_size = @sizeOf(Entry.Header); | ||||
| 
 | ||||
|                 if (archive.index_cache.lookup(entry_path)) |cursor| { | ||||
|                     try archive.file_access.seek(cursor); | ||||
| 
 | ||||
|                     if ((try archive.file_access.read(std.mem.asBytes(&header))) != header_size) { | ||||
|                         std.debug.assert(archive.index_cache.remove(entry_path) != null); | ||||
| 
 | ||||
|                         return error.EntryNotFound; | ||||
|                     } | ||||
| 
 | ||||
|                     break: find_header header; | ||||
|                 } else { | ||||
|                     const mem = std.mem; | ||||
| 
 | ||||
|                     // Start from beginning of archive. | ||||
|                     try archive.file_access.seek(0); | ||||
| 
 | ||||
|                     // Read first entry. | ||||
|                     while ((try archive.file_access.read(mem.asBytes(&header))) == header_size) { | ||||
|                         if (entry_path.equals(header.path)) { | ||||
|                             // If caching fails... oh well... | ||||
|                             archive.index_cache.insert(entry_path, header.absolute_offset) catch {}; | ||||
| 
 | ||||
|                             break: find_header header; | ||||
|                         } | ||||
| 
 | ||||
|                         // Move over file data following the entry. | ||||
|                         var to_skip = header.file_size; | ||||
| 
 | ||||
|                         while (to_skip != 0) { | ||||
|                             const math = std.math; | ||||
|                             const skipped = math.min(to_skip, math.maxInt(i64)); | ||||
| 
 | ||||
|                             try archive.file_access.skip(@intCast(i64, skipped)); | ||||
| 
 | ||||
|                             to_skip -= skipped; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 return error.EntryNotFound; | ||||
|             }, | ||||
| 
 | ||||
|             .owner = &archive.file_access, | ||||
|             .cursor = 0, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// Attempts to initialize a new [Archive] with `cache_allocator` as the allocator for managing | ||||
|     /// the in-memory archive layout caches and `archive_file_access` as the currently open archive | ||||
|     /// file. | ||||
|     /// | ||||
|     /// **Note** that `archive_file_access` does nothing to manage the lifetime of the open file. | ||||
|     /// | ||||
|     pub fn init(cache_allocator: std.mem.Allocator, | ||||
|         archive_file_access: ona.io.FileAccess) InitError!Archive { | ||||
| 
 | ||||
|         return Archive{ | ||||
|             .index_cache = try IndexCache.init(cache_allocator), | ||||
|             .file_access = archive_file_access, | ||||
|         }; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| /// Typically, following the block in memory is the file data it holds the meta-information for. | ||||
| /// | ||||
| /// Handles the state of an opened archive entry. | ||||
| /// | ||||
| pub const Entry = struct { | ||||
|     owner: ?*ona.io.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 { | ||||
| pub const Entry = extern struct { | ||||
|     signature: [signature_magic.len]u8 = signature_magic, | ||||
|     revision: u8, | ||||
|     path: Path, | ||||
| @ -144,18 +15,35 @@ pub const Entry = struct { | ||||
|     padding: [232]u8 = std.mem.zeroes([232]u8), | ||||
| 
 | ||||
|     comptime { | ||||
|             const entry_size = @sizeOf(Header); | ||||
|         const entry_size = @sizeOf(Entry); | ||||
| 
 | ||||
|         if (entry_size != 512) | ||||
|             @compileError("Entry is " ++ | ||||
|                 std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes"); | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// | ||||
|     /// | ||||
|     pub fn next(file_access: ona.io.FileAccess) ona.io.FileAccess.Error!?Entry { | ||||
|         var entry = std.mem.zeroes(Entry); | ||||
|         const origin = try file_access.queryCursor(); | ||||
| 
 | ||||
|         if (((try file_access.read(std.mem.asBytes(&entry))) != @sizeOf(Entry)) and | ||||
|             ona.io.equalsBytes(entry.signature[0 ..], signature_magic[0 ..])) { | ||||
| 
 | ||||
|             try file_access.seek(origin); | ||||
| 
 | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return entry; | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// Magic identifier used to validate [Entry] data. | ||||
|     /// | ||||
|     pub const signature_magic = [3]u8{'o', 'a', 'r'}; | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user