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_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,
|
||||
|
|
170
src/oar/main.zig
170
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'};
|
||||
};
|
||||
|
||||
///
|
||||
|
|
Loading…
Reference in New Issue