Split archive-specific logic into own module
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details

This commit is contained in:
kayomn 2022-10-15 13:14:02 +01:00
parent 5f4e4cc811
commit 2792f27473
2 changed files with 183 additions and 107 deletions

View File

@ -1,29 +1,16 @@
const std = @import("std"); const std = @import("std");
const sys = @import("./sys.zig"); const sys = @import("./sys.zig");
const table = @import("./table.zig");
/// ///
/// An entry block of an Oar archive file.
/// ///
/// Typically, following this block in memory is the file data it holds the meta-information for.
/// ///
pub const Entry = extern struct { pub const Archive = struct {
signature: [3]u8 = signature_magic, file_access: sys.FileAccess,
revision: u8, index_cache: IndexCache,
name_buffer: [255]u8 = std.mem.zeroes([255]u8),
name_length: u8 = 0,
file_size: u64,
file_offset: u64,
padding: [232]u8 = std.mem.zeroes([232]u8),
comptime {
const entry_size = @sizeOf(Entry);
if (entry_size != 512)
@compileError("Entry is " ++ std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes");
}
/// ///
/// [FindError.EntryNotFound] happens when an entry could not be found. /// [OpenError.EntryNotFound] happens when an entry could not be found.
/// ///
pub const FindError = sys.FileAccess.Error || error { pub const FindError = sys.FileAccess.Error || error {
EntryNotFound, EntryNotFound,
@ -32,15 +19,110 @@ pub const Entry = extern struct {
/// ///
/// ///
/// ///
pub fn find(file_access: sys.FileAccess, entry_name: []const u8) FindError!Entry { pub const InitError = std.mem.Allocator.Error;
_ = file_access;
_ = entry_name; ///
///
///
const IndexCache = table.Hashed([]const u8, Entry.Header, table.string_context);
///
/// 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: []const u8) FindError!Entry {
return Entry{
.header = find_header: {
if (archive.index_cache.lookup(entry_path)) |entry_header|
break: find_header entry_header.*;
// Start from beginning of archive.
try archive.file_access.seek(0);
var entry_header = Entry.Header{
.revision = 0,
.file_size = 0,
.file_offset = 0
};
const read_buffer = std.mem.asBytes(&entry_header);
// Read first entry.
while ((try archive.file_access.read(read_buffer)) == @sizeOf(Entry.Header)) {
if (std.mem.eql(u8, entry_path, entry_header.
name_buffer[0 .. entry_header.name_length])) {
// If caching fails... oh well...
archive.index_cache.insert(entry_path, entry_header) catch {};
break: find_header entry_header;
}
// Move over file data following the entry.
var to_skip = entry_header.file_size;
while (to_skip != 0) {
const skipped = std.math.min(to_skip, std.math.maxInt(i64));
try archive.file_access.skip(@intCast(i64, skipped));
to_skip -= skipped;
}
}
return error.EntryNotFound; return error.EntryNotFound;
},
.owner = &archive.file_access,
.cursor = 0,
};
}
///
///
///
pub fn init(allocator: std.mem.Allocator, archive_file_access: sys.FileAccess) InitError!Archive {
return Archive{
.index_cache = try IndexCache.init(allocator),
.file_access = archive_file_access,
};
}
};
///
/// Handles the state of an opened archive entry.
///
pub const Entry = struct {
owner: ?*sys.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,
name_buffer: [255]u8 = std.mem.zeroes([255]u8),
name_length: u8 = 0,
file_size: u64,
file_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");
} }
/// ///
/// Magic identifier used to validate [Entry] data. /// Magic identifier used to validate [Entry] data.
/// ///
const signature_magic = [3]u8{'o', 'a', 'r'}; pub const signature_magic = [3]u8{'o', 'a', 'r'};
};
}; };

View File

@ -8,7 +8,6 @@ const meta = @import("./meta.zig");
const oar = @import("./oar.zig"); const oar = @import("./oar.zig");
const stack = @import("./stack.zig"); const stack = @import("./stack.zig");
const std = @import("std"); const std = @import("std");
const table = @import("./table.zig");
/// ///
/// A thread-safe platform abstraction over multiplexing system I/O processing and event handling. /// A thread-safe platform abstraction over multiplexing system I/O processing and event handling.
@ -131,28 +130,25 @@ pub const AppContext = opaque {
} }
/// ///
/// Initializes a new [Implemenation] with `data_archive_file_access` as the data archive to /// Initializes a new [Implemenation] with `data_access` as the data archive to read from
/// read from and `user_path_prefix` as the native writable user data directory. /// and `user_path_prefix` as the native writable user data directory.
/// ///
/// Returns the created [Implementation] value on success or [InitError] on failure. /// Returns the created [Implementation] value on success or [InitError] on failure.
/// ///
fn init(allocator: std.mem.Allocator, fn init(allocator: std.mem.Allocator, data_access: FileAccess) InitError!Implementation {
data_archive_file_access: FileAccess) InitError!Implementation {
const user_path_prefix = ext.SDL_GetPrefPath("ona", "ona") orelse const user_path_prefix = ext.SDL_GetPrefPath("ona", "ona") orelse
return error.OutOfMemory; return error.OutOfMemory;
return Implementation{ return Implementation{
.user_file_system = .{.native = .{.path_prefix = .user_file_system = .{.native =
user_path_prefix[0 .. std.mem.len(user_path_prefix)]}}, user_path_prefix[0 .. std.mem.len(user_path_prefix)]},
.message_semaphore = ext.SDL_CreateSemaphore(0) orelse return error.OutOfSemaphores, .message_semaphore = ext.SDL_CreateSemaphore(0) orelse return error.OutOfSemaphores,
.message_mutex = ext.SDL_CreateMutex() orelse return error.OutOfMutexes, .message_mutex = ext.SDL_CreateMutex() orelse return error.OutOfMutexes,
.user_path_prefix = user_path_prefix, .user_path_prefix = user_path_prefix,
.data_file_system = .{.archive = .{ .data_file_system = .{.archive = .{
.file_access = data_archive_file_access, .instance = try oar.Archive.init(allocator, data_access),
.index_cache = try FileSystem.ArchiveIndexCache.init(allocator),
}}, }},
.message_thread = null, .message_thread = null,
@ -283,6 +279,7 @@ pub const FileAccess = struct {
read: fn (*anyopaque, []u8) Error!usize, read: fn (*anyopaque, []u8) Error!usize,
seek: fn (*anyopaque, u64) Error!void, seek: fn (*anyopaque, u64) Error!void,
seekToEnd: fn (*anyopaque) Error!void, seekToEnd: fn (*anyopaque) Error!void,
skip: fn (*anyopaque, i64) Error!void,
}; };
/// ///
@ -343,14 +340,22 @@ pub const FileAccess = struct {
} }
/// ///
/// Attempts to seek `file_access` to the end of the file while using `app_context` as the execution /// Attempts to seek `file_access` to the end of the file.
/// context.
/// ///
/// Returns [Error] on failure. /// Returns [Error] on failure.
/// ///
pub fn seekToEnd(file_access: *FileAccess) Error!void { pub fn seekToEnd(file_access: *FileAccess) Error!void {
return file_access.implementation.seekToEnd(file_access.context); return file_access.implementation.seekToEnd(file_access.context);
} }
///
/// Attempts to seek `file_access` by `offset` from the current file position.
///
/// Returns [Error] on failure;
///
pub fn skip(file_access: *FileAccess, offset: i64) Error!void {
return file_access.implementation.skip(file_access.context, offset);
}
}; };
/// ///
@ -358,31 +363,15 @@ pub const FileAccess = struct {
/// available to the application in a sandboxed environment. /// available to the application in a sandboxed environment.
/// ///
pub const FileSystem = union(enum) { pub const FileSystem = union(enum) {
native: struct { native: []const u8,
path_prefix: []const u8,
},
archive: struct { archive: struct {
file_access: FileAccess, instance: oar.Archive,
index_cache: ArchiveIndexCache, entry_table: [max_open_entries]oar.Entry = std.mem.zeroes([max_open_entries]oar.Entry),
entry_table: [max_open_entries]ArchiveEntry = pub const max_open_entries = 16;
std.mem.zeroes([max_open_entries]ArchiveEntry),
const max_open_entries = 16;
}, },
///
/// Handles the state of an opened archive entry.
///
const ArchiveEntry = struct {
using: ?*FileAccess,
header: oar.Entry,
cursor: u64,
};
const ArchiveIndexCache = table.Hashed([]const u8, oar.Entry, table.string_context);
/// ///
/// Platform-agnostic mechanism for referencing files and directories on a [FileSystem]. /// Platform-agnostic mechanism for referencing files and directories on a [FileSystem].
/// ///
@ -462,57 +451,37 @@ pub const FileSystem = union(enum) {
.archive => |*archive| { .archive => |*archive| {
if (mode != .readonly) return error.ModeUnsupported; if (mode != .readonly) return error.ModeUnsupported;
for (archive.entry_table) |*entry| if (entry.using == null) { for (archive.entry_table) |*entry| if (entry.owner == null) {
const entry_path = path.buffer[0 .. path.length];
entry.* = .{
.header = find_header: {
if (archive.index_cache.lookup(entry_path)) |header|
break: find_header header.*;
const header = oar.Entry.find(archive.file_access,
entry_path) catch return error.FileNotFound;
// If caching fails... oh well...
archive.index_cache.insert(entry_path, header) catch {};
break: find_header header;
},
.using = &archive.file_access,
.cursor = 0,
};
const Implementation = struct { const Implementation = struct {
fn archiveEntryCast(context: *anyopaque) *ArchiveEntry { fn close(context: *anyopaque) void {
return @ptrCast(*ArchiveEntry, @alignCast( entryCast(context).owner = null;
@alignOf(ArchiveEntry), context));
} }
fn close(context: *anyopaque) void { fn entryCast(context: *anyopaque) *oar.Entry {
archiveEntryCast(context).using = null; return @ptrCast(*oar.Entry, @alignCast(
@alignOf(oar.Entry), context));
} }
fn queryCursor(context: *anyopaque) FileAccess.Error!u64 { fn queryCursor(context: *anyopaque) FileAccess.Error!u64 {
const archive_entry = archiveEntryCast(context); const archive_entry = entryCast(context);
if (archive_entry.using == null) return error.FileInaccessible; if (archive_entry.owner == null) return error.FileInaccessible;
return archive_entry.cursor; return archive_entry.cursor;
} }
fn queryLength(context: *anyopaque) FileAccess.Error!u64 { fn queryLength(context: *anyopaque) FileAccess.Error!u64 {
const archive_entry = archiveEntryCast(context); const archive_entry = entryCast(context);
if (archive_entry.using == null) return error.FileInaccessible; if (archive_entry.owner == null) return error.FileInaccessible;
return archive_entry.header.file_size; return archive_entry.header.file_size;
} }
fn read(context: *anyopaque, buffer: []u8) FileAccess.Error!usize { fn read(context: *anyopaque, buffer: []u8) FileAccess.Error!usize {
const archive_entry = archiveEntryCast(context); const archive_entry = entryCast(context);
const file_access = archive_entry.using orelse const file_access = archive_entry.owner orelse
return error.FileInaccessible; return error.FileInaccessible;
if (archive_entry.cursor >= archive_entry.header.file_size) if (archive_entry.cursor >= archive_entry.header.file_size)
@ -525,20 +494,40 @@ 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 = archiveEntryCast(context); const archive_entry = entryCast(context);
if (archive_entry.using == null) return error.FileInaccessible; if (archive_entry.owner == null) return error.FileInaccessible;
archive_entry.cursor = cursor; archive_entry.cursor = cursor;
} }
fn seekToEnd(context: *anyopaque) FileAccess.Error!void { fn seekToEnd(context: *anyopaque) FileAccess.Error!void {
const archive_entry = archiveEntryCast(context); const archive_entry = entryCast(context);
if (archive_entry.using == null) return error.FileInaccessible; if (archive_entry.owner == null) return error.FileInaccessible;
archive_entry.cursor = archive_entry.header.file_size; archive_entry.cursor = archive_entry.header.file_size;
} }
fn skip(context: *anyopaque, offset: i64) FileAccess.Error!void {
const math = std.math;
const archive_entry = entryCast(context);
if (archive_entry.owner == null) return error.FileInaccessible;
if (offset < 0) {
archive_entry.cursor = math.max(0,
archive_entry.cursor - math.absCast(offset));
} else {
archive_entry.cursor += @intCast(u64, offset);
}
}
};
entry.* = archive.instance.find(path.buffer[0 .. path.length]) catch |err|
return switch (err) {
error.FileInaccessible => error.FileNotFound,
error.EntryNotFound => error.FileNotFound,
}; };
return FileAccess{ return FileAccess{
@ -551,6 +540,7 @@ pub const FileSystem = union(enum) {
.read = Implementation.read, .read = Implementation.read,
.seek = Implementation.seek, .seek = Implementation.seek,
.seekToEnd = Implementation.seekToEnd, .seekToEnd = Implementation.seekToEnd,
.skip = Implementation.skip,
}, },
}; };
}; };
@ -559,22 +549,19 @@ pub const FileSystem = union(enum) {
}, },
.native => |native| { .native => |native| {
if (native.path_prefix.len == 0) return error.FileNotFound; if (native.len == 0) return error.FileNotFound;
var path_buffer = std.mem.zeroes([4096]u8); var path_buffer = std.mem.zeroes([4096]u8);
const seperator_length = @boolToInt(native[native.len - 1] != seperator);
const seperator_length = @boolToInt(native.path_prefix[ if ((native.len + seperator_length + path.length) >=
native.path_prefix.len - 1] != seperator);
if ((native.path_prefix.len + seperator_length + path.length) >=
path_buffer.len) return error.FileNotFound; path_buffer.len) return error.FileNotFound;
std.mem.copy(u8, path_buffer[0 ..], native.path_prefix); std.mem.copy(u8, path_buffer[0 ..], native);
if (seperator_length != 0) if (seperator_length != 0) path_buffer[native.len] = seperator;
path_buffer[native.path_prefix.len] = seperator;
std.mem.copy(u8, path_buffer[native.path_prefix.len .. std.mem.copy(u8, path_buffer[native.len ..
path_buffer.len], path.buffer[0 .. path.length]); path_buffer.len], path.buffer[0 .. path.length]);
ext.SDL_ClearError(); ext.SDL_ClearError();
@ -629,15 +616,14 @@ pub const FileSystem = union(enum) {
while (to_seek != 0) { while (to_seek != 0) {
const math = std.math; const math = std.math;
const sought = @intCast(i64, math.min(to_seek, math.maxInt(i64))); const sought = math.min(to_seek, math.maxInt(i64));
ext.SDL_ClearError(); ext.SDL_ClearError();
if (ext.SDL_RWseek(rwOpsCast(context), sought, ext.RW_SEEK_CUR) < 0) if (ext.SDL_RWseek(rwOpsCast(context), @intCast(i64, sought),
return error.FileInaccessible; ext.RW_SEEK_CUR) < 0) return error.FileInaccessible;
// Cannot be less than zero because it is derived from `read`. to_seek -= sought;
to_seek -= @intCast(u64, sought);
} }
} }
@ -647,6 +633,13 @@ pub const FileSystem = union(enum) {
if (ext.SDL_RWseek(rwOpsCast(context), 0, ext.RW_SEEK_END) < 0) if (ext.SDL_RWseek(rwOpsCast(context), 0, ext.RW_SEEK_END) < 0)
return error.FileInaccessible; return error.FileInaccessible;
} }
fn skip(context: *anyopaque, offset: i64) FileAccess.Error!void {
ext.SDL_ClearError();
if (ext.SDL_RWseek(rwOpsCast(context), offset, ext.RW_SEEK_SET) < 0)
return error.FileInaccessible;
}
}; };
return FileAccess{ return FileAccess{
@ -663,6 +656,7 @@ pub const FileSystem = union(enum) {
.read = Implementation.read, .read = Implementation.read,
.seek = Implementation.seek, .seek = Implementation.seek,
.seekToEnd = Implementation.seekToEnd, .seekToEnd = Implementation.seekToEnd,
.skip = Implementation.skip,
}, },
}; };
}, },
@ -855,7 +849,7 @@ pub fn runGraphics(comptime Error: anytype,
defer ext.SDL_DestroyRenderer(renderer); defer ext.SDL_DestroyRenderer(renderer);
var cwd_file_system = FileSystem{.native =.{.path_prefix = "./"}}; var cwd_file_system = FileSystem{.native ="./"};
var data_access = try (try cwd_file_system.joinedPath(&.{"./data.oar"})).open(.readonly); var data_access = try (try cwd_file_system.joinedPath(&.{"./data.oar"})).open(.readonly);
defer data_access.close(); defer data_access.close();