diff --git a/src/engine/sys.zig b/src/engine/sys.zig index 4dc6cdf..427ac7c 100644 --- a/src/engine/sys.zig +++ b/src/engine/sys.zig @@ -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, diff --git a/src/oar/main.zig b/src/oar/main.zig index 98db1a2..63ba2e0 100644 --- a/src/oar/main.zig +++ b/src/oar/main.zig @@ -2,160 +2,48 @@ 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, +/// Typically, following the block in memory is the file data it holds the meta-information for. +/// +pub 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), - /// - /// [OpenError.EntryNotFound] happens when an entry could not be found. - /// - pub const FindError = ona.io.FileAccess.Error || error { - EntryNotFound, - }; + comptime { + const entry_size = @sizeOf(Entry); - /// - /// 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(); + if (entry_size != 512) + @compileError("Entry is " ++ + std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes"); } /// - /// 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 - }; + 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(); - const header_size = @sizeOf(Entry.Header); + if (((try file_access.read(std.mem.asBytes(&entry))) != @sizeOf(Entry)) and + ona.io.equalsBytes(entry.signature[0 ..], signature_magic[0 ..])) { - if (archive.index_cache.lookup(entry_path)) |cursor| { - try archive.file_access.seek(cursor); + try file_access.seek(origin); - 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, - }; - } -}; - -/// -/// 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 { - 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(Header); - - if (entry_size != 512) - @compileError("Entry is " ++ - std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes"); + return null; } - /// - /// Magic identifier used to validate [Entry] data. - /// - pub const signature_magic = [3]u8{'o', 'a', 'r'}; - }; + return entry; + } + + /// + /// Magic identifier used to validate [Entry] data. + /// + pub const signature_magic = [3]u8{'o', 'a', 'r'}; }; ///