Application Context Implementation #4
219
src/core/io.zig
219
src/core/io.zig
|
@ -4,31 +4,129 @@ const stack = @import("./stack.zig");
|
||||||
const testing = @import("./testing.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 {
|
pub const AccessError = error {
|
||||||
existing: ?[*]u8,
|
Inaccessible,
|
||||||
alignment: u29,
|
|
||||||
size: usize,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
/// 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);
|
pub const AllocationError = error {
|
||||||
|
|
||||||
///
|
|
||||||
/// [MakeError.OutOfMemory] if the requested amount of memory could not be allocated.
|
|
||||||
///
|
|
||||||
pub const MakeError = error {
|
|
||||||
OutOfMemory,
|
OutOfMemory,
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Closure that captures a reference to readable resources like block devices, memory buffers,
|
/// Memory layout description for a memory allocation.
|
||||||
/// network sockets, and more.
|
|
||||||
///
|
///
|
||||||
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
|
/// 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.
|
/// 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`.
|
/// 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.
|
/// `allocated_memory`. Anything else will result is considered unreachable logic.
|
||||||
///
|
///
|
||||||
pub fn free(allocator: Allocator, allocated_memory: anytype) void {
|
pub fn free(allocator: Allocator, allocated_memory: anytype) void {
|
||||||
if (allocator.call(.{
|
allocator.dealloc(@ptrCast([*]u8, switch (@typeInfo(@TypeOf(allocated_memory))) {
|
||||||
.existing = @ptrCast([*]u8, switch (@typeInfo(@TypeOf(allocated_memory))) {
|
|
||||||
.Pointer => |info| switch (info.size) {
|
.Pointer => |info| switch (info.size) {
|
||||||
.One, .Many, .C => allocated_memory,
|
.One, .Many, .C => allocated_memory,
|
||||||
.Slice => allocated_memory.ptr,
|
.Slice => allocated_memory.ptr,
|
||||||
},
|
},
|
||||||
|
|
||||||
else => @compileError("`allocated_memory` must be a pointer"),
|
else => @compileError("`allocated_memory` must be a pointer"),
|
||||||
}),
|
}));
|
||||||
|
|
||||||
.size = 0,
|
|
||||||
.alignment = 0,
|
|
||||||
}) != null) unreachable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "free" {
|
test "free" {
|
||||||
|
@ -369,14 +509,13 @@ test "hashBytes" {
|
||||||
/// Attempts to allocate a buffer of `size` `Element`s using `allocator`, returning it or a
|
/// Attempts to allocate a buffer of `size` `Element`s using `allocator`, returning it or a
|
||||||
/// [MakeError] if it failed.
|
/// [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);
|
const alignment = @alignOf(Element);
|
||||||
|
|
||||||
return @ptrCast([*]Element, @alignCast(alignment, allocator.call(.{
|
return @ptrCast([*]Element, @alignCast(alignment, try allocator.alloc(.{
|
||||||
.existing = null,
|
.length = @sizeOf(Element) * size,
|
||||||
.size = @sizeOf(Element) * size,
|
|
||||||
.alignment = alignment,
|
.alignment = alignment,
|
||||||
}) orelse return error.OutOfMemory));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "makeMany" {
|
test "makeMany" {
|
||||||
|
@ -392,14 +531,13 @@ test "makeMany" {
|
||||||
/// Attempts to allocate a buffer of `1` `Element` using `allocator`, returning it or a [MakeError]
|
/// Attempts to allocate a buffer of `1` `Element` using `allocator`, returning it or a [MakeError]
|
||||||
/// if it failed.
|
/// 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);
|
const alignment = @alignOf(Element);
|
||||||
|
|
||||||
return @ptrCast(*Element, @alignCast(alignment, allocator.call(.{
|
return @ptrCast(*Element, @alignCast(alignment, try allocator.alloc(.{
|
||||||
.existing = null,
|
.length = @sizeOf(Element),
|
||||||
.size = @sizeOf(Element),
|
|
||||||
.alignment = alignment,
|
.alignment = alignment,
|
||||||
}) orelse return error.OutOfMemory));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "makeOne" {
|
test "makeOne" {
|
||||||
|
@ -429,6 +567,11 @@ test "swap" {
|
||||||
try testing.expect(b == 0);
|
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
|
/// Thread-safe and lock-free [Writer] that silently consumes all given data without failure and
|
||||||
/// throws it away.
|
/// 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
|
/// This is commonly used for testing or redirected otherwise unwanted output data that has to be
|
||||||
/// sent somewhere for whatever reason.
|
/// sent somewhere for whatever reason.
|
||||||
///
|
///
|
||||||
pub const null_writer = Writer.from(struct {
|
pub const null_writer = Writer.wrap(@ptrCast(*const opaque {
|
||||||
fn write(buffer: []const u8) usize {
|
const Self = @This();
|
||||||
|
|
||||||
|
fn write(_: Self, buffer: []const u8) usize {
|
||||||
return buffer.len;
|
return buffer.len;
|
||||||
}
|
}
|
||||||
}.write);
|
}, &null_context));
|
||||||
|
|
||||||
test "null_writer" {
|
test "null_writer" {
|
||||||
const sequence = "foo";
|
const sequence = "foo";
|
||||||
|
|
|
@ -8,77 +8,3 @@ pub fn FnReturn(comptime Fn: type) type {
|
||||||
|
|
||||||
return type_info.Fn.return_type orelse void;
|
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.
|
/// memory pool to linearly allocate memory from.
|
||||||
///
|
///
|
||||||
pub fn fixedAllocator(fixed_stack: *Fixed(u8)) io.Allocator {
|
pub fn fixedAllocator(fixed_stack: *Fixed(u8)) io.Allocator {
|
||||||
return io.Allocator.fromClosure(fixed_stack, struct {
|
const FixedStack = @TypeOf(fixed_stack.*);
|
||||||
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);
|
|
||||||
|
|
||||||
// Check the buffer is within the address space of the stack buffer. If not, it
|
return io.Allocator.wrap(@ptrCast(*opaque {
|
||||||
// should just be returned to let the caller know it cannot be freed.
|
const Self = @This();
|
||||||
if (buffer_address < stack_address or buffer_address >=
|
|
||||||
(stack_address + stack.filled)) return buffer;
|
|
||||||
|
|
||||||
// TODO: Investigate ways of actually freeing if it is the last allocation.
|
pub fn alloc(self: *Self, layout: io.AllocationLayout) io.AllocationError![*]u8 {
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
// TODO: Investigate ways of in-place relocating if it is the last allocation.
|
|
||||||
};
|
|
||||||
|
|
||||||
// Reallocate / allocate the memory.
|
|
||||||
// TODO: Remove stdlib dependency.
|
// TODO: Remove stdlib dependency.
|
||||||
|
const stack = self.stackCast();
|
||||||
|
|
||||||
const adjusted_offset = @import("std").mem.alignPointerOffset(stack.buffer.ptr +
|
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 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;
|
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" {
|
test "fixedAllocator" {
|
||||||
|
@ -185,14 +216,11 @@ test "fixedAllocator" {
|
||||||
const allocator = fixedAllocator(&stack);
|
const allocator = fixedAllocator(&stack);
|
||||||
|
|
||||||
// Allocation
|
// Allocation
|
||||||
var block_memory = allocator.call(.{
|
var block_memory = try allocator.alloc(.{
|
||||||
.existing = null,
|
|
||||||
.alignment = @alignOf(u64),
|
.alignment = @alignOf(u64),
|
||||||
.size = @sizeOf(u64),
|
.length = @sizeOf(u64),
|
||||||
});
|
});
|
||||||
|
|
||||||
try testing.expect(block_memory != null);
|
|
||||||
|
|
||||||
const buffer_address_head = @ptrToInt(&buffer);
|
const buffer_address_head = @ptrToInt(&buffer);
|
||||||
const buffer_address_tail = @ptrToInt(&buffer) + buffer.len;
|
const buffer_address_tail = @ptrToInt(&buffer) + buffer.len;
|
||||||
|
|
||||||
|
@ -204,14 +232,11 @@ test "fixedAllocator" {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reallocation.
|
// Reallocation.
|
||||||
block_memory = allocator.call(.{
|
block_memory = try allocator.realloc(block_memory, .{
|
||||||
.existing = block_memory,
|
|
||||||
.alignment = @alignOf(u64),
|
.alignment = @alignOf(u64),
|
||||||
.size = @sizeOf(u64),
|
.length = @sizeOf(u64),
|
||||||
});
|
});
|
||||||
|
|
||||||
try testing.expect(block_memory != null);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const block_memory_address = @ptrToInt(block_memory);
|
const block_memory_address = @ptrToInt(block_memory);
|
||||||
|
|
||||||
|
@ -220,11 +245,7 @@ test "fixedAllocator" {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deallocation.
|
// Deallocation.
|
||||||
try testing.expect(allocator.call(.{
|
allocator.dealloc(block_memory);
|
||||||
.existing = block_memory,
|
|
||||||
.alignment = 0,
|
|
||||||
.size = 0,
|
|
||||||
}) == null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -234,15 +255,23 @@ test "fixedAllocator" {
|
||||||
/// referenced by `fixed_stack` until it is full.
|
/// referenced by `fixed_stack` until it is full.
|
||||||
///
|
///
|
||||||
pub fn fixedWriter(fixed_stack: *Fixed(u8)) io.Writer {
|
pub fn fixedWriter(fixed_stack: *Fixed(u8)) io.Writer {
|
||||||
return io.Writer.fromClosure(fixed_stack, struct {
|
const FixedStack = @TypeOf(fixed_stack.*);
|
||||||
fn write(stack: *Fixed(u8), buffer: []const u8) usize {
|
|
||||||
stack.pushAll(buffer) catch |err| switch (err) {
|
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,
|
error.BufferOverflow => return 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
return buffer.len;
|
return buffer.len;
|
||||||
}
|
}
|
||||||
}.write);
|
}, fixed_stack));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "fixedWriter" {
|
test "fixedWriter" {
|
||||||
|
@ -250,6 +279,8 @@ test "fixedWriter" {
|
||||||
var sequence_stack = Fixed(u8){.buffer = &buffer};
|
var sequence_stack = Fixed(u8){.buffer = &buffer};
|
||||||
const sequence_data = [_]u8{8, 16, 32, 64};
|
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));
|
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,
|
maybe_next_index: ?usize = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Errors that may occur during initialization of a hash table.
|
||||||
|
///
|
||||||
|
pub const InitError = io.AllocationError;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Hash table type.
|
/// 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.
|
/// 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;
|
const capacity = 4;
|
||||||
|
|
||||||
return Self{
|
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
|
/// [InsertError.KeyExists] occurs when an insertion was attempted on a table with a matching key
|
||||||
/// already present.
|
/// already present.
|
||||||
///
|
///
|
||||||
pub const InsertError = io.MakeError || error {
|
pub const InsertError = io.AllocationError || error {
|
||||||
KeyExists,
|
KeyExists,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ const testing = @import("./testing.zig");
|
||||||
/// [PrintError.WriteFailure] occurs when the underlying [io.Writer] implementation failed to write
|
/// [PrintError.WriteFailure] occurs when the underlying [io.Writer] implementation failed to write
|
||||||
/// the entirety of a the requested print operation.
|
/// the entirety of a the requested print operation.
|
||||||
///
|
///
|
||||||
pub const PrintError = error {
|
pub const PrintError = io.AccessError || error {
|
||||||
WriteFailure,
|
WriteFailure,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ pub fn printInt(writer: io.Writer, radix: Radix, value: anytype) PrintError!void
|
||||||
if (value == 0) {
|
if (value == 0) {
|
||||||
const zero = "0";
|
const zero = "0";
|
||||||
|
|
||||||
if (writer.call(zero) != zero.len) return error.WriteFailure;
|
if ((try writer.write(zero)) != zero.len) return error.WriteFailure;
|
||||||
} else {
|
} else {
|
||||||
// Big enough to hold the hexadecimal representation of the integer type, which is
|
// Big enough to hold the hexadecimal representation of the integer type, which is
|
||||||
// the largest number format accomodated for in [Radix].
|
// 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|
|
for (buffer[0 .. (buffer_count / 2)]) |_, i|
|
||||||
io.swap(u8, &buffer[i], &buffer[buffer_count - i - 1]);
|
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;
|
return error.WriteFailure;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,22 +13,19 @@ pub fn main() anyerror!void {
|
||||||
/// Runs the game engine.
|
/// Runs the game engine.
|
||||||
///
|
///
|
||||||
fn runEngine(app: *sys.App, graphics: *sys.Graphics) anyerror!void {
|
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();
|
defer file_reader.close();
|
||||||
|
|
||||||
const file_size = try file_reader.size();
|
const file_size = (try app.data.query(path)).length;
|
||||||
const allocator = gpa.allocator();
|
const allocator = sys.threadSafeAllocator();
|
||||||
const buffer = try allocator.alloc(u8, file_size);
|
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);
|
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");
|
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,
|
signature: [signature_magic.len]u8 = signature_magic,
|
||||||
path: sys.Path = sys.Path.empty,
|
path: sys.Path = sys.Path.empty,
|
||||||
data_size: u64 = 0,
|
data_offset: u64 = 0,
|
||||||
data_head: u64 = 0,
|
data_length: u64 = 0,
|
||||||
padding: [232]u8 = [_]u8{0} ** 232,
|
padding: [232]u8 = [_]u8{0} ** 232,
|
||||||
|
|
||||||
comptime {
|
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
|
/// [FindError.ArchiveUnsupported] occurs when trying to read a file that does not follow an Oar
|
||||||
/// of the file and its size.
|
/// archive format considered valid by this implemenatation.
|
||||||
///
|
///
|
||||||
pub const Entry = struct {
|
/// [FindError.EntryNotFound] occurs when the queried entry was not found in the archive file.
|
||||||
head: u64,
|
///
|
||||||
size: u64,
|
pub const FindError = core.io.AccessError || error {
|
||||||
|
|
||||||
///
|
|
||||||
/// [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,
|
|
||||||
ArchiveUnsupported,
|
ArchiveUnsupported,
|
||||||
};
|
EntryNotFound,
|
||||||
|
|
||||||
///
|
|
||||||
/// 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)]);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -106,8 +36,7 @@ const Header = extern struct {
|
||||||
signature: [signature_magic.len]u8 = signature_magic,
|
signature: [signature_magic.len]u8 = signature_magic,
|
||||||
revision: u8 = revision_magic,
|
revision: u8 = revision_magic,
|
||||||
entry_count: u32 = 0,
|
entry_count: u32 = 0,
|
||||||
entry_head: u64 = 0,
|
padding: [502]u8 = [_]u8{0} ** 502,
|
||||||
padding: [496]u8 = [_]u8{0} ** 496,
|
|
||||||
|
|
||||||
comptime {
|
comptime {
|
||||||
const size = @sizeOf(@This());
|
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;
|
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 FileStatus = struct {
|
||||||
pub const ReadableFile = opaque {
|
length: u64,
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
kayomn marked this conversation as resolved
|
|||||||
/// [Error.FileInaccessible] is a generic catch-all for a [FileAccess] reference no longer pointing
|
/// Interface for working with bi-directional, streamable resources accessed through a file-system.
|
||||||
/// to a file or the file becomming invalid for whatever reason.
|
|
||||||
///
|
///
|
||||||
pub const FileError = error {
|
pub const FileReader = struct {
|
||||||
FileInaccessible,
|
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) {
|
pub const FileSystem = union(enum) {
|
||||||
native: []const u8,
|
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 -
|
/// [AccessError.FileNotFound] occurs when a queried file could not be found on the file-system
|
||||||
/// from physical to virtual errors - these are all encapsulated by the API as general
|
/// by the process. This may mean the file does not exist, however it may also mean that the
|
||||||
/// [OpenError.FileNotFound] errors.
|
/// process does not have sufficient rights to read it.
|
||||||
///
|
///
|
||||||
/// When a given [FileSystem] does not support a specified [OpenMode],
|
/// [AccessError.FileSystemfailure] denotes a file-system implementation-specific failure to
|
||||||
/// [OpenError.ModeUnsupported] is used to inform the consuming code that another [OpenMode]
|
/// access resources has occured and therefore cannot proceed to access the file.
|
||||||
/// should be tried or, if no mode other is suitable, that the resource is effectively
|
|
||||||
/// unavailable.
|
|
||||||
///
|
///
|
||||||
/// If the number of known [FileAccess] handles has been exhausted, [OpenError.OutOfFiles] is
|
pub const AccessError = error {
|
||||||
/// used to communicate this.
|
|
||||||
///
|
|
||||||
pub const OpenError = error {
|
|
||||||
FileNotFound,
|
FileNotFound,
|
||||||
ModeUnsupported,
|
FileSystemFailure,
|
||||||
OutOfFiles,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
/// 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
|
pub fn openFileReader(file_system: FileSystem, path: Path) AccessError!FileReader {
|
||||||
/// [OpenError] if it failed.
|
switch (file_system) {
|
||||||
///
|
.archive => |archive| {
|
||||||
pub fn openRead(file_system: *const FileSystem, path: Path) OpenError!*ReadableFile {
|
const archive_reader = try archive.file_system.openFileReader(archive.path);
|
||||||
switch (file_system.*) {
|
|
||||||
.archive_file => |archive_file| {
|
|
||||||
const entry = oar.Entry.find(archive_file, path) catch return error.FileNotFound;
|
|
||||||
|
|
||||||
_ = entry;
|
errdefer archive_reader.close();
|
||||||
// TODO: Alloc file context.
|
|
||||||
|
|
||||||
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| {
|
.native => |native| {
|
||||||
|
@ -239,8 +301,124 @@ pub const FileSystem = union(enum) {
|
||||||
|
|
||||||
ext.SDL_ClearError();
|
ext.SDL_ClearError();
|
||||||
|
|
||||||
return @ptrCast(*ReadableFile, ext.SDL_RWFromFile(&path_buffer, "rb")
|
const rw_ops =
|
||||||
orelse return error.FileNotFound);
|
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 {
|
pub fn compare(this: Path, that: Path) isize {
|
||||||
return core.io.compareBytes(this.buffer[0 ..this.length], that.buffer[0 .. that.length]);
|
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 {
|
pub fn threadSafeAllocator() core.io.Allocator {
|
||||||
// TODO: Add leak detection.
|
const io = core.io;
|
||||||
return .{
|
|
||||||
.bound = .{
|
return io.Allocator.wrap(@as(*opaque {
|
||||||
.alloc = ext.SDL_alloc,
|
const Self = @This();
|
||||||
.dealloc = ext.SDL_free,
|
|
||||||
},
|
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,
|
pub fn display(comptime Error: anytype,
|
||||||
comptime run: fn (*App, *Graphics) callconv(.Async) Error!void) (RunError || Error)!void {
|
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;
|
const user_prefix = ext.SDL_GetPrefPath("ona", "ona") orelse return error.InitFailure;
|
||||||
|
|
||||||
defer ext.SDL_free(user_prefix);
|
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{
|
var app = App{
|
||||||
.user = .{.native = std.mem.sliceTo(user_prefix, 0)},
|
.user = .{.native = std.mem.sliceTo(user_prefix, 0)},
|
||||||
|
|
||||||
|
@ -480,17 +667,13 @@ pub fn display(comptime Error: anytype,
|
||||||
return error.InitFailure;
|
return error.InitFailure;
|
||||||
},
|
},
|
||||||
|
|
||||||
.data = .{
|
.data = .{.archive = .{
|
||||||
.archive_file = cwd.openRead(try Path.from(&.{"./data.oar"})) catch {
|
.file_system = &cwd,
|
||||||
ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to load ./data.oar");
|
.path = try Path.from(&.{"./data.oar"}),
|
||||||
|
}},
|
||||||
return error.InitFailure;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
app.data.archive_file.close();
|
|
||||||
ext.SDL_DestroySemaphore(app.message_semaphore);
|
ext.SDL_DestroySemaphore(app.message_semaphore);
|
||||||
ext.SDL_DestroyMutex(app.message_mutex);
|
ext.SDL_DestroyMutex(app.message_mutex);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Missing documentation comment.