Simplify Oar API
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details

This commit is contained in:
kayomn 2022-10-19 00:05:49 +01:00
parent 3a23f5feca
commit 6769ea92af
2 changed files with 92 additions and 161 deletions

View File

@ -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,

View File

@ -1,141 +1,12 @@
const ona = @import("ona");
const std = @import("std");
///
/// Thin file-wrapper and in-memory layout cache 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,
};
}
};
///
/// 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,19 +15,36 @@ 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'};
};
};
///
/// Unique identifier pointing to an entry within an archive.