diff --git a/src/main.zig b/src/main.zig index 2f85626..9b95b9d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -11,7 +11,9 @@ const sys = @import("./sys.zig"); /// Entry point. /// pub fn main() anyerror!void { - return nosuspend await async sys.runGraphics(anyerror, run); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + + return nosuspend await async sys.runGraphics(gpa.allocator(), anyerror, run); } test { diff --git a/src/sys.zig b/src/sys.zig index f427eeb..cbfe2a8 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -35,6 +35,7 @@ pub const AppContext = opaque { /// Internal state of the event loop hidden from the API consumer. /// const Implementation = struct { + user_path_prefix: [*]u8, data_file_system: FileSystem, user_file_system: FileSystem, message_semaphore: *ext.SDL_sem, @@ -104,6 +105,7 @@ pub const AppContext = opaque { ext.SDL_DestroyMutex(implementation.message_mutex); ext.SDL_DestroySemaphore(implementation.message_semaphore); + ext.SDL_free(implementation.user_path_prefix); } /// @@ -134,14 +136,25 @@ pub const AppContext = opaque { /// /// Returns the created [Implementation] value on success or [InitError] on failure. /// - fn init(data_archive_file_access: FileAccess, - user_path_prefix: []const u8) InitError!Implementation { + fn init(allocator: std.mem.Allocator, + data_archive_file_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)]}}, + .message_semaphore = ext.SDL_CreateSemaphore(0) orelse return error.OutOfSemaphores, .message_mutex = ext.SDL_CreateMutex() orelse return error.OutOfMutexes, - .data_file_system = .{.archive = .{.file_access = data_archive_file_access}}, - .user_file_system = .{.native = .{.path_prefix = user_path_prefix}}, + .user_path_prefix = user_path_prefix, + + .data_file_system = .{.archive = .{ + .file_access = data_archive_file_access, + .index_cache = try FileSystem.ArchiveIndexCache.init(allocator), + }}, + .message_thread = null, }; } @@ -351,7 +364,7 @@ pub const FileSystem = union(enum) { archive: struct { file_access: FileAccess, - index_cache: *table.Dynamic([]const u8, ArchiveEntry, table.string_context), + index_cache: ArchiveIndexCache, entry_table: [max_open_entries]ArchiveEntry = std.mem.zeroes([max_open_entries]ArchiveEntry), @@ -368,6 +381,8 @@ pub const FileSystem = union(enum) { cursor: u64, }; + const ArchiveIndexCache = table.Hashed([]const u8, oar.Entry, table.string_context); + /// /// Platform-agnostic mechanism for referencing files and directories on a [FileSystem]. /// @@ -447,99 +462,98 @@ pub const FileSystem = union(enum) { .archive => |*archive| { if (mode != .readonly) return error.ModeUnsupported; - for (archive.entry_table) |_, index| { - if (archive.entry_table[index].using == null) { - const archive_path = path.buffer[0 .. path.length]; + 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 entry_header = archive.index_cache.lookup(archive_path) orelse { const header = oar.Entry.find(archive.file_access, - archive_path) catch return error.FileNotFound; + entry_path) catch return error.FileNotFound; - archive.index_cache.insert(archive_path, header) catch { - // If caching fails... oh well... - }; + // If caching fails... oh well... + archive.index_cache.insert(entry_path, header) catch {}; - break header; - }; + break: find_header header; + }, - archive.entry_table[index] = .{ - .header = entry_header, - .using = &archive.file_access, - .cursor = 0, - }; + .using = &archive.file_access, + .cursor = 0, + }; - const Implementation = struct { - fn archiveEntryCast(context: *anyopaque) *ArchiveEntry { - return @ptrCast(*ArchiveEntry, @alignCast( - @alignOf(ArchiveEntry), context)); - } + const Implementation = struct { + fn archiveEntryCast(context: *anyopaque) *ArchiveEntry { + return @ptrCast(*ArchiveEntry, @alignCast( + @alignOf(ArchiveEntry), context)); + } - fn close(context: *anyopaque) void { - archiveEntryCast(context).using = null; - } + fn close(context: *anyopaque) void { + archiveEntryCast(context).using = null; + } - fn queryCursor(context: *anyopaque) FileAccess.Error!u64 { - const archive_entry = archiveEntryCast(context); + fn queryCursor(context: *anyopaque) FileAccess.Error!u64 { + const archive_entry = archiveEntryCast(context); - if (archive_entry.using == null) return error.FileInaccessible; + if (archive_entry.using == null) return error.FileInaccessible; - return archive_entry.cursor; - } + return archive_entry.cursor; + } - fn queryLength(context: *anyopaque) FileAccess.Error!u64 { - const archive_entry = archiveEntryCast(context); + fn queryLength(context: *anyopaque) FileAccess.Error!u64 { + const archive_entry = archiveEntryCast(context); - if (archive_entry.using == null) return error.FileInaccessible; + if (archive_entry.using == 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 { - const archive_entry = archiveEntryCast(context); + fn read(context: *anyopaque, buffer: []u8) FileAccess.Error!usize { + const archive_entry = archiveEntryCast(context); - const file_access = archive_entry.using orelse - return error.FileInaccessible; + const file_access = archive_entry.using orelse + return error.FileInaccessible; - if (archive_entry.cursor >= archive_entry.header.file_size) - return error.FileInaccessible; + if (archive_entry.cursor >= archive_entry.header.file_size) + return error.FileInaccessible; - try file_access.seek(archive_entry.header.file_offset); + try file_access.seek(archive_entry.header.file_offset); - return file_access.read(buffer[0 .. std.math.min( - buffer.len, archive_entry.header.file_size)]); - } + 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); + fn seek(context: *anyopaque, cursor: usize) FileAccess.Error!void { + const archive_entry = archiveEntryCast(context); - if (archive_entry.using == null) return error.FileInaccessible; + if (archive_entry.using == null) return error.FileInaccessible; - archive_entry.cursor = cursor; - } + archive_entry.cursor = cursor; + } - fn seekToEnd(context: *anyopaque) FileAccess.Error!void { - const archive_entry = archiveEntryCast(context); + fn seekToEnd(context: *anyopaque) FileAccess.Error!void { + const archive_entry = archiveEntryCast(context); - if (archive_entry.using == null) return error.FileInaccessible; + if (archive_entry.using == null) return error.FileInaccessible; - archive_entry.cursor = archive_entry.header.file_size; - } - }; + archive_entry.cursor = archive_entry.header.file_size; + } + }; - return FileAccess{ - .context = &archive.entry_table[index], + return FileAccess{ + .context = entry, - .implementation = &.{ - .close = Implementation.close, - .queryCursor = Implementation.queryCursor, - .queryLength = Implementation.queryLength, - .read = Implementation.read, - .seek = Implementation.seek, - .seekToEnd = Implementation.seekToEnd, - }, - }; - } - } + .implementation = &.{ + .close = Implementation.close, + .queryCursor = Implementation.queryCursor, + .queryLength = Implementation.queryLength, + .read = Implementation.read, + .seek = Implementation.seek, + .seekToEnd = Implementation.seekToEnd, + }, + }; + }; return error.OutOfFiles; }, @@ -805,7 +819,7 @@ pub const RunError = error { /// Should an error from `run` occur, an `Error` is returned, otherwise a [RunError] is returned if /// the underlying runtime fails and is logged. /// -pub fn runGraphics(comptime Error: anytype, +pub fn runGraphics(allocator: std.mem.Allocator, comptime Error: anytype, comptime run: GraphicsRunner(Error)) (RunError || Error)!void { if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) { @@ -841,14 +855,6 @@ pub fn runGraphics(comptime Error: anytype, defer ext.SDL_DestroyRenderer(renderer); - const user_path_prefix = ext.SDL_GetPrefPath("ona", "ona") orelse { - ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to load user path"); - - return error.InitFailure; - }; - - defer ext.SDL_free(user_path_prefix); - var cwd_file_system = FileSystem{.native =.{.path_prefix = "./"}}; var data_archive_file_access = try (try cwd_file_system. @@ -856,9 +862,7 @@ pub fn runGraphics(comptime Error: anytype, defer data_archive_file_access.close(); - var app_context = AppContext.Implementation.init(data_archive_file_access, - user_path_prefix[0 .. std.mem.len(user_path_prefix)]) catch |err| { - + var app_context = AppContext.Implementation.init(allocator, data_archive_file_access) catch |err| { ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, switch (err) { error.OutOfMemory => "Failed to allocate necessary memory", error.OutOfMutexes => "Failed to create file-system work lock", diff --git a/src/table.zig b/src/table.zig index 81d7f03..c89439c 100644 --- a/src/table.zig +++ b/src/table.zig @@ -1,61 +1,119 @@ const std = @import("std"); -pub fn Dynamic(comptime Key: type, comptime Value: type, comptime key_context: KeyContext(Key)) type { +/// +/// Returns a hash-backed table type of `Value`s indexed by `Key` and using `key_context` as the key +/// context. +/// +pub fn Hashed(comptime Key: type, comptime Value: type, + comptime key_context: KeyContext(Key)) type { + + const Allocator = std.mem.Allocator; + return struct { - load_maximum: f32, - buckets_used: usize, + allocator: Allocator, + load_limit: f32, buckets: []Bucket, + filled: usize, /// - /// + /// A slot in the hash table. /// const Bucket = struct { maybe_entry: ?struct { key: Key, value: Value, - }, + } = null, - maybe_next_index: ?usize, + maybe_next_index: ?usize = null, }; /// - /// + /// Hash table type. /// const Self = @This(); /// + /// Searches for `key` to delete it, returning the deleted value or `null` if no matching + /// key was found. /// - /// - pub fn delete(self: Self, key: Key) bool { - _ = key; - _ = self; + pub fn remove(self: Self, key: Key) ?Value { + var bucket = &(self.buckets[@mod(key_context.hash(key), self.buckets.len)]); + + if (bucket.maybe_entry) |*entry| if (key_context.equals(entry.key, key)) { + defer entry.value = null; + + self.filled -= 1; + + return entry.value; + }; + + while (bucket.maybe_next_index) |index| { + bucket = &(self.buckets[index]); + + if (bucket.maybe_entry) |*entry| if (key_context.equals(entry.key, key)) { + defer entry.value = null; + + self.filled -= 1; + + return entry.value; + }; + } + + return null; + } + + pub fn init(allocator: Allocator) Allocator.Error!Self { + return Self{ + .buckets = try allocator.alloc(Bucket, 4), + .filled = 0, + .allocator = allocator, + .load_limit = 0.75, + }; } /// + /// Attempts to insert the value at `key` to be `value` in `self`, returning an + /// [InsertError] if it fails. /// - /// - pub fn insert(self: Self, key: Key, value: Value) InsertError!void { - if ((@intToFloat(f32, self.buckets_used) / @intToFloat( - f32, self.buckets.len)) >= self.load_maximum) try self.rehash(); + pub fn insert(self: *Self, key: Key, value: Value) InsertError!void { + if (self.loadFactor() >= self.load_limit) { + const old_buckets = self.buckets; + + defer self.allocator.free(old_buckets); + + self.buckets = try self.allocator.alloc(Bucket, old_buckets.len * 2); + + for (old_buckets) |bucket, index| self.buckets[index] = bucket; + } var hash = @mod(key_context.hash(key), self.buckets.len); while (true) { const bucket = &(self.buckets[hash]); - const entry = &(bucket.maybe_entry orelse { + if (key_context.equals((bucket.maybe_entry orelse { bucket.maybe_entry = .{ .key = key, .value = value }; - }); - if (key_context.equals(entry.key, key)) return error.KeyExists; + self.filled += 1; - hash = @mod(hashHash(hash), self.buckets.len); + break; + }).key, key)) return error.KeyExists; + + hash = @mod(hash + 1, self.buckets.len); } } + /// + /// Returns the current load factor of `self`, which is derived from the number of capacity + /// that has been filled. + /// + pub fn loadFactor(self: Self) f32 { + return @intToFloat(f32, self.filled) / @intToFloat(f32, self.buckets.len); + } + /// /// Searches for a value indexed with `key` in `self`, returning it or `null` if no matching /// entry was found. @@ -81,7 +139,7 @@ pub fn Dynamic(comptime Key: type, comptime Value: type, comptime key_context: K /// /// /// -pub const InsertError = std.mem.Allocator || error { +pub const InsertError = std.mem.Allocator.Error || error { KeyExists, };