Simplify Oar API
This commit is contained in:
parent
3a23f5feca
commit
6769ea92af
|
@ -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_free(implementation.user_path_prefix);
|
||||||
ext.SDL_DestroyMutex(implementation.message_mutex);
|
ext.SDL_DestroyMutex(implementation.message_mutex);
|
||||||
ext.SDL_DestroySemaphore(implementation.message_semaphore);
|
ext.SDL_DestroySemaphore(implementation.message_semaphore);
|
||||||
|
@ -148,7 +148,8 @@ pub const AppContext = opaque {
|
||||||
.user_path_prefix = user_path_prefix,
|
.user_path_prefix = user_path_prefix,
|
||||||
|
|
||||||
.data_file_system = .{.archive = .{
|
.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,
|
.message_thread = null,
|
||||||
|
@ -270,12 +271,32 @@ pub const FileSystem = union(enum) {
|
||||||
native: []const u8,
|
native: []const u8,
|
||||||
|
|
||||||
archive: struct {
|
archive: struct {
|
||||||
instance: oar.Archive,
|
file_access: ona.io.FileAccess,
|
||||||
entry_table: [max_open_entries]oar.Entry = std.mem.zeroes([max_open_entries]oar.Entry),
|
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].
|
/// 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) {
|
for (archive.entry_table) |*entry| if (entry.owner == null) {
|
||||||
const Implementation = struct {
|
const Implementation = struct {
|
||||||
fn close(context: *anyopaque) void {
|
fn archiveEntryCast(context: *anyopaque) *ArchiveEntry {
|
||||||
entryCast(context).owner = null;
|
return @ptrCast(*ArchiveEntry, @alignCast(
|
||||||
|
@alignOf(ArchiveEntry), context));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entryCast(context: *anyopaque) *oar.Entry {
|
fn close(context: *anyopaque) void {
|
||||||
return @ptrCast(*oar.Entry, @alignCast(
|
archiveEntryCast(context).owner = null;
|
||||||
@alignOf(oar.Entry), context));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn queryCursor(context: *anyopaque) FileAccess.Error!u64 {
|
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;
|
if (archive_entry.owner == null) return error.FileInaccessible;
|
||||||
|
|
||||||
|
@ -376,7 +397,7 @@ pub const FileSystem = union(enum) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn queryLength(context: *anyopaque) FileAccess.Error!u64 {
|
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;
|
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 {
|
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
|
const file_access = archive_entry.owner orelse
|
||||||
return error.FileInaccessible;
|
return error.FileInaccessible;
|
||||||
|
@ -399,7 +420,7 @@ pub const FileSystem = union(enum) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn seek(context: *anyopaque, cursor: usize) FileAccess.Error!void {
|
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;
|
if (archive_entry.owner == null) return error.FileInaccessible;
|
||||||
|
|
||||||
|
@ -407,7 +428,7 @@ pub const FileSystem = union(enum) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn seekToEnd(context: *anyopaque) FileAccess.Error!void {
|
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;
|
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 {
|
fn skip(context: *anyopaque, offset: i64) FileAccess.Error!void {
|
||||||
const math = std.math;
|
const math = std.math;
|
||||||
const archive_entry = entryCast(context);
|
const archive_entry = archiveEntryCast(context);
|
||||||
|
|
||||||
if (archive_entry.owner == null) return error.FileInaccessible;
|
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) {
|
if (archive.index_cache.lookup(path.path)) |index| {
|
||||||
error.FileInaccessible => error.FileNotFound,
|
archive.file_access.seek(index) catch return error.FileNotFound;
|
||||||
error.EntryNotFound => 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{
|
return FileAccess{
|
||||||
.context = entry,
|
.context = entry,
|
||||||
|
|
170
src/oar/main.zig
170
src/oar/main.zig
|
@ -2,160 +2,48 @@ const ona = @import("ona");
|
||||||
const std = @import("std");
|
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 {
|
/// Typically, following the block in memory is the file data it holds the meta-information for.
|
||||||
file_access: ona.io.FileAccess,
|
///
|
||||||
index_cache: IndexCache,
|
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),
|
||||||
|
|
||||||
///
|
comptime {
|
||||||
/// [OpenError.EntryNotFound] happens when an entry could not be found.
|
const entry_size = @sizeOf(Entry);
|
||||||
///
|
|
||||||
pub const FindError = ona.io.FileAccess.Error || error {
|
|
||||||
EntryNotFound,
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
if (entry_size != 512)
|
||||||
/// See [std.mem.Allocator.Error].
|
@compileError("Entry is " ++
|
||||||
///
|
std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes");
|
||||||
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 {
|
pub fn next(file_access: ona.io.FileAccess) ona.io.FileAccess.Error!?Entry {
|
||||||
return Entry{
|
var entry = std.mem.zeroes(Entry);
|
||||||
.header = find_header: {
|
const origin = try file_access.queryCursor();
|
||||||
var header = Entry.Header{
|
|
||||||
.revision = 0,
|
|
||||||
.path = Path.empty,
|
|
||||||
.file_size = 0,
|
|
||||||
.absolute_offset = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
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 file_access.seek(origin);
|
||||||
try archive.file_access.seek(cursor);
|
|
||||||
|
|
||||||
if ((try archive.file_access.read(std.mem.asBytes(&header))) != header_size) {
|
return null;
|
||||||
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 entry;
|
||||||
/// Magic identifier used to validate [Entry] data.
|
}
|
||||||
///
|
|
||||||
pub const signature_magic = [3]u8{'o', 'a', 'r'};
|
///
|
||||||
};
|
/// Magic identifier used to validate [Entry] data.
|
||||||
|
///
|
||||||
|
pub const signature_magic = [3]u8{'o', 'a', 'r'};
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|
Loading…
Reference in New Issue