From e95c754d62f126742fd9be48d6eb34d187e63c04 Mon Sep 17 00:00:00 2001 From: kayomn Date: Tue, 1 Nov 2022 15:08:10 +0000 Subject: [PATCH] Refactor Oar implementation --- src/ona/main.zig | 8 +- src/ona/oar.zig | 168 ++++++++--------- src/ona/sys.zig | 467 +++++++++++++++++++++-------------------------- 3 files changed, 282 insertions(+), 361 deletions(-) diff --git a/src/ona/main.zig b/src/ona/main.zig index d38b13d..e8e8718 100644 --- a/src/ona/main.zig +++ b/src/ona/main.zig @@ -15,17 +15,17 @@ fn run(app: *sys.App, graphics: *sys.Graphics) anyerror!void { defer _ = gpa.deinit(); { - var file_access = try app.data.open(try sys.Path.joined(&.{"ona.lua"}), .readonly); + var file_reader = try app.data.openRead(try sys.Path.from(&.{"ona.lua"})); - defer file_access.close(); + defer file_reader.close(); - const file_size = try file_access.queryLength(); + const file_size = try file_reader.size(); const allocator = gpa.allocator(); const buffer = try allocator.alloc(u8, file_size); defer allocator.free(buffer); - if ((try file_access.read(buffer)) != file_size) return error.ScriptLoadFailure; + if ((try file_reader.read(0, buffer)) != file_size) return error.ScriptLoadFailure; app.log(.debug, buffer); } diff --git a/src/ona/oar.zig b/src/ona/oar.zig index d24da0d..16feb84 100644 --- a/src/ona/oar.zig +++ b/src/ona/oar.zig @@ -1,138 +1,116 @@ -const core = @import("./core.zig"); +const core = @import("core"); const sys = @import("./sys.zig"); /// +/// Metadata of an Oar archive entry. +/// +const Block = extern struct { + signature: [signature_magic.len]u8 = signature_magic, + revision: u8 = 0, + path: sys.Path = sys.Path.empty, + data_size: u64 = 0, + data_head: u64 = 0, + padding: [232]u8 = [_]u8{0} ** 232, + + comptime { + const entry_size = @sizeOf(@This()); + + if (entry_size != 512) @compileError("EntryBlock is greater than 512 bytes"); + } +}; + /// /// -pub const Archive = struct { - file_system: *const sys.FileSystem, - archive_path: sys.Path, +/// +pub const Entry = struct { + head: u64, + size: u64, /// /// /// - const Header = extern struct { - signature: [signature_magic.len]u8, - revision: u8, - entry_offset: u64, - padding: [500]u8 = std.mem.zeroes([500]u8), - - /// - /// Magic identifier used to validate [Entry] data. - /// - const signature_magic = [3]u8{'o', 'a', 'r'}; - - comptime { - const size = @sizeOf(@This()); - - if (size != 512) - @compileError("Header is " ++ - std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes"); - } - }; - - /// - /// An entry block of an Oar archive file. - /// - /// Typically, following the block in memory is the file data it holds the meta-information for. - /// - 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 { - const entry_size = @sizeOf(Entry); - - if (entry_size != 512) - @compileError("Entry is " ++ - std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes"); - } - }; - - /// - /// - /// - pub const OpenError = error { - FileNotFound, + pub const FindError = error { EntryNotFound, - UnsupportedArchive, + ArchiveUnsupported, }; /// /// /// - pub fn open(archive: Archive, entry_path: Path) OpenError!EntryAccess { - const file_access = try archive.file_system.open(entry_path, .readonly); - - errdefer file_access.close(); - - var header = std.mem.zeroes(Header); + pub fn find(archive_file: *sys.ReadableFile, entry_path: sys.Path) FindError!Entry { + var header = Header{}; const header_size = @sizeOf(Header); const io = core.io; - // Validate header. - if ((try file_access.read(io.bytes(&header)) != header_size) or header - (!core.io.equals(u8, &header.signature, &Header.signature_magic)) or - (header.revision != revision) or - (header.entry_offset <= header_size)) return error.UnsupportedArchive; - - // Go to file table. - try file_access.seek(header.entry_offset); + if (((archive_file.read(0, io.bytesOf(&header)) catch + return error.ArchiveUnsupported) != header_size) or + (!io.equals(u8, &header.signature, &signature_magic)) or + (header.revision != revision_magic) or + (header.entry_head <= header_size)) return error.ArchiveUnsupported; // Read file table. - var entry_buffer = std.mem.zeroes([8]Entry); - var bytes_read = try file_access.read(io.bytes(&entry_buffer)); - - while (@mod(bytes_read, @sizeOf(Entry)) == 0) { - for (entry_buffer[0 .. (bytes_read / @sizeOf(Entry))]) |entry| { - if (entry.path.equals(entry_path)) { - file_access.seek(entry.file_offset); - - return Entry{ - .file_access = file_access, - }; - } - } - - bytes_read = try file_access.read(io.bytes(&entry_buffer)); - } - - var head = std.mem.zeroes(usize); - var tail = (header.file_count - 1); + var head: usize = 0; + var tail: usize = (header.entry_count - 1); + const block_size = @sizeOf(Block); while (head <= tail) { + var block = Block{}; const midpoint = (head + (tail - head) / 2); - const comparison = entry_path.compare(arr[m])); + if ((archive_file.read(header.entry_head + (block_size * midpoint), io.bytesOf(&block)) + catch return error.ArchiveUnsupported) != block_size) return error.EntryNotFound; - if (comparison == 0) return midpoint; + const comparison = entry_path.compare(block.path); + + if (comparison == 0) return Entry{ + .head = block.data_head, + .size = block.data_size, + }; if (comparison > 0) { - // If x greater, ignore left half head = (midpoint + 1); } else { - // If x is smaller, ignore right half tail = (midpoint - 1); } } return error.EntryNotFound; } -}; -pub const EntryAccess = struct { - file_access: FileAccess, + /// + /// + /// + pub fn read(entry: Entry, archive_file: *sys.ReadableFile, + offset: u64, buffer: []u8) sys.FileError!usize { - pub fn close(entry: Entry) void { - entry.file_access.close(); + return archive_file.read(entry.head + offset, + buffer[0 .. core.math.min(usize, buffer.len, entry.size)]); } }; /// /// /// -const revision = 0; +const Header = extern struct { + signature: [signature_magic.len]u8 = signature_magic, + revision: u8 = revision_magic, + entry_count: u32 = 0, + entry_head: u64 = 0, + padding: [496]u8 = [_]u8{0} ** 496, + + comptime { + const size = @sizeOf(@This()); + + if (size != 512) @compileError("Header is greater than 512 bytes"); + } +}; + +/// +/// +/// +const revision_magic = 0; + +/// +/// Magic identifier used to validate [Entry] data. +/// +const signature_magic = [3]u8{'o', 'a', 'r'}; diff --git a/src/ona/sys.zig b/src/ona/sys.zig index 63f3ed3..27bbae2 100644 --- a/src/ona/sys.zig +++ b/src/ona/sys.zig @@ -3,7 +3,7 @@ const ext = @cImport({ }); const core = @import("core"); -const oar = @import("oar"); +const oar = @import("./oar.zig"); const std = @import("std"); /// @@ -92,13 +92,93 @@ pub const App = struct { } }; +/// +/// +/// +pub const ReadableFile = opaque { + /// + /// + /// + pub fn close(readable_file: *ReadableFile) void { + if (ext.SDL_RWclose(readable_file.rwOpsCast()) != 0) + return ext.SDL_LogWarn(ext.SDL_LOG_CATEGORY_APPLICATION, + "Attempt to close an invalid file reference"); + } + + /// + /// + /// + pub fn read(readable_file: *ReadableFile, offset: u64, buffer: []u8) FileError!u64 { + const rw_ops = readable_file.rwOpsCast(); + + { + ext.SDL_ClearError(); + + var sought = core.math.min(u64, offset, core.math.maxInt(i64)); + + if (ext.SDL_RWseek(rw_ops, @intCast(i64, sought), ext.RW_SEEK_SET) < 0) + return error.FileInaccessible; + + var to_seek = offset - sought; + + while (to_seek != 0) { + sought = core.math.min(u64, to_seek, core.math.maxInt(i64)); + + ext.SDL_ClearError(); + + if (ext.SDL_RWseek(rw_ops, @intCast(i64, sought), ext.RW_SEEK_CUR) < 0) + return error.FileInaccessible; + + to_seek -= sought; + } + } + + ext.SDL_ClearError(); + + const buffer_read = ext.SDL_RWread(rw_ops, buffer.ptr, @sizeOf(u8), buffer.len); + + if ((buffer_read == 0) and (ext.SDL_GetError() != null)) + return error.FileInaccessible; + + return buffer_read; + } + + /// + /// + /// + pub fn rwOpsCast(readable_file: *ReadableFile) *ext.SDL_RWops { + return @ptrCast(*ext.SDL_RWops, @alignCast(@alignOf(ext.SDL_RWops), readable_file)); + } + + /// + /// + /// + pub fn size(readable_file: *ReadableFile) FileError!u64 { + ext.SDL_ClearError(); + + const byte_size = ext.SDL_RWsize(readable_file.rwOpsCast()); + + if (byte_size < 0) return error.FileInaccessible; + + return @intCast(u64, byte_size); + } +}; + +/// +/// [Error.FileInaccessible] is a generic catch-all for a [FileAccess] reference no longer pointing +/// to a file or the file becomming invalid for whatever reason. +/// +pub const FileError = error { + FileInaccessible, +}; + /// /// Platform-agnostic mechanism for working with an abstraction of the underlying file-system(s) /// available to the application in a sandboxed environment. /// pub const FileSystem = union(enum) { native: []const u8, - archive: Archive, + archive_file: *ReadableFile, /// /// With files typically being backed by a block device, they can produce a variety of errors - @@ -119,275 +199,45 @@ pub const FileSystem = union(enum) { OutOfFiles, }; - /// - /// [OpenMode.readonly] indicates that an existing file is opened in a read-only state, - /// disallowing write access. - /// - /// [OpenMode.overwrite] indicates that an empty file has been created or an existing file has - /// been completely overwritten into. - /// - /// [OpenMode.append] indicates that an existing file that has been opened for reading from and - /// writing to on the end of existing data. - /// - pub const OpenMode = enum { - readonly, - overwrite, - append, - }; - /// /// Attempts to open the file identified by `path` with `mode` as the mode for opening the file. /// - /// Returns a [FileAccess] reference that provides access to the file referenced by `path`or a + /// Returns a [ReadableFile] reference that provides access to the file referenced by `path`or a /// [OpenError] if it failed. /// - pub fn open(file_system: *const FileSystem, path: Path, - mode: OpenMode) OpenError!core.io.FileAccess { - + pub fn openRead(file_system: *const FileSystem, path: Path) OpenError!*ReadableFile { switch (file_system.*) { - .archive => |*archive| { - if (mode != .readonly) return error.ModeUnsupported; + .archive_file => |archive_file| { + const entry = oar.Entry.find(archive_file, path) catch return error.FileNotFound; - const FileAccess = core.io.FileAccess; + _ = entry; + // TODO: Alloc file context. - for (archive.entry_table) |*entry| if (entry.owner == null) { - const Implementation = struct { - fn archiveEntryCast(context: *anyopaque) *Archive.Entry { - return @ptrCast(*Archive.Entry, @alignCast( - @alignOf(Archive.Entry), context)); - } - - fn close(context: *anyopaque) void { - archiveEntryCast(context).owner = null; - } - - fn queryCursor(context: *anyopaque) FileAccess.Error!u64 { - const archive_entry = archiveEntryCast(context); - - if (archive_entry.owner == null) return error.FileInaccessible; - - return archive_entry.cursor; - } - - fn queryLength(context: *anyopaque) FileAccess.Error!u64 { - const archive_entry = archiveEntryCast(context); - - 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 file_access = archive_entry.owner orelse - return error.FileInaccessible; - - if (archive_entry.cursor >= archive_entry.header.file_size) - return error.FileInaccessible; - - try file_access.seek(archive_entry.header.absolute_offset); - - return file_access.read(buffer[0 .. std.math.min( - buffer.len, archive_entry.header.file_size)]); - } - - fn seek(context: *anyopaque, cursor: usize) FileAccess.Error!void { - const archive_entry = archiveEntryCast(context); - - if (archive_entry.owner == null) return error.FileInaccessible; - - archive_entry.cursor = cursor; - } - - fn seekToEnd(context: *anyopaque) FileAccess.Error!void { - const archive_entry = archiveEntryCast(context); - - 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 archive_entry = archiveEntryCast(context); - - if (archive_entry.owner == null) return error.FileInaccessible; - - if (offset < 0) { - const math = std.math; - - archive_entry.cursor = math.max(0, - archive_entry.cursor - math.absCast(offset)); - } else { - archive_entry.cursor += @intCast(u64, offset); - } - } - }; - - const Header = oar.Entry; - - if (archive.index_cache.lookup(path)) |index| { - archive.file_access.seek(index) catch return error.FileNotFound; - - entry.* = .{ - .owner = &archive.file_access, - .cursor = 0, - - .header = (Header.next(archive.file_access) catch - return error.FileNotFound) orelse { - - // Remove cannot fail if lookup succeeded. - std.debug.assert(archive.index_cache.remove(path) != null); - - return error.FileNotFound; - }, - }; - } else { - while (Header.next(archive.file_access) catch - return error.FileNotFound) |entry_header| { - - if (entry.header.path.equals(path)) entry.* = .{ - .owner = &archive.file_access, - .cursor = 0, - .header = entry_header, - }; - } - - return error.FileNotFound; - } - - return FileAccess{ - .context = entry, - - .implementation = &.{ - .close = Implementation.close, - .queryCursor = Implementation.queryCursor, - .queryLength = Implementation.queryLength, - .read = Implementation.read, - .seek = Implementation.seek, - .seekToEnd = Implementation.seekToEnd, - .skip = Implementation.skip, - }, - }; - }; - - return error.OutOfFiles; + return error.FileNotFound; }, .native => |native| { if (native.len == 0) return error.FileNotFound; - const mem = std.mem; - var path_buffer = mem.zeroes([4096]u8); - const seperator_length = @boolToInt(native[native.len - 1] != oar.Path.seperator); + var path_buffer = [_]u8{0} ** 4096; + const seperator_length = @boolToInt(native[native.len - 1] != Path.seperator); - if ((native.len + seperator_length + path.length) >= - path_buffer.len) return error.FileNotFound; + if ((native.len + seperator_length + path.length) >= path_buffer.len) + return error.FileNotFound; - mem.copy(u8, &path_buffer, native); + const io = core.io; - if (seperator_length != 0) path_buffer[native.len] = oar.Path.seperator; + io.copy(u8, &path_buffer, native); - mem.copy(u8, path_buffer[native.len .. path_buffer. - len], path.buffer[0 .. path.length]); + if (seperator_length != 0) path_buffer[native.len] = Path.seperator; - const FileAccess = core.io.FileAccess; - - const Implementation = struct { - fn rwOpsCast(context: *anyopaque) *ext.SDL_RWops { - return @ptrCast(*ext.SDL_RWops, @alignCast( - @alignOf(ext.SDL_RWops), context)); - } - - fn close(context: *anyopaque) void { - ext.SDL_ClearError(); - - if (ext.SDL_RWclose(rwOpsCast(context)) != 0) - ext.SDL_LogWarn(ext.SDL_LOG_CATEGORY_APPLICATION, ext.SDL_GetError()); - } - - fn queryCursor(context: *anyopaque) FileAccess.Error!u64 { - ext.SDL_ClearError(); - - const sought = ext.SDL_RWtell(rwOpsCast(context)); - - if (sought < 0) return error.FileInaccessible; - - return @intCast(u64, sought); - } - - fn queryLength(context: *anyopaque) FileAccess.Error!u64 { - ext.SDL_ClearError(); - - const sought = ext.SDL_RWsize(rwOpsCast(context)); - - if (sought < 0) return error.FileInaccessible; - - return @intCast(u64, sought); - } - - fn read(context: *anyopaque, buffer: []u8) FileAccess.Error!usize { - ext.SDL_ClearError(); - - const buffer_read = ext.SDL_RWread(rwOpsCast( - context), buffer.ptr, @sizeOf(u8), buffer.len); - - if ((buffer_read == 0) and (ext.SDL_GetError() != null)) - return error.FileInaccessible; - - return buffer_read; - } - - fn seek(context: *anyopaque, cursor: usize) FileAccess.Error!void { - var to_seek = cursor; - - while (to_seek != 0) { - const math = std.math; - const sought = math.min(to_seek, math.maxInt(i64)); - - ext.SDL_ClearError(); - - if (ext.SDL_RWseek(rwOpsCast(context), @intCast(i64, sought), - ext.RW_SEEK_CUR) < 0) return error.FileInaccessible; - - to_seek -= sought; - } - } - - fn seekToEnd(context: *anyopaque) FileAccess.Error!void { - ext.SDL_ClearError(); - - 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; - } - }; + io.copy(u8, path_buffer[native.len .. path_buffer.len], + path.buffer[0 .. path.length]); ext.SDL_ClearError(); - return FileAccess{ - .context = ext.SDL_RWFromFile(&path_buffer, switch (mode) { - .readonly => "rb", - .overwrite => "wb", - .append => "ab", - }) orelse return error.FileNotFound, - - .implementation = &.{ - .close = Implementation.close, - .queryCursor = Implementation.queryCursor, - .queryLength = Implementation.queryLength, - .read = Implementation.read, - .seek = Implementation.seek, - .seekToEnd = Implementation.seekToEnd, - .skip = Implementation.skip, - }, - }; + return @ptrCast(*ReadableFile, ext.SDL_RWFromFile(&path_buffer, "rb") + orelse return error.FileNotFound); }, } } @@ -472,7 +322,102 @@ pub const Message = struct { /// /// Path to a file on a [FileSystem]. /// -pub const Path = oar.Path; +pub const Path = extern struct { + buffer: [255]u8, + length: u8, + + /// + /// [Error.TooLong] occurs when creating a path that is greater than the maximum path size **in + /// bytes**. + /// + pub const FromError = error { + TooLong, + }; + + /// + /// An empty [Path] with a length of `0`. + /// + pub const empty = Path{ + .buffer = [_]u8{0} ** 255, + .length = 0, + }; + + /// + /// + /// + pub fn compare(this: Path, that: Path) isize { + return core.io.compareBytes(this.buffer[0 ..this.length], that.buffer[0 .. that.length]); + } + + /// + /// Returns `true` if `this` is equal to `that`, otherwise `false`. + /// + pub fn equals(this: Path, that: Path) bool { + return core.io.equals(u8, this.buffer[0 ..this.length], that.buffer[0 .. that.length]); + } + + /// + /// Attempts to create a [Path] with the path components in `sequences` as a fully qualified + /// path from root. + /// + /// A [Path] value is returned containing the fully qualified path from the file-system root or + /// a [FromError] if it could not be created. + /// + pub fn from(sequences: []const []const u8) FromError!Path { + var path = empty; + + if (sequences.len != 0) { + const last_sequence_index = sequences.len - 1; + + for (sequences) |sequence, index| if (sequence.len != 0) { + var components = core.io.Spliterator(u8){ + .source = sequence, + .delimiter = "/", + }; + + while (components.next()) |component| if (component.len != 0) { + for (component) |byte| { + if (path.length == max) return error.TooLong; + path.buffer[path.length] = byte; + path.length += 1; + } + + if (components.hasNext()) { + if (path.length == max) return error.TooLong; + path.buffer[path.length] = '/'; + path.length += 1; + } + }; + + if (index < last_sequence_index) { + if (path.length == max) return error.TooLong; + + path.buffer[path.length] = '/'; + path.length += 1; + } + }; + } + + return path; + } + + /// + /// Returns the hash of the text in `path`. + /// + pub fn hash(path: Path) usize { + return core.io.hashBytes(path.buffer[0 .. path.length]); + } + + /// + /// Maximum number of **bytes** in a [Path]. + /// + pub const max = 255; + + /// + /// Textual separator between components of a [Path]. + /// + pub const seperator = '/'; +}; /// /// [RunError.InitFailure] occurs when the runtime fails to initialize. @@ -504,32 +449,30 @@ pub fn display(comptime Error: anytype, .user = .{.native = std.mem.sliceTo(user_prefix, 0)}, .message_semaphore = ext.SDL_CreateSemaphore(0) orelse { - ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to create message semaphore"); + ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, + "Failed to create message semaphore"); return error.InitFailure; }, .message_mutex = ext.SDL_CreateMutex() orelse { - ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to create message mutex"); + ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, + "Failed to create message mutex"); return error.InitFailure; }, - .data = .{.archive = .{ - .index_cache = FileSystem.Archive.IndexCache.init(gpa.allocator()) catch - return error.InitFailure, - - .file_access = cwd.open(try Path.joined(&.{"./data.oar"}), .readonly) catch { + .data = .{ + .archive_file = cwd.openRead(try Path.from(&.{"./data.oar"})) catch { ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to load ./data.oar"); return error.InitFailure; }, - }}, + }, }; defer { - app.data.archive.file_access.close(); - app.data.archive.index_cache.deinit(); + app.data.archive_file.close(); ext.SDL_DestroySemaphore(app.message_semaphore); ext.SDL_DestroyMutex(app.message_mutex); } @@ -546,7 +489,7 @@ pub fn display(comptime Error: anytype, app.enqueue(&message); { - var status = std.mem.zeroes(c_int); + var status: c_int = 0; // SDL2 defines waiting on a null thread reference as a no-op. See // https://wiki.libsdl.org/SDL_WaitThread for more information