Implement loading of Oar archive entry-backed files
This commit is contained in:
		
							parent
							
								
									4f0224a029
								
							
						
					
					
						commit
						47a997b0ec
					
				
							
								
								
									
										219
									
								
								src/core/io.zig
									
									
									
									
									
								
							
							
						
						
									
										219
									
								
								src/core/io.zig
									
									
									
									
									
								
							| @ -4,31 +4,129 @@ const stack = @import("./stack.zig"); | ||||
| const testing = @import("./testing.zig"); | ||||
| 
 | ||||
| /// | ||||
| /// Allocation options for an [Allocator]. | ||||
| /// [AccessError.Inacessible] is a generic catch-all for IO resources that are inaccessible for | ||||
| /// implementation-specific reasons. | ||||
| /// | ||||
| pub const Allocation = struct { | ||||
|     existing: ?[*]u8, | ||||
|     alignment: u29, | ||||
|     size: usize, | ||||
| pub const AccessError = error { | ||||
|     Inaccessible, | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| /// Closure for dynamic memory allocation through the referenced allocator state machine capture. | ||||
| /// [AllocationError.OutOfMemory] if the requested amount of memory could not be allocated. | ||||
| /// | ||||
| pub const Allocator = meta.Function(Allocation, ?[*]u8); | ||||
| 
 | ||||
| /// | ||||
| /// [MakeError.OutOfMemory] if the requested amount of memory could not be allocated. | ||||
| /// | ||||
| pub const MakeError = error { | ||||
| pub const AllocationError = error { | ||||
|     OutOfMemory, | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| /// Closure that captures a reference to readable resources like block devices, memory buffers, | ||||
| /// network sockets, and more. | ||||
| /// Memory layout description for a memory allocation. | ||||
| /// | ||||
| pub const Reader = meta.Function([]u8, usize); | ||||
| pub const AllocationLayout = struct { | ||||
|     length: usize, | ||||
|     alignment: u29 = 8, | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| /// Interface for dynamic memory allocation through the state machine of the wrapped allocator | ||||
| /// implementation. | ||||
| /// | ||||
| pub const Allocator = struct { | ||||
|     context: *anyopaque, | ||||
| 
 | ||||
|     vtable: *const struct { | ||||
|         alloc: fn (*anyopaque, AllocationLayout) AllocationError![*]u8, | ||||
|         dealloc: fn (*anyopaque, [*]u8) void, | ||||
|         realloc: fn (*anyopaque, [*]u8, AllocationLayout) AllocationError![*]u8, | ||||
|     }, | ||||
| 
 | ||||
|     /// | ||||
|     /// Attempts to allocate a block of memory from `allocator` according to `layout`, returning it | ||||
|     /// or [AllocationError] if it failed. | ||||
|     /// | ||||
|     pub fn alloc(allocator: Allocator, layout: AllocationLayout) AllocationError![*]u8 { | ||||
|         return allocator.vtable.alloc(allocator.context, layout); | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// Deallocates the block of memory from `allocator` referenced by `allocation`. | ||||
|     /// | ||||
|     pub fn dealloc(allocator: Allocator, allocation: [*]u8) void { | ||||
|         allocator.vtable.dealloc(allocator.context, allocation); | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// Attempts to reallocate the existing block of memory from `allocator` referenced by | ||||
|     /// `allocation` according to `layout`, returning it or [AllocationError] if it failed. | ||||
|     /// | ||||
|     pub fn realloc(allocator: Allocator, allocation: [*]u8, | ||||
|         layout: AllocationLayout) AllocationError![*]u8 { | ||||
| 
 | ||||
|         return allocator.vtable.realloc(allocator.context, allocation, layout); | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// Wraps `implementation`, returning the [Allocator] value. | ||||
|     /// | ||||
|     pub fn wrap(implementation: anytype) Allocator { | ||||
|         const Implementation = @TypeOf(implementation.*); | ||||
| 
 | ||||
|         return .{ | ||||
|             .context = @ptrCast(*anyopaque, implementation), | ||||
| 
 | ||||
|             .vtable = switch (@typeInfo(Implementation)) { | ||||
|                 .Struct => &.{ | ||||
|                     .alloc = struct { | ||||
|                         fn call(context: *anyopaque, layout: AllocationLayout) AllocationError![*]u8 { | ||||
|                             return @ptrCast(*Implementation, @alignCast( | ||||
|                                 @alignOf(Implementation), context)).alloc(layout); | ||||
|                         } | ||||
|                     }.call, | ||||
| 
 | ||||
|                     .dealloc = struct { | ||||
|                         fn call(context: *anyopaque, allocation: [*]u8) void { | ||||
|                             return @ptrCast(*Implementation, @alignCast( | ||||
|                                 @alignOf(Implementation), context)).dealloc(allocation); | ||||
|                         } | ||||
|                     }.call, | ||||
| 
 | ||||
|                     .realloc = struct { | ||||
|                         fn call(context: *anyopaque, allocation: [*]u8, | ||||
|                             layout: AllocationLayout) AllocationError![*]u8 { | ||||
| 
 | ||||
|                             return @ptrCast(*Implementation, @alignCast( | ||||
|                                 @alignOf(Implementation), context)).realloc(allocation, layout); | ||||
|                         } | ||||
|                     }.call, | ||||
|                 }, | ||||
| 
 | ||||
|                 .Opaque => &.{ | ||||
|                     .alloc = struct { | ||||
|                         fn call(context: *anyopaque, layout: AllocationLayout) AllocationError![*]u8 { | ||||
|                             return @ptrCast(*Implementation, context).alloc(layout); | ||||
|                         } | ||||
|                     }.call, | ||||
| 
 | ||||
|                     .dealloc = struct { | ||||
|                         fn call(context: *anyopaque, allocation: [*]u8) void { | ||||
|                             return @ptrCast(*Implementation, context).dealloc(allocation); | ||||
|                         } | ||||
|                     }.call, | ||||
| 
 | ||||
|                     .realloc = struct { | ||||
|                         fn call(context: *anyopaque, allocation: [*]u8, | ||||
|                             layout: AllocationLayout) AllocationError![*]u8 { | ||||
| 
 | ||||
|                             return @ptrCast(*Implementation, context).realloc(allocation, layout); | ||||
|                         } | ||||
|                     }.call, | ||||
|                 }, | ||||
| 
 | ||||
|                 else => @compileError( | ||||
|                     "`context` must a single-element pointer referencing a struct or opaque type"), | ||||
|             }, | ||||
|         }; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| /// Returns a state machine for lazily computing all `Element` components of a given source input | ||||
| @ -143,10 +241,57 @@ test "Spliterator(u8)" { | ||||
| } | ||||
| 
 | ||||
| /// | ||||
| /// Closure that captures a reference to writable resources like block devices, memory buffers, | ||||
| /// Interface for capturing a reference to a writable resource like block devices, memory buffers, | ||||
| /// network sockets, and more. | ||||
| /// | ||||
| pub const Writer = meta.Function([]const u8, usize); | ||||
| pub const Writer = struct { | ||||
|     context: *anyopaque, | ||||
| 
 | ||||
|     vtable: *const struct { | ||||
|         write: fn (*anyopaque, []const u8) AccessError!usize, | ||||
|     }, | ||||
| 
 | ||||
|     /// | ||||
|     /// Wraps `implementation`, returning the [Writer] value. | ||||
|     /// | ||||
|     pub fn wrap(implementation: anytype) Writer { | ||||
|         const Implementation = @TypeOf(implementation.*); | ||||
| 
 | ||||
|         return .{ | ||||
|             .context = @ptrCast(*anyopaque, implementation), | ||||
| 
 | ||||
|             .vtable = switch (@typeInfo(Implementation)) { | ||||
|                 .Struct => &.{ | ||||
|                     .write = struct { | ||||
|                         fn call(context: *anyopaque, buffer: []const u8) AccessError!usize { | ||||
|                             return @ptrCast(*Implementation, | ||||
|                                 @alignCast(@alignOf(Implementation), context)).write(buffer); | ||||
|                         } | ||||
|                     }.call, | ||||
|                 }, | ||||
| 
 | ||||
|                 .Opaque => &.{ | ||||
|                     .write = struct { | ||||
|                         fn call(context: *anyopaque, buffer: []const u8) AccessError!usize { | ||||
|                             return @ptrCast(*Implementation, context).write(buffer); | ||||
|                         } | ||||
|                     }.call, | ||||
|                 }, | ||||
| 
 | ||||
|                 else => @compileError( | ||||
|                     "`context` must a single-element pointer referencing a struct or opaque type"), | ||||
|             }, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// Attempts to write to `buffer` to `writer`, returning the number of successfully written or | ||||
|     /// [AccessError] if it failed. | ||||
|     /// | ||||
|     pub fn write(writer: Writer, buffer: []const u8) AccessError!usize { | ||||
|         return writer.vtable.write(writer.context, buffer); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| /// Returns a sliced reference of the raw bytes in `pointer`. | ||||
| @ -320,19 +465,14 @@ test "findFirstOf" { | ||||
| /// `allocated_memory`. Anything else will result is considered unreachable logic. | ||||
| /// | ||||
| pub fn free(allocator: Allocator, allocated_memory: anytype) void { | ||||
|     if (allocator.call(.{ | ||||
|         .existing = @ptrCast([*]u8, switch (@typeInfo(@TypeOf(allocated_memory))) { | ||||
|     allocator.dealloc(@ptrCast([*]u8, switch (@typeInfo(@TypeOf(allocated_memory))) { | ||||
|         .Pointer => |info| switch (info.size) { | ||||
|             .One, .Many, .C => allocated_memory, | ||||
|             .Slice => allocated_memory.ptr, | ||||
|         }, | ||||
| 
 | ||||
|         else => @compileError("`allocated_memory` must be a pointer"), | ||||
|         }), | ||||
| 
 | ||||
|         .size = 0, | ||||
|         .alignment = 0, | ||||
|     }) != null) unreachable; | ||||
|     })); | ||||
| } | ||||
| 
 | ||||
| test "free" { | ||||
| @ -369,14 +509,13 @@ test "hashBytes" { | ||||
| /// Attempts to allocate a buffer of `size` `Element`s using `allocator`, returning it or a | ||||
| /// [MakeError] if it failed. | ||||
| /// | ||||
| pub fn makeMany(comptime Element: type, allocator: Allocator, size: usize) MakeError![*]Element { | ||||
| pub fn makeMany(comptime Element: type, allocator: Allocator, size: usize) AllocationError![*]Element { | ||||
|     const alignment = @alignOf(Element); | ||||
| 
 | ||||
|     return @ptrCast([*]Element, @alignCast(alignment, allocator.call(.{ | ||||
|         .existing = null, | ||||
|         .size = @sizeOf(Element) * size, | ||||
|     return @ptrCast([*]Element, @alignCast(alignment, try allocator.alloc(.{ | ||||
|         .length = @sizeOf(Element) * size, | ||||
|         .alignment = alignment, | ||||
|     }) orelse return error.OutOfMemory)); | ||||
|     }))); | ||||
| } | ||||
| 
 | ||||
| test "makeMany" { | ||||
| @ -392,14 +531,13 @@ test "makeMany" { | ||||
| /// Attempts to allocate a buffer of `1` `Element` using `allocator`, returning it or a [MakeError] | ||||
| /// if it failed. | ||||
| /// | ||||
| pub fn makeOne(comptime Element: type, allocator: Allocator) MakeError!*Element { | ||||
| pub fn makeOne(comptime Element: type, allocator: Allocator) AllocationError!*Element { | ||||
|     const alignment = @alignOf(Element); | ||||
| 
 | ||||
|     return @ptrCast(*Element, @alignCast(alignment, allocator.call(.{ | ||||
|         .existing = null, | ||||
|         .size = @sizeOf(Element), | ||||
|     return @ptrCast(*Element, @alignCast(alignment, try allocator.alloc(.{ | ||||
|         .length = @sizeOf(Element), | ||||
|         .alignment = alignment, | ||||
|     }) orelse return error.OutOfMemory)); | ||||
|     }))); | ||||
| } | ||||
| 
 | ||||
| test "makeOne" { | ||||
| @ -429,6 +567,11 @@ test "swap" { | ||||
|     try testing.expect(b == 0); | ||||
| } | ||||
| 
 | ||||
| /// | ||||
| /// Mandatory context variable used by [null_writer]. | ||||
| /// | ||||
| const null_context: u64 = 0; | ||||
| 
 | ||||
| /// | ||||
| /// Thread-safe and lock-free [Writer] that silently consumes all given data without failure and | ||||
| /// throws it away. | ||||
| @ -436,11 +579,13 @@ test "swap" { | ||||
| /// This is commonly used for testing or redirected otherwise unwanted output data that has to be | ||||
| /// sent somewhere for whatever reason. | ||||
| /// | ||||
| pub const null_writer = Writer.from(struct { | ||||
|     fn write(buffer: []const u8) usize { | ||||
| pub const null_writer = Writer.wrap(@ptrCast(*const opaque { | ||||
|     const Self = @This(); | ||||
| 
 | ||||
|     fn write(_: Self, buffer: []const u8) usize { | ||||
|         return buffer.len; | ||||
|     } | ||||
| }.write); | ||||
| }, &null_context)); | ||||
| 
 | ||||
| test "null_writer" { | ||||
|     const sequence = "foo"; | ||||
|  | ||||
| @ -8,77 +8,3 @@ pub fn FnReturn(comptime Fn: type) type { | ||||
| 
 | ||||
|     return type_info.Fn.return_type orelse void; | ||||
| } | ||||
| 
 | ||||
| /// | ||||
| /// Returns a single-input single-output closure type where `In` represents the input type, `Out` | ||||
| /// represents the output type, and `captures_size` represents the size of the closure context. | ||||
| /// | ||||
| pub fn Function(comptime In: type, comptime Out: type) type { | ||||
|     return struct { | ||||
|         callErased: fn (*anyopaque, In) Out, | ||||
|         context: *anyopaque, | ||||
| 
 | ||||
|         fn Invoker(comptime Context: type) type { | ||||
|             return if (Context == void) fn (In) Out else fn (Context, In) Out; | ||||
|         } | ||||
| 
 | ||||
|         /// | ||||
|         /// Function type. | ||||
|         /// | ||||
|         const Self = @This(); | ||||
| 
 | ||||
|         /// | ||||
|         /// Invokes `self` with `input`, producing a result according to the current context data. | ||||
|         /// | ||||
|         pub fn call(self: Self, input: In) Out { | ||||
|             return self.callErased(self.context, input); | ||||
|         } | ||||
| 
 | ||||
|         /// | ||||
|         /// Creates and returns a [Self] using the `invoke` as the behavior executed when [call] or | ||||
|         /// [callErased] is called. | ||||
|         /// | ||||
|         /// For creating a closure-style function, see [fromClosure]. | ||||
|         /// | ||||
|         pub fn from(comptime invoke: fn (In) Out) Self { | ||||
|             return .{ | ||||
|                 .context = undefined, | ||||
| 
 | ||||
|                 .callErased = struct { | ||||
|                     fn callErased(_: *anyopaque, input: In) Out { | ||||
|                         return invoke(input); | ||||
|                     } | ||||
|                 }.callErased, | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         /// | ||||
|         /// Creates and returns a [Self] by capturing the `context` value as the capture context and | ||||
|         /// `invoke` as the behavior executed when [call] or [callErased] is called. | ||||
|         /// | ||||
|         /// The newly created [Self] is returned. | ||||
|         /// | ||||
|         pub fn fromClosure(context: anytype, comptime invoke: fn (@TypeOf(context), In) Out) Self { | ||||
|             const Context = @TypeOf(context); | ||||
| 
 | ||||
|             switch (@typeInfo(Context)) { | ||||
|                 .Pointer => |info| if (info.size == .Slice) | ||||
|                     @compileError("`context` cannot be a slice"), | ||||
| 
 | ||||
|                 .Void => {}, | ||||
|                 else => @compileError("`context` must be a pointer"), | ||||
|             } | ||||
| 
 | ||||
|             return Self{ | ||||
|                 .context = @ptrCast(*anyopaque, context), | ||||
| 
 | ||||
|                 .callErased = struct { | ||||
|                     fn callErased(erased: *anyopaque, input: In) Out { | ||||
|                         return if (Context == void) invoke(input) else invoke(@ptrCast( | ||||
|                             Context, @alignCast(@alignOf(Context), erased)), input); | ||||
|                     } | ||||
|                 }.callErased, | ||||
|             }; | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| @ -145,38 +145,69 @@ pub const FixedPushError = error { | ||||
| /// memory pool to linearly allocate memory from. | ||||
| /// | ||||
| pub fn fixedAllocator(fixed_stack: *Fixed(u8)) io.Allocator { | ||||
|     return io.Allocator.fromClosure(fixed_stack, struct { | ||||
|         fn alloc(stack: *Fixed(u8), allocation: io.Allocation) ?[*]u8 { | ||||
|             if (allocation.existing) |buffer| if (allocation.size == 0) { | ||||
|                 // Deallocate the memory. | ||||
|                 const buffer_address = @ptrToInt(buffer); | ||||
|                 const stack_address = @ptrToInt(stack.buffer.ptr); | ||||
|     const FixedStack = @TypeOf(fixed_stack.*); | ||||
| 
 | ||||
|                 // Check the buffer is within the address space of the stack buffer. If not, it | ||||
|                 // should just be returned to let the caller know it cannot be freed. | ||||
|                 if (buffer_address < stack_address or buffer_address >= | ||||
|                     (stack_address + stack.filled)) return buffer; | ||||
|     return io.Allocator.wrap(@ptrCast(*opaque { | ||||
|         const Self = @This(); | ||||
| 
 | ||||
|                 // TODO: Investigate ways of actually freeing if it is the last allocation. | ||||
|                 return null; | ||||
|             } else { | ||||
|                 // TODO: Investigate ways of in-place relocating if it is the last allocation. | ||||
|             }; | ||||
| 
 | ||||
|             // Reallocate / allocate the memory. | ||||
|         pub fn alloc(self: *Self, layout: io.AllocationLayout) io.AllocationError![*]u8 { | ||||
|             // TODO: Remove stdlib dependency. | ||||
|             const stack = self.stackCast(); | ||||
| 
 | ||||
|             const adjusted_offset = @import("std").mem.alignPointerOffset(stack.buffer.ptr + | ||||
|                 stack.filled, allocation.alignment) orelse return null; | ||||
|                 stack.filled, layout.alignment) orelse return error.OutOfMemory; | ||||
| 
 | ||||
|             const head = stack.filled + adjusted_offset; | ||||
|             const tail = head + allocation.size; | ||||
|             const tail = head + layout.length; | ||||
| 
 | ||||
|             stack.pushMany(0, tail) catch return null; | ||||
|             stack.pushMany(0, tail) catch return error.OutOfMemory; | ||||
| 
 | ||||
|             return stack.buffer[head .. tail].ptr; | ||||
| 
 | ||||
|         } | ||||
|     }.alloc); | ||||
| 
 | ||||
|         pub fn dealloc(self: *Self, allocation: [*]u8) void { | ||||
|             // Deallocate the memory. | ||||
|             const stack = self.stackCast(); | ||||
|             const allocation_address = @ptrToInt(allocation); | ||||
|             const stack_address = @ptrToInt(stack.buffer.ptr); | ||||
| 
 | ||||
|             // Check the buffer is within the address space of the stack buffer. If not, it cannot | ||||
|             // be freed. | ||||
|             if (allocation_address < stack_address or allocation_address >= | ||||
|                 (stack_address + stack.filled)) unreachable; | ||||
| 
 | ||||
|             // TODO: Investigate ways of actually freeing if it is the last allocation. | ||||
|         } | ||||
| 
 | ||||
|         pub fn realloc(self: *Self, allocation: [*]u8, | ||||
|             layout: io.AllocationLayout) io.AllocationError![*]u8 { | ||||
| 
 | ||||
|             // TODO: Investigate ways of in-place relocating if it is the last allocation. | ||||
|             // TODO: Remove stdlib dependency. | ||||
|             const stack = self.stackCast(); | ||||
|             const allocation_address = @ptrToInt(allocation); | ||||
|             const stack_address = @ptrToInt(stack.buffer.ptr); | ||||
| 
 | ||||
|             // Check the buffer is within the address space of the stack buffer. If not, it cannot | ||||
|             // be reallocated. | ||||
|             if (allocation_address < stack_address or allocation_address >= | ||||
|                 (stack_address + stack.filled)) unreachable; | ||||
| 
 | ||||
|             const adjusted_offset = @import("std").mem.alignPointerOffset(stack.buffer.ptr + | ||||
|                 stack.filled, layout.alignment) orelse return error.OutOfMemory; | ||||
| 
 | ||||
|             const head = stack.filled + adjusted_offset; | ||||
|             const tail = head + layout.length; | ||||
| 
 | ||||
|             stack.pushMany(0, tail) catch return error.OutOfMemory; | ||||
| 
 | ||||
|             return stack.buffer[head .. tail].ptr; | ||||
|         } | ||||
| 
 | ||||
|         fn stackCast(self: *Self) *Fixed(u8) { | ||||
|             return @ptrCast(*FixedStack, @alignCast(@alignOf(FixedStack), self)); | ||||
|         } | ||||
|     }, fixed_stack)); | ||||
| } | ||||
| 
 | ||||
| test "fixedAllocator" { | ||||
| @ -185,14 +216,11 @@ test "fixedAllocator" { | ||||
|     const allocator = fixedAllocator(&stack); | ||||
| 
 | ||||
|     // Allocation | ||||
|     var block_memory = allocator.call(.{ | ||||
|         .existing = null, | ||||
|     var block_memory = try allocator.alloc(.{ | ||||
|         .alignment = @alignOf(u64), | ||||
|         .size = @sizeOf(u64), | ||||
|         .length = @sizeOf(u64), | ||||
|     }); | ||||
| 
 | ||||
|     try testing.expect(block_memory != null); | ||||
| 
 | ||||
|     const buffer_address_head = @ptrToInt(&buffer); | ||||
|     const buffer_address_tail = @ptrToInt(&buffer) + buffer.len; | ||||
| 
 | ||||
| @ -204,14 +232,11 @@ test "fixedAllocator" { | ||||
|     } | ||||
| 
 | ||||
|     // Reallocation. | ||||
|     block_memory = allocator.call(.{ | ||||
|         .existing = block_memory, | ||||
|     block_memory = try allocator.realloc(block_memory, .{ | ||||
|         .alignment = @alignOf(u64), | ||||
|         .size = @sizeOf(u64), | ||||
|         .length = @sizeOf(u64), | ||||
|     }); | ||||
| 
 | ||||
|     try testing.expect(block_memory != null); | ||||
| 
 | ||||
|     { | ||||
|         const block_memory_address = @ptrToInt(block_memory); | ||||
| 
 | ||||
| @ -220,11 +245,7 @@ test "fixedAllocator" { | ||||
|     } | ||||
| 
 | ||||
|     // Deallocation. | ||||
|     try testing.expect(allocator.call(.{ | ||||
|         .existing = block_memory, | ||||
|         .alignment = 0, | ||||
|         .size = 0, | ||||
|     }) == null); | ||||
|     allocator.dealloc(block_memory); | ||||
| } | ||||
| 
 | ||||
| /// | ||||
| @ -234,15 +255,23 @@ test "fixedAllocator" { | ||||
| /// referenced by `fixed_stack` until it is full. | ||||
| /// | ||||
| pub fn fixedWriter(fixed_stack: *Fixed(u8)) io.Writer { | ||||
|     return io.Writer.fromClosure(fixed_stack, struct { | ||||
|         fn write(stack: *Fixed(u8), buffer: []const u8) usize { | ||||
|             stack.pushAll(buffer) catch |err| switch (err) { | ||||
|     const FixedStack = @TypeOf(fixed_stack.*); | ||||
| 
 | ||||
|     return io.Writer.wrap(@ptrCast(*opaque { | ||||
|         const Self = @This(); | ||||
| 
 | ||||
|         fn stackCast(self: *Self) *Fixed(u8) { | ||||
|             return @ptrCast(*FixedStack, @alignCast(@alignOf(FixedStack), self)); | ||||
|         } | ||||
| 
 | ||||
|         pub fn write(self: *Self, buffer: []const u8) io.AccessError!usize { | ||||
|             self.stackCast().pushAll(buffer) catch |err| switch (err) { | ||||
|                 error.BufferOverflow => return 0, | ||||
|             }; | ||||
| 
 | ||||
|             return buffer.len; | ||||
|         } | ||||
|     }.write); | ||||
|     }, fixed_stack)); | ||||
| } | ||||
| 
 | ||||
| test "fixedWriter" { | ||||
| @ -250,6 +279,8 @@ test "fixedWriter" { | ||||
|     var sequence_stack = Fixed(u8){.buffer = &buffer}; | ||||
|     const sequence_data = [_]u8{8, 16, 32, 64}; | ||||
| 
 | ||||
|     try testing.expect(fixedWriter(&sequence_stack).call(&sequence_data) == sequence_data.len); | ||||
|     try testing.expect((try fixedWriter(&sequence_stack). | ||||
|         write(&sequence_data)) == sequence_data.len); | ||||
| 
 | ||||
|     try testing.expect(io.equals(u8, sequence_stack.buffer, &sequence_data)); | ||||
| } | ||||
|  | ||||
| @ -29,6 +29,11 @@ pub fn Hashed(comptime Key: type, comptime Value: type, | ||||
|             maybe_next_index: ?usize = null, | ||||
|         }; | ||||
| 
 | ||||
|         /// | ||||
|         /// Errors that may occur during initialization of a hash table. | ||||
|         /// | ||||
|         pub const InitError = io.AllocationError; | ||||
| 
 | ||||
|         /// | ||||
|         /// Hash table type. | ||||
|         /// | ||||
| @ -46,9 +51,9 @@ pub fn Hashed(comptime Key: type, comptime Value: type, | ||||
|         /// | ||||
|         /// Initializes a [Self] using `allocator` as the memory allocation strategy. | ||||
|         /// | ||||
|         /// Returns a new [Self] value or an [io.MakeError] if initializing failed. | ||||
|         /// Returns a new [Self] value or an [InitError] if initializing failed. | ||||
|         /// | ||||
|         pub fn init(allocator: Allocator) io.MakeError!Self { | ||||
|         pub fn init(allocator: Allocator) InitError!Self { | ||||
|             const capacity = 4; | ||||
| 
 | ||||
|             return Self{ | ||||
| @ -166,7 +171,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, | ||||
| /// [InsertError.KeyExists] occurs when an insertion was attempted on a table with a matching key | ||||
| /// already present. | ||||
| /// | ||||
| pub const InsertError = io.MakeError || error { | ||||
| pub const InsertError = io.AllocationError || error { | ||||
|     KeyExists, | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -7,7 +7,7 @@ const testing = @import("./testing.zig"); | ||||
| /// [PrintError.WriteFailure] occurs when the underlying [io.Writer] implementation failed to write | ||||
| /// the entirety of a the requested print operation. | ||||
| /// | ||||
| pub const PrintError = error { | ||||
| pub const PrintError = io.AccessError || error { | ||||
|     WriteFailure, | ||||
| }; | ||||
| 
 | ||||
| @ -69,7 +69,7 @@ pub fn printInt(writer: io.Writer, radix: Radix, value: anytype) PrintError!void | ||||
|             if (value == 0) { | ||||
|                 const zero = "0"; | ||||
| 
 | ||||
|                 if (writer.call(zero) != zero.len) return error.WriteFailure; | ||||
|                 if ((try writer.write(zero)) != zero.len) return error.WriteFailure; | ||||
|             } else { | ||||
|                 // Big enough to hold the hexadecimal representation of the integer type, which is | ||||
|                 // the largest number format accomodated for in [Radix]. | ||||
| @ -95,7 +95,7 @@ pub fn printInt(writer: io.Writer, radix: Radix, value: anytype) PrintError!void | ||||
|                 for (buffer[0 .. (buffer_count / 2)]) |_, i| | ||||
|                     io.swap(u8, &buffer[i], &buffer[buffer_count - i - 1]); | ||||
| 
 | ||||
|                 if (writer.call(buffer[0 .. buffer_count]) != buffer_count) | ||||
|                 if ((try writer.write(buffer[0 .. buffer_count])) != buffer_count) | ||||
|                     return error.WriteFailure; | ||||
|             } | ||||
|         }, | ||||
|  | ||||
| @ -13,22 +13,19 @@ pub fn main() anyerror!void { | ||||
| /// Runs the game engine. | ||||
| /// | ||||
| fn runEngine(app: *sys.App, graphics: *sys.Graphics) anyerror!void { | ||||
|     var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | ||||
| 
 | ||||
|     defer _ = gpa.deinit(); | ||||
| 
 | ||||
|     { | ||||
|         var file_reader = try app.data.openRead(try sys.Path.from(&.{"ona.lua"})); | ||||
|         const path = try sys.Path.from(&.{"ona.lua"}); | ||||
|         var file_reader = try app.data.openFileReader(path); | ||||
| 
 | ||||
|         defer file_reader.close(); | ||||
| 
 | ||||
|         const file_size = try file_reader.size(); | ||||
|         const allocator = gpa.allocator(); | ||||
|         const buffer = try allocator.alloc(u8, file_size); | ||||
|         const file_size = (try app.data.query(path)).length; | ||||
|         const allocator = sys.threadSafeAllocator(); | ||||
|         const buffer = (try core.io.makeMany(u8, allocator, file_size))[0 .. file_size]; | ||||
| 
 | ||||
|         defer allocator.free(buffer); | ||||
|         defer core.io.free(allocator, buffer); | ||||
| 
 | ||||
|         if ((try file_reader.read(0, buffer)) != file_size) return error.ScriptLoadFailure; | ||||
|         if ((try file_reader.read(buffer)) != file_size) return error.ScriptLoadFailure; | ||||
| 
 | ||||
|         app.log(.debug, buffer); | ||||
|     } | ||||
|  | ||||
							
								
								
									
										142
									
								
								src/ona/oar.zig
									
									
									
									
									
								
							
							
						
						
									
										142
									
								
								src/ona/oar.zig
									
									
									
									
									
								
							| @ -2,13 +2,13 @@ const core = @import("core"); | ||||
| const sys = @import("./sys.zig"); | ||||
| 
 | ||||
| /// | ||||
| /// Metadata of an Oar archive entry. | ||||
| /// Metadata of an Oar archive file entry. | ||||
| /// | ||||
| const Block = extern struct { | ||||
| const Entry = extern struct { | ||||
|     signature: [signature_magic.len]u8 = signature_magic, | ||||
|     path: sys.Path = sys.Path.empty, | ||||
|     data_size: u64 = 0, | ||||
|     data_head: u64 = 0, | ||||
|     data_offset: u64 = 0, | ||||
|     data_length: u64 = 0, | ||||
|     padding: [232]u8 = [_]u8{0} ** 232, | ||||
| 
 | ||||
|     comptime { | ||||
| @ -19,84 +19,14 @@ const Block = extern struct { | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| /// Reference to a file entry in an Oar archive, denoting the starting offset from the top of head | ||||
| /// of the file and its size. | ||||
| /// [FindError.ArchiveUnsupported] occurs when trying to read a file that does not follow an Oar | ||||
| /// archive format considered valid by this implemenatation. | ||||
| /// | ||||
| pub const Entry = struct { | ||||
|     head: u64, | ||||
|     size: u64, | ||||
| 
 | ||||
|     /// | ||||
|     /// [FindError.EntryNotFound] occurs when no entry matched the parameters of the find operation. | ||||
|     /// | ||||
|     /// [FindError.ArchiveUnsupported] occurs if the file provided to the find operation is not a | ||||
|     /// valid archive file. | ||||
|     /// | ||||
|     pub const FindError = error { | ||||
|         EntryNotFound, | ||||
| /// [FindError.EntryNotFound] occurs when the queried entry was not found in the archive file. | ||||
| /// | ||||
| pub const FindError = core.io.AccessError || error { | ||||
|     ArchiveUnsupported, | ||||
|     }; | ||||
| 
 | ||||
|     /// | ||||
|     /// Attempts to perform a binary search on the entry blocks defined in `archive_file` for one | ||||
|     /// matching `entry_path`, returning an [Entry] referencing its data or a [FindError] if it | ||||
|     /// failed. | ||||
|     /// | ||||
|     /// **Note** that this operation has `O(log n)` time complexity. | ||||
|     /// | ||||
|     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; | ||||
| 
 | ||||
|         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 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); | ||||
| 
 | ||||
|             if ((archive_file.read(header.entry_head + (block_size * midpoint), io.bytesOf(&block)) | ||||
|                 catch return error.ArchiveUnsupported) != block_size) return error.EntryNotFound; | ||||
| 
 | ||||
|             const comparison = entry_path.compare(block.path); | ||||
| 
 | ||||
|             if (comparison == 0) return Entry{ | ||||
|                 .head = block.data_head, | ||||
|                 .size = block.data_size, | ||||
|             }; | ||||
| 
 | ||||
|             if (comparison > 0) { | ||||
|                 head = (midpoint + 1); | ||||
|             } else { | ||||
|                 tail = (midpoint - 1); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return error.EntryNotFound; | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// Reads the data from `entry` in `archive_file` from the byte at the entry-relative `offset` | ||||
|     /// into `buffer` until either the end of the entry data, end of archive file, or end of buffer | ||||
|     /// is reached. | ||||
|     /// | ||||
|     /// The number of bytes read is returned or [sys.FileError] if it failed. | ||||
|     /// | ||||
|     pub fn read(entry: Entry, archive_file: *sys.ReadableFile, | ||||
|         offset: u64, buffer: []u8) sys.FileError!usize { | ||||
| 
 | ||||
|         return archive_file.read(entry.head + offset, | ||||
|             buffer[0 .. core.math.min(usize, buffer.len, entry.size)]); | ||||
|     } | ||||
|     EntryNotFound, | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| @ -106,8 +36,7 @@ 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, | ||||
|     padding: [502]u8 = [_]u8{0} ** 502, | ||||
| 
 | ||||
|     comptime { | ||||
|         const size = @sizeOf(@This()); | ||||
| @ -117,7 +46,54 @@ const Header = extern struct { | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| /// The magic revision number that this Oar software implementation understands. | ||||
| /// Attempts to find an [Entry] with a path name matching `path` in `archive_reader`. | ||||
| /// | ||||
| /// An [Entry] value is returned if a match was found, otherwise [FindError] if it failed. | ||||
| /// | ||||
| pub fn findEntry(archive_reader: sys.FileReader, path: sys.Path) FindError!Entry { | ||||
|     var header = Header{}; | ||||
|     const header_size = @sizeOf(Header); | ||||
|     const io = core.io; | ||||
| 
 | ||||
|     if ((try archive_reader.read(io.bytesOf(&header))) != header_size) | ||||
|         return error.ArchiveUnsupported; | ||||
| 
 | ||||
|     if (!io.equals(u8, &header.signature, &signature_magic)) | ||||
|         return error.ArchiveUnsupported; | ||||
| 
 | ||||
|     if (header.revision != revision_magic) return error.ArchiveUnsupported; | ||||
| 
 | ||||
|     // Read file table. | ||||
|     var head: u64 = 0; | ||||
|     var tail: u64 = (header.entry_count - 1); | ||||
|     const entry_size = @sizeOf(Entry); | ||||
| 
 | ||||
|     while (head <= tail) { | ||||
|         var entry = Entry{}; | ||||
|         const midpoint = head + ((tail - head) / 2); | ||||
|         const offset = header_size + (entry_size * midpoint); | ||||
| 
 | ||||
|         try archive_reader.seek(offset); | ||||
| 
 | ||||
|         if ((try archive_reader.read(io.bytesOf(&entry))) != entry_size) | ||||
|             return error.ArchiveUnsupported; | ||||
| 
 | ||||
|         const comparison = path.compare(entry.path); | ||||
| 
 | ||||
|         if (comparison == 0) return entry; | ||||
| 
 | ||||
|         if (comparison > 0) { | ||||
|             head = (midpoint + 1); | ||||
|         } else { | ||||
|             tail = (midpoint - 1); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return error.EntryNotFound; | ||||
| } | ||||
| 
 | ||||
| /// | ||||
| /// Magic revision number that this Oar software implementation understands. | ||||
| /// | ||||
| const revision_magic = 0; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										435
									
								
								src/ona/sys.zig
									
									
									
									
									
								
							
							
						
						
									
										435
									
								
								src/ona/sys.zig
									
									
									
									
									
								
							| @ -93,86 +93,106 @@ pub const App = struct { | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| /// Snapshotted information about the status of a file. | ||||
| /// | ||||
| /// | ||||
| 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(); | ||||
| 
 | ||||
|             const math = core.math; | ||||
|             const min = math.min; | ||||
|             const maxIntValue = math.maxIntValue; | ||||
|             var sought = min(u64, offset, maxIntValue(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 = min(u64, to_seek, maxIntValue(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); | ||||
|     } | ||||
| pub const FileStatus = struct { | ||||
|     length: u64, | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| /// [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. | ||||
| /// Interface for working with bi-directional, streamable resources accessed through a file-system. | ||||
| /// | ||||
| pub const FileError = error { | ||||
|     FileInaccessible, | ||||
| pub const FileReader = struct { | ||||
|     context: *anyopaque, | ||||
| 
 | ||||
|     vtable: *const struct { | ||||
|         close: fn (*anyopaque) void, | ||||
|         read: fn (*anyopaque, []u8) core.io.AccessError!u64, | ||||
|         seek: fn (*anyopaque, u64) core.io.AccessError!void, | ||||
|     }, | ||||
| 
 | ||||
|     /// | ||||
|     /// Closes the `file_reader`, logging a wraning if the `file_reader` is already considered | ||||
|     /// closed. | ||||
|     /// | ||||
|     pub fn close(file_reader: FileReader) void { | ||||
|         file_reader.vtable.close(file_reader.context); | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// Attempts to read from `file_reader` into `buffer`, returning the number of bytes | ||||
|     /// successfully read or [core.io.AccessError] if it failed. | ||||
|     /// | ||||
|     pub fn read(file_reader: FileReader, buffer: []u8) core.io.AccessError!u64 { | ||||
|         return file_reader.vtable.read(file_reader.context, buffer); | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// Attempts to seek from the beginning of `file_reader` to `cursor` bytes in, returning | ||||
|     /// [core.io.AccessError] if it failed. | ||||
|     /// | ||||
|     pub fn seek(file_reader: FileReader, cursor: u64) core.io.AccessError!void { | ||||
|         return file_reader.vtable.seek(file_reader.context, cursor); | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// Wraps `implementation`, returning a [FileReader] value. | ||||
|     /// | ||||
|     pub fn wrap(implementation: anytype) FileReader { | ||||
|         const Implementation = @TypeOf(implementation.*); | ||||
| 
 | ||||
|         return .{ | ||||
|             .context = @ptrCast(*anyopaque, implementation), | ||||
| 
 | ||||
|             .vtable = switch (@typeInfo(Implementation)) { | ||||
|                 .Struct => &.{ | ||||
|                     .close = struct { | ||||
|                         fn call(context: *anyopaque) void { | ||||
|                             @ptrCast(*Implementation, @alignCast( | ||||
|                                 @alignOf(Implementation), context)).close(); | ||||
|                         } | ||||
|                     }.call, | ||||
| 
 | ||||
|                     .read = struct { | ||||
|                         fn call(context: *anyopaque, buffer: []u8) core.io.AccessError!u64 { | ||||
|                             return @ptrCast(*Implementation, @alignCast( | ||||
|                                 @alignOf(Implementation), context)).read(buffer); | ||||
|                         } | ||||
|                     }.call, | ||||
| 
 | ||||
|                     .seek = struct { | ||||
|                         fn call(context: *anyopaque, cursor: u64) core.io.AccessError!void { | ||||
|                             return @ptrCast(*Implementation, @alignCast( | ||||
|                                 @alignOf(Implementation), context)).seek(cursor); | ||||
|                         } | ||||
|                     }.call, | ||||
|                 }, | ||||
| 
 | ||||
|                 .Opaque => &.{ | ||||
|                     .close = struct { | ||||
|                         fn call(context: *anyopaque) void { | ||||
|                             @ptrCast(*Implementation, context).close(); | ||||
|                         } | ||||
|                     }.call, | ||||
| 
 | ||||
|                     .read = struct { | ||||
|                         fn call(context: *anyopaque, buffer: []u8) core.io.AccessError!u64 { | ||||
|                             return @ptrCast(*Implementation, context).read(buffer); | ||||
|                         } | ||||
|                     }.call, | ||||
| 
 | ||||
|                     .seek = struct { | ||||
|                         fn call(context: *anyopaque, cursor: u64) core.io.AccessError!void { | ||||
|                             return @ptrCast(*Implementation, context).seek(cursor); | ||||
|                         } | ||||
|                     }.call, | ||||
|                 }, | ||||
| 
 | ||||
|                 else => @compileError( | ||||
|                     "`context` must a single-element pointer referencing a struct or opaque type"), | ||||
|             }, | ||||
|         }; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| @ -181,42 +201,84 @@ pub const FileError = error { | ||||
| /// | ||||
| pub const FileSystem = union(enum) { | ||||
|     native: []const u8, | ||||
|     archive_file: *ReadableFile, | ||||
| 
 | ||||
|     archive: struct { | ||||
|         file_system: *const FileSystem, | ||||
|         path: Path, | ||||
|     }, | ||||
| 
 | ||||
|     /// | ||||
|     /// With files typically being backed by a block device, they can produce a variety of errors - | ||||
|     /// from physical to virtual errors - these are all encapsulated by the API as general | ||||
|     /// [OpenError.FileNotFound] errors. | ||||
|     /// [AccessError.FileNotFound] occurs when a queried file could not be found on the file-system | ||||
|     /// by the process. This may mean the file does not exist, however it may also mean that the | ||||
|     /// process does not have sufficient rights to read it. | ||||
|     /// | ||||
|     /// When a given [FileSystem] does not support a specified [OpenMode], | ||||
|     /// [OpenError.ModeUnsupported] is used to inform the consuming code that another [OpenMode] | ||||
|     /// should be tried or, if no mode other is suitable, that the resource is effectively | ||||
|     /// unavailable. | ||||
|     /// [AccessError.FileSystemfailure] denotes a file-system implementation-specific failure to | ||||
|     /// access resources has occured and therefore cannot proceed to access the file. | ||||
|     /// | ||||
|     /// If the number of known [FileAccess] handles has been exhausted, [OpenError.OutOfFiles] is | ||||
|     /// used to communicate this. | ||||
|     /// | ||||
|     pub const OpenError = error { | ||||
|     pub const AccessError = error { | ||||
|         FileNotFound, | ||||
|         ModeUnsupported, | ||||
|         OutOfFiles, | ||||
|         FileSystemFailure, | ||||
|     }; | ||||
| 
 | ||||
|     /// | ||||
|     /// Attempts to open the file identified by `path` with `mode` as the mode for opening the file. | ||||
|     /// Attempts to open the file identified by `path` on `file_system` for reading, returning a | ||||
|     /// [FileReader] value that provides access to the opened file or [AccessError] if it failed. | ||||
|     /// | ||||
|     /// Returns a [ReadableFile] reference that provides access to the file referenced by `path`or a | ||||
|     /// [OpenError] if it failed. | ||||
|     /// | ||||
|     pub fn openRead(file_system: *const FileSystem, path: Path) OpenError!*ReadableFile { | ||||
|         switch (file_system.*) { | ||||
|             .archive_file => |archive_file| { | ||||
|                 const entry = oar.Entry.find(archive_file, path) catch return error.FileNotFound; | ||||
|     pub fn openFileReader(file_system: FileSystem, path: Path) AccessError!FileReader { | ||||
|         switch (file_system) { | ||||
|             .archive => |archive| { | ||||
|                 const archive_reader = try archive.file_system.openFileReader(archive.path); | ||||
| 
 | ||||
|                 _ = entry; | ||||
|                 // TODO: Alloc file context. | ||||
|                 errdefer archive_reader.close(); | ||||
| 
 | ||||
|                 return error.FileNotFound; | ||||
|                 const entry = oar.findEntry(archive_reader, path) catch |err| return switch (err) { | ||||
|                     error.ArchiveUnsupported, error.Inaccessible => error.FileSystemFailure, | ||||
|                     error.EntryNotFound => error.FileNotFound, | ||||
|                 }; | ||||
| 
 | ||||
|                 archive_reader.seek(entry.data_offset) catch return error.FileSystemFailure; | ||||
| 
 | ||||
|                 const io = core.io; | ||||
| 
 | ||||
|                 const allocator = threadSafeAllocator(); | ||||
| 
 | ||||
|                 const entry_reader = io.makeOne(struct { | ||||
|                     allocator: io.Allocator, | ||||
|                     base_reader: FileReader, | ||||
|                     cursor: u64, | ||||
|                     offset: u64, | ||||
|                     length: u64, | ||||
| 
 | ||||
|                     const Self = @This(); | ||||
| 
 | ||||
|                     pub fn close(self: *Self) void { | ||||
|                         self.base_reader.close(); | ||||
|                         io.free(self.allocator, self); | ||||
|                     } | ||||
| 
 | ||||
|                     pub fn read(self: *Self, buffer: []u8) io.AccessError!u64 { | ||||
|                         try self.base_reader.seek(self.offset + self.cursor); | ||||
| 
 | ||||
|                         return self.base_reader.read(buffer[0 .. | ||||
|                             core.math.min(usize, buffer.len, self.length)]); | ||||
|                     } | ||||
| 
 | ||||
|                     pub fn seek(self: *Self, cursor: u64) io.AccessError!void { | ||||
|                         self.cursor = cursor; | ||||
|                     } | ||||
|                 }, allocator) catch return error.FileSystemFailure; | ||||
| 
 | ||||
|                 errdefer io.free(allocator, entry_reader); | ||||
| 
 | ||||
|                 entry_reader.* = .{ | ||||
|                     .allocator = allocator, | ||||
|                     .base_reader = archive_reader, | ||||
|                     .cursor = 0, | ||||
|                     .offset = entry.data_offset, | ||||
|                     .length = entry.data_length, | ||||
|                 }; | ||||
| 
 | ||||
|                 return FileReader.wrap(entry_reader); | ||||
|             }, | ||||
| 
 | ||||
|             .native => |native| { | ||||
| @ -239,8 +301,124 @@ pub const FileSystem = union(enum) { | ||||
| 
 | ||||
|                 ext.SDL_ClearError(); | ||||
| 
 | ||||
|                 return @ptrCast(*ReadableFile, ext.SDL_RWFromFile(&path_buffer, "rb") | ||||
|                     orelse return error.FileNotFound); | ||||
|                 const rw_ops = | ||||
|                     ext.SDL_RWFromFile(&path_buffer, "rb") orelse return error.FileNotFound; | ||||
| 
 | ||||
|                 errdefer _ = ext.SDL_RWclose(rw_ops); | ||||
| 
 | ||||
|                 return FileReader.wrap(@ptrCast(*opaque { | ||||
|                     const Self = @This(); | ||||
| 
 | ||||
|                     fn rwOpsCast(self: *Self) *ext.SDL_RWops { | ||||
|                         return @ptrCast(*ext.SDL_RWops, @alignCast(@alignOf(ext.SDL_RWops), self)); | ||||
|                     } | ||||
| 
 | ||||
|                     pub fn read(self: *Self, buffer: []u8) core.io.AccessError!u64 { | ||||
|                         ext.SDL_ClearError(); | ||||
| 
 | ||||
|                         const bytes_read = | ||||
|                             ext.SDL_RWread(self.rwOpsCast(), buffer.ptr, @sizeOf(u8), buffer.len); | ||||
| 
 | ||||
|                         if ((bytes_read == 0) and (ext.SDL_GetError() != null)) | ||||
|                             return error.Inaccessible; | ||||
| 
 | ||||
|                         return bytes_read; | ||||
|                     } | ||||
| 
 | ||||
|                     pub fn seek(self: *Self, cursor: u64) core.io.AccessError!void { | ||||
|                         ext.SDL_ClearError(); | ||||
| 
 | ||||
|                         const math = core.math; | ||||
|                         const min = math.min; | ||||
|                         const maxIntValue = math.maxIntValue; | ||||
|                         var sought = min(u64, cursor, maxIntValue(i64)); | ||||
|                         const ops = self.rwOpsCast(); | ||||
| 
 | ||||
|                         if (ext.SDL_RWseek(ops, @intCast(i64, sought), ext.RW_SEEK_SET) < 0) | ||||
|                             return error.Inaccessible; | ||||
| 
 | ||||
|                         var to_seek = cursor - sought; | ||||
| 
 | ||||
|                         while (to_seek != 0) { | ||||
|                             sought = min(u64, to_seek, maxIntValue(i64)); | ||||
| 
 | ||||
|                             ext.SDL_ClearError(); | ||||
| 
 | ||||
|                             if (ext.SDL_RWseek(ops, @intCast(i64, sought), ext.RW_SEEK_CUR) < 0) | ||||
|                                 return error.Inaccessible; | ||||
| 
 | ||||
|                             to_seek -= sought; | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     pub fn close(self: *Self) void { | ||||
|                         ext.SDL_ClearError(); | ||||
| 
 | ||||
|                         if (ext.SDL_RWclose(self.rwOpsCast()) != 0) | ||||
|                             return ext.SDL_LogWarn(ext.SDL_LOG_CATEGORY_APPLICATION, | ||||
|                                 "Attempt to close an invalid file reference"); | ||||
|                     } | ||||
|                 }, rw_ops)); | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// Attempts to query the status of the file identified by `path` on `file_system` for reading, | ||||
|     /// returning a [FileStatus] value containing a the state of the file at the moment or | ||||
|     /// [AccessError] if it failed. | ||||
|     /// | ||||
|     pub fn query(file_system: FileSystem, path: Path) AccessError!FileStatus { | ||||
|         switch (file_system) { | ||||
|             .archive => |archive| { | ||||
|                 const archive_reader = try archive.file_system.openFileReader(archive.path); | ||||
| 
 | ||||
|                 defer archive_reader.close(); | ||||
| 
 | ||||
|                 const entry = oar.findEntry(archive_reader, path) catch |err| return switch (err) { | ||||
|                     error.ArchiveUnsupported, error.Inaccessible => error.FileSystemFailure, | ||||
|                     error.EntryNotFound => error.FileNotFound, | ||||
|                 }; | ||||
| 
 | ||||
|                 return FileStatus{ | ||||
|                     .length = entry.data_length, | ||||
|                 }; | ||||
|             }, | ||||
| 
 | ||||
|             .native => |native| { | ||||
|                 if (native.len == 0) return error.FileNotFound; | ||||
| 
 | ||||
|                 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; | ||||
| 
 | ||||
|                 const io = core.io; | ||||
| 
 | ||||
|                 io.copy(u8, &path_buffer, native); | ||||
| 
 | ||||
|                 if (seperator_length != 0) path_buffer[native.len] = Path.seperator; | ||||
| 
 | ||||
|                 io.copy(u8, path_buffer[native.len .. path_buffer.len], | ||||
|                     path.buffer[0 .. path.length]); | ||||
| 
 | ||||
|                 ext.SDL_ClearError(); | ||||
| 
 | ||||
|                 const rw_ops = | ||||
|                     ext.SDL_RWFromFile(&path_buffer, "rb") orelse return error.FileSystemFailure; | ||||
| 
 | ||||
|                 defer if (ext.SDL_RWclose(rw_ops) != 0) unreachable; | ||||
| 
 | ||||
|                 ext.SDL_ClearError(); | ||||
| 
 | ||||
|                 const size = ext.SDL_RWsize(rw_ops); | ||||
| 
 | ||||
|                 if (size < 0) return error.FileSystemFailure; | ||||
| 
 | ||||
|                 return FileStatus{ | ||||
|                     .length = @intCast(u64, size), | ||||
|                 }; | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
| @ -346,7 +524,8 @@ pub const Path = extern struct { | ||||
|     }; | ||||
| 
 | ||||
|     /// | ||||
|     /// | ||||
|     /// Returns a value above `0` if the path of `this` is greater than `that`, below `0` if it is | ||||
|     /// less, or `0` if they are identical. | ||||
|     /// | ||||
|     pub fn compare(this: Path, that: Path) isize { | ||||
|         return core.io.compareBytes(this.buffer[0 ..this.length], that.buffer[0 .. that.length]); | ||||
| @ -432,16 +611,29 @@ pub const RunError = error { | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| /// Returns a [core.io.Allocator] bound to the underlying system allocator. | ||||
| /// Returns a thread-safe [core.io.Allocator] value based on the default system allocation strategy. | ||||
| /// | ||||
| pub fn allocator() core.io.Allocator { | ||||
|     // TODO: Add leak detection. | ||||
|     return .{ | ||||
|         .bound = .{ | ||||
|             .alloc = ext.SDL_alloc, | ||||
|             .dealloc = ext.SDL_free, | ||||
|         }, | ||||
|     }; | ||||
| pub fn threadSafeAllocator() core.io.Allocator { | ||||
|     const io = core.io; | ||||
| 
 | ||||
|     return io.Allocator.wrap(@as(*opaque { | ||||
|         const Self = @This(); | ||||
| 
 | ||||
|         pub fn alloc(_: *Self, layout: io.AllocationLayout) io.AllocationError![*]u8 { | ||||
|             return @ptrCast([*]u8, ext.SDL_malloc(layout.length) orelse return error.OutOfMemory); | ||||
|         } | ||||
| 
 | ||||
|         pub fn realloc(_: *Self, allocation: [*]u8, | ||||
|             layout: io.AllocationLayout) io.AllocationError![*]u8 { | ||||
| 
 | ||||
|             return @ptrCast([*]u8, ext.SDL_realloc(allocation, layout.length) | ||||
|                 orelse return error.OutOfMemory); | ||||
|         } | ||||
| 
 | ||||
|         pub fn dealloc(_: *Self, allocation: [*]u8) void { | ||||
|             ext.SDL_free(allocation); | ||||
|         } | ||||
|     }, undefined)); | ||||
| } | ||||
| 
 | ||||
| /// | ||||
| @ -453,16 +645,11 @@ pub fn allocator() core.io.Allocator { | ||||
| pub fn display(comptime Error: anytype, | ||||
|     comptime run: fn (*App, *Graphics) callconv(.Async) Error!void) (RunError || Error)!void { | ||||
| 
 | ||||
|     var cwd = FileSystem{.native = "./"}; | ||||
|     const cwd = FileSystem{.native = "./"}; | ||||
|     const user_prefix = ext.SDL_GetPrefPath("ona", "ona") orelse return error.InitFailure; | ||||
| 
 | ||||
|     defer ext.SDL_free(user_prefix); | ||||
| 
 | ||||
|     var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | ||||
| 
 | ||||
|     defer if (gpa.deinit()) | ||||
|         ext.SDL_LogWarn(ext.SDL_LOG_CATEGORY_APPLICATION, "Runtime allocator leaked memory"); | ||||
| 
 | ||||
|     var app = App{ | ||||
|         .user = .{.native = std.mem.sliceTo(user_prefix, 0)}, | ||||
| 
 | ||||
| @ -480,17 +667,13 @@ pub fn display(comptime Error: anytype, | ||||
|             return error.InitFailure; | ||||
|         }, | ||||
| 
 | ||||
|         .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; | ||||
|             }, | ||||
|         }, | ||||
|         .data = .{.archive = .{ | ||||
|             .file_system = &cwd, | ||||
|             .path = try Path.from(&.{"./data.oar"}), | ||||
|         }}, | ||||
|     }; | ||||
| 
 | ||||
|     defer { | ||||
|         app.data.archive_file.close(); | ||||
|         ext.SDL_DestroySemaphore(app.message_semaphore); | ||||
|         ext.SDL_DestroyMutex(app.message_mutex); | ||||
|     } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user