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 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 {
signature: [3]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(Entry);
if (entry_size != 512)
@compileError("Entry is " ++ std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes");
}
pub const Archive = struct {
file_access: sys.FileAccess,
index_cache: IndexCache,
///
/// [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 {
EntryNotFound,
@ -32,15 +19,110 @@ pub const Entry = extern struct {
///
///
///
pub fn find(file_access: sys.FileAccess, entry_name: []const u8) FindError!Entry {
_ = file_access;
_ = entry_name;
pub const InitError = std.mem.Allocator.Error;
return error.EntryNotFound;
///
///
///
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;
},
.owner = &archive.file_access,
.cursor = 0,
};
}
///
/// Magic identifier used to validate [Entry] data.
///
const signature_magic = [3]u8{'o', 'a', 'r'};
///
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.
///
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 stack = @import("./stack.zig");
const std = @import("std");
const table = @import("./table.zig");
///
/// 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
/// read from and `user_path_prefix` as the native writable user data directory.
/// Initializes a new [Implemenation] with `data_access` as the data archive to read from
/// and `user_path_prefix` as the native writable user data directory.
///
/// Returns the created [Implementation] value on success or [InitError] on failure.
///
fn init(allocator: std.mem.Allocator,
data_archive_file_access: FileAccess) InitError!Implementation {
fn init(allocator: std.mem.Allocator, data_access: FileAccess) InitError!Implementation {
const user_path_prefix = ext.SDL_GetPrefPath("ona", "ona") orelse
return error.OutOfMemory;
return Implementation{
.user_file_system = .{.native = .{.path_prefix =
user_path_prefix[0 .. std.mem.len(user_path_prefix)]}},
.user_file_system = .{.native =
user_path_prefix[0 .. std.mem.len(user_path_prefix)]},
.message_semaphore = ext.SDL_CreateSemaphore(0) orelse return error.OutOfSemaphores,
.message_mutex = ext.SDL_CreateMutex() orelse return error.OutOfMutexes,
.user_path_prefix = user_path_prefix,
.data_file_system = .{.archive = .{
.file_access = data_archive_file_access,
.index_cache = try FileSystem.ArchiveIndexCache.init(allocator),
.instance = try oar.Archive.init(allocator, data_access),
}},
.message_thread = null,
@ -283,6 +279,7 @@ pub const FileAccess = struct {
read: fn (*anyopaque, []u8) Error!usize,
seek: fn (*anyopaque, u64) 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
/// context.
/// Attempts to seek `file_access` to the end of the file.
///
/// Returns [Error] on failure.
///
pub fn seekToEnd(file_access: *FileAccess) Error!void {
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.
///
pub const FileSystem = union(enum) {
native: struct {
path_prefix: []const u8,
},
native: []const u8,
archive: struct {
file_access: FileAccess,
index_cache: ArchiveIndexCache,
instance: oar.Archive,
entry_table: [max_open_entries]oar.Entry = std.mem.zeroes([max_open_entries]oar.Entry),
entry_table: [max_open_entries]ArchiveEntry =
std.mem.zeroes([max_open_entries]ArchiveEntry),
const max_open_entries = 16;
pub 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].
///
@ -462,57 +451,37 @@ pub const FileSystem = union(enum) {
.archive => |*archive| {
if (mode != .readonly) return error.ModeUnsupported;
for (archive.entry_table) |*entry| if (entry.using == 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,
};
for (archive.entry_table) |*entry| if (entry.owner == null) {
const Implementation = struct {
fn archiveEntryCast(context: *anyopaque) *ArchiveEntry {
return @ptrCast(*ArchiveEntry, @alignCast(
@alignOf(ArchiveEntry), context));
fn close(context: *anyopaque) void {
entryCast(context).owner = null;
}
fn close(context: *anyopaque) void {
archiveEntryCast(context).using = null;
fn entryCast(context: *anyopaque) *oar.Entry {
return @ptrCast(*oar.Entry, @alignCast(
@alignOf(oar.Entry), context));
}
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;
}
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;
}
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;
if (archive_entry.cursor >= archive_entry.header.file_size)
@ -525,22 +494,42 @@ pub const FileSystem = union(enum) {
}
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;
}
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;
}
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{
.context = entry,
@ -551,6 +540,7 @@ pub const FileSystem = union(enum) {
.read = Implementation.read,
.seek = Implementation.seek,
.seekToEnd = Implementation.seekToEnd,
.skip = Implementation.skip,
},
};
};
@ -559,22 +549,19 @@ pub const FileSystem = union(enum) {
},
.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);
const seperator_length = @boolToInt(native[native.len - 1] != seperator);
const seperator_length = @boolToInt(native.path_prefix[
native.path_prefix.len - 1] != seperator);
if ((native.path_prefix.len + seperator_length + path.length) >=
if ((native.len + seperator_length + path.length) >=
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)
path_buffer[native.path_prefix.len] = seperator;
if (seperator_length != 0) path_buffer[native.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]);
ext.SDL_ClearError();
@ -629,15 +616,14 @@ pub const FileSystem = union(enum) {
while (to_seek != 0) {
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();
if (ext.SDL_RWseek(rwOpsCast(context), sought, ext.RW_SEEK_CUR) < 0)
return error.FileInaccessible;
if (ext.SDL_RWseek(rwOpsCast(context), @intCast(i64, sought),
ext.RW_SEEK_CUR) < 0) return error.FileInaccessible;
// Cannot be less than zero because it is derived from `read`.
to_seek -= @intCast(u64, sought);
to_seek -= sought;
}
}
@ -647,6 +633,13 @@ pub const FileSystem = union(enum) {
if (ext.SDL_RWseek(rwOpsCast(context), 0, ext.RW_SEEK_END) < 0)
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{
@ -663,6 +656,7 @@ pub const FileSystem = union(enum) {
.read = Implementation.read,
.seek = Implementation.seek,
.seekToEnd = Implementation.seekToEnd,
.skip = Implementation.skip,
},
};
},
@ -855,7 +849,7 @@ pub fn runGraphics(comptime Error: anytype,
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);
defer data_access.close();