Application Context Implementation #4
| @ -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 { | ||||
|  | ||||
							
								
								
									
										66
									
								
								src/sys.zig
									
									
									
									
									
								
							
							
						
						
									
										66
									
								
								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,23 +462,23 @@ 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... | ||||
|                                 }; | ||||
|                                 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, | ||||
|                         }; | ||||
| @ -527,7 +542,7 @@ pub const FileSystem = union(enum) { | ||||
|                         }; | ||||
| 
 | ||||
|                         return FileAccess{ | ||||
|                                 .context = &archive.entry_table[index], | ||||
|                             .context = entry, | ||||
| 
 | ||||
|                             .implementation = &.{ | ||||
|                                 .close = Implementation.close, | ||||
| @ -538,8 +553,7 @@ pub const FileSystem = union(enum) { | ||||
|                                 .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", | ||||
|  | ||||
| @ -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, | ||||
| }; | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user