diff --git a/src/main.zig b/src/main.zig index f894182..05ac72f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -27,20 +27,20 @@ fn run(app: *sys.AppContext, graphics: *sys.GraphicsContext) anyerror!void { defer _ = gpa.deinit(); { - const file_access = try (try app.data().joinedPath(&.{"ona.lua"})).open(app, .readonly); + const file_access = try (try app.data().joinedPath(&.{"ona.lua"})).open(.readonly); - defer file_access.close(app); + defer file_access.close(); - const file_size = try file_access.queryLength(app); + const file_size = try file_access.queryLength(); const allocator = gpa.allocator(); const buffer = try allocator.alloc(u8, file_size); defer allocator.free(buffer); - if ((try file_access.read(app, buffer)) != file_size) + if ((try file_access.read(buffer)) != file_size) return error.ScriptLoadFailure; - sys.Log.debug.write(app, buffer); + sys.Log.debug.write(buffer); } while (graphics.poll()) |_| { diff --git a/src/meta.zig b/src/meta.zig new file mode 100644 index 0000000..9fa2c48 --- /dev/null +++ b/src/meta.zig @@ -0,0 +1,10 @@ +/// +/// Returns the return type of the function type `Fn`. +/// +pub fn FnReturn(comptime Fn: type) type { + const type_info = @typeInfo(Fn); + + if (type_info != .Fn) @compileError("`Fn` must be a function type"); + + return type_info.Fn.return_type orelse void; +} diff --git a/src/sys.zig b/src/sys.zig index a31e086..edaf8db 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -5,6 +5,7 @@ const ext = @cImport({ const io = @import("./io.zig"); const math = @import("./math.zig"); const mem = @import("./mem.zig"); +const meta = @import("./meta.zig"); const oar = @import("./oar.zig"); const stack = @import("./stack.zig"); const std = @import("std"); @@ -18,7 +19,6 @@ pub const AppContext = opaque { /// const Message = struct { next: ?*Message = null, - frame: anyframe, kind: union(enum) { quit, @@ -26,6 +26,7 @@ pub const AppContext = opaque { task: struct { data: *anyopaque, action: fn (*anyopaque) void, + frame: anyframe, }, }, }; @@ -85,12 +86,9 @@ pub const AppContext = opaque { /// processes quit and waits for them to do so before freeing any resources. /// fn deinit(implementation: *Implementation) void { - var message = Message{ - .frame = @frame(), - .kind = .quit, - }; + var message = Message{.kind = .quit}; - @ptrCast(*AppContext, implementation).schedule(&message); + implementation.enqueue(&message); { var status = @as(c_int, 0); @@ -108,13 +106,37 @@ pub const AppContext = opaque { ext.SDL_DestroySemaphore(implementation.message_semaphore); } + /// + /// Enqueues `message` to the message processor of `implementation` to be processed at a + /// later, non-deterministic point in time. + /// + fn enqueue(implementation: *Implementation, message: *Message) void { + { + // TODO: Error check these. + _ = ext.SDL_LockMutex(implementation.message_mutex); + + defer _ = ext.SDL_UnlockMutex(implementation.message_mutex); + + if (implementation.messages) |messages| { + messages.next = message; + } else { + implementation.messages = message; + } + } + + // TODO: Error check this. + _ = ext.SDL_SemPost(implementation.message_semaphore); + } + /// /// Initializes a new [Implemenation] with `data_archive_path` as the read-only data archive /// to read from and `user_path_prefix` as the native writable user data directory. /// /// Returns the created [Implementation] value on success or [InitError] on failure. /// - fn init(data_archive_path: []const u8, user_path_prefix: []const u8) InitError!Implementation { + fn init(data_archive_path: []const u8, + user_path_prefix: []const u8) InitError!Implementation { + return Implementation{ .message_semaphore = ext.SDL_CreateSemaphore(0) orelse return error.OutOfSemaphores, .message_mutex = ext.SDL_CreateMutex() orelse return error.OutOfMutexes, @@ -132,26 +154,31 @@ pub const AppContext = opaque { /// occured. /// fn processTasks(userdata: ?*anyopaque) callconv(.C) c_int { - const implementation = Implementation.cast(@ptrCast(*AppContext, userdata orelse unreachable)); + const implementation = Implementation.cast( + @ptrCast(*AppContext, userdata orelse unreachable)); while (true) { + // TODO: Error check these. + _ = ext.SDL_SemWait(implementation.message_semaphore); _ = ext.SDL_LockMutex(implementation.message_mutex); defer _ = ext.SDL_UnlockMutex(implementation.message_mutex); while (implementation.messages) |messages| { switch (messages.kind) { - .quit => return 0, - .task => |task| task.action(task.data), - } + .quit => { + return 0; + }, - resume messages.frame; + .task => |task| { + task.action(task.data); + + resume task.frame; + }, + } implementation.messages = messages.next; } - - // TODO: Error check this. - _ = ext.SDL_SemWait(implementation.message_semaphore); } } @@ -183,25 +210,37 @@ pub const AppContext = opaque { } /// - /// Enqueues `message` to the message processor of `app_context` to be processed at a later, non- - /// deterministic point. /// - pub fn schedule(app_context: *AppContext, message: *Message) void { - const implementation = Implementation.cast(app_context); + /// + pub fn schedule(app_context: *AppContext, procedure: anytype, arguments: anytype) meta.FnReturn(@TypeOf(procedure)) { + const Task = struct { + procedure: @TypeOf(procedure), + arguments: *@TypeOf(arguments), + result: meta.FnReturn(@TypeOf(procedure)), - // TODO: Error check these. - _ = ext.SDL_LockMutex(implementation.message_mutex); + const Task = @This(); - defer _ = ext.SDL_UnlockMutex(implementation.message_mutex); + fn process(userdata: *anyopaque) void { + const task = @ptrCast(*Task, @alignCast(@alignOf(Task), userdata)); - if (implementation.messages) |messages| { - messages.next = message; - } else { - implementation.messages = message; - } + task.result = @call(.{}, task.procedure, task.arguments.*); + } + }; - // TODO: Error check this. - _ = ext.SDL_SemPost(implementation.message_semaphore); + var task = Task{ + .procedure = procedure, + .arguments = &arguments, + }; + + var message = AppContext.Message{ + .kind = .{.task = .{ + .data = &task, + .action = Task.process, + .frame = @frame(), + }}, + }; + + suspend Implementation.cast(app_context).enqueue(&message); } /// @@ -232,39 +271,15 @@ pub const FileAccess = opaque { } /// - /// Close the file referenced by `file_access`, invalidating the reference to it and releasing - /// any associated resources. + /// Close the file referenced by `file_access` on the main thread, invalidating the reference to + /// it and releasing any associated resources. /// /// Freeing an invalid `file_access` has no effect on the file and logs a warning over the /// wasted effort. /// - pub fn close(file_access: *FileAccess, app_context: *AppContext) void { - const Task = struct { - file_access: *FileAccess, - - const Task = @This(); - - fn process(data: *anyopaque) void { - const task = @ptrCast(*Task, @alignCast(@alignOf(Task), data)); - - if (ext.SDL_RWclose(task.file_access.asRwOps()) != 0) - ext.SDL_LogWarn(ext.SDL_LOG_CATEGORY_APPLICATION, - "Closed an invalid file reference"); - } - }; - - var task = Task{.file_access = file_access}; - - var message = AppContext.Message{ - .frame = @frame(), - - .kind = .{.task = .{ - .data = &task, - .action = Task.process, - }}, - }; - - suspend app_context.schedule(&message); + pub fn close(file_access: *FileAccess) void { + if (ext.SDL_RWclose(file_access.asRwOps()) != 0) + ext.SDL_LogWarn(ext.SDL_LOG_CATEGORY_APPLICATION, "Closed an invalid file reference"); } /// @@ -273,47 +288,14 @@ pub const FileAccess = opaque { /// Returns the number of bytes into the file that the cursor is relative to its beginning or a /// [Error] on failure. /// - pub fn queryCursor(file_access: *FileAccess, app_context: *AppContext) Error!u64 { - const Task = struct { - file_access: *FileAccess, - result: Error!u64, + pub fn queryCursor(file_access: *FileAccess) Error!u64 { + ext.SDL_ClearError(); - const Task = @This(); + const sought = ext.SDL_RWtell(file_access.asRwOps()); - fn process(data: *anyopaque) void { - const task = @ptrCast(*Task, @alignCast(@alignOf(Task), data)); + if (sought < 0) return error.FileInaccessible; - ext.SDL_ClearError(); - - const sought = ext.SDL_RWtell(task.file_access.asRwOps()); - - if (sought < 0) { - task.result = error.FileInaccessible; - - return; - } - - task.result = @intCast(u64, sought); - } - }; - - var task = Task{ - .file_access = file_access, - .result = error.FileInaccessible, - }; - - var message = AppContext.Message{ - .frame = @frame(), - - .kind = .{.task = .{ - .data = &task, - .action = Task.process, - }}, - }; - - suspend app_context.schedule(&message); - - return task.result; + return @intCast(u64, sought); } /// @@ -322,156 +304,51 @@ pub const FileAccess = opaque { /// Returns the current length of the file at the time of the operation or a [Error] if the file /// failed to be queried. /// - pub fn queryLength(file_access: *FileAccess, app_context: *AppContext) Error!u64 { - const Task = struct { - file_access: *FileAccess, - result: Error!usize, + pub fn queryLength(file_access: *FileAccess) Error!u64 { + ext.SDL_ClearError(); - const Task = @This(); + const sought = ext.SDL_RWsize(file_access.asRwOps()); - fn process(data: *anyopaque) void { - const task = @ptrCast(*Task, @alignCast(@alignOf(Task), data)); + if (sought < 0) return error.FileInaccessible; - ext.SDL_ClearError(); - - const sought = ext.SDL_RWsize(task.file_access.asRwOps()); - - if (sought < 0) { - task.result = error.FileInaccessible; - - return; - } - - task.result = @intCast(u64, sought); - } - }; - - var task = Task{ - .file_access = file_access, - .result = error.FileInaccessible, - }; - - var message = AppContext.Message{ - .frame = @frame(), - - .kind = .{.task = .{ - .data = &task, - .action = Task.process, - }}, - }; - - suspend app_context.schedule(&message); - - return task.result; + return @intCast(u64, sought); } /// - /// Attempts to read `file_access` from the its current position into `buffer`, while using - /// `app_context` as the execution context. + /// Attempts to read `file_access` from the its current position into `buffer`. /// /// Returns the number of bytes that were available to be read, otherwise an [Error] on failure. /// - pub fn read(file_access: *FileAccess, app_context: *AppContext, buffer: []u8) Error!usize { - const Task = struct { - file_access: *FileAccess, - buffer: []u8, - result: Error!usize, + pub fn read(file_access: *FileAccess, buffer: []u8) Error!usize { + ext.SDL_ClearError(); - const Task = @This(); + const buffer_read = + ext.SDL_RWread(file_access.asRwOps(), buffer.ptr, @sizeOf(u8), buffer.len); - fn process(data: *anyopaque) void { - const task = @ptrCast(*Task, @alignCast(@alignOf(Task), data)); + if ((buffer_read == 0) and (ext.SDL_GetError() != null)) return error.FileInaccessible; - ext.SDL_ClearError(); - - const buffer_read = ext.SDL_RWread(task.file_access.asRwOps(), - task.buffer.ptr, @sizeOf(u8), task.buffer.len); - - if ((buffer_read == 0) and (ext.SDL_GetError() != null)) { - task.result = error.FileInaccessible; - - return; - } - - task.result = buffer_read; - } - }; - - var task = Task{ - .file_access = file_access, - .buffer = buffer, - .result = error.FileInaccessible, - }; - - var message = AppContext.Message{ - .frame = @frame(), - - .kind = .{.task = .{ - .data = &task, - .action = Task.process, - }}, - }; - - suspend app_context.schedule(&message); - - return task.result; + return buffer_read; } /// - /// Attempts to seek `file_access` from the beginning of the file to `cursor` bytes while using - /// `app_context` as the execution context. + /// Attempts to seek `file_access` from the beginning of the file to `cursor` bytes. /// /// Returns [Error] on failure. /// - pub fn seek(file_access: *FileAccess, app_context: *AppContext, cursor: u64) Error!void { - const Task = struct { - file_access: *FileAccess, - cursor: u64, - result: Error!void, + pub fn seek(file_access: *FileAccess, cursor: u64) Error!void { + var to_seek = cursor; - const Task = @This(); + while (to_seek != 0) { + const sought = @intCast(i64, std.math.min(to_seek, std.math.maxInt(i64))); - fn process(data: *anyopaque) void { - const task = @ptrCast(*Task, @alignCast(@alignOf(Task), data)); + ext.SDL_ClearError(); - if (task.cursor >= std.math.maxInt(i64)) { - task.result = error.OutOfRange; + if (ext.SDL_RWseek(file_access.asRwOps(), sought, ext.RW_SEEK_CUR) < 0) + return error.FileInaccessible; - return; - } - - ext.SDL_ClearError(); - - if (ext.SDL_RWseek(task.file_access.asRwOps(), - @intCast(i64, task.cursor), ext.RW_SEEK_SET) < 0) { - - task.result = error.FileInaccessible; - - return; - } - - task.result = {}; - } - }; - - var task = Task{ - .file_access = file_access, - .cursor = cursor, - .result = error.FileInaccessible, - }; - - var message = AppContext.Message{ - .frame = @frame(), - - .kind = .{.task = .{ - .data = &task, - .action = Task.process, - }}, - }; - - suspend app_context.schedule(&message); - - return task.result; + // Cannot be less than zero because it is derived from `read`. + to_seek -= @intCast(u64, sought); + } } /// @@ -480,45 +357,11 @@ pub const FileAccess = opaque { /// /// Returns [Error] on failure. /// - pub fn seekToEnd(file_access: *FileAccess, app_context: *AppContext) Error!void { - const Task = struct { - file_access: *FileAccess, - result: Error!void, + pub fn seekToEnd(file_access: *FileAccess) Error!void { + ext.SDL_ClearError(); - const Task = @This(); - - fn process(data: *anyopaque) void { - const task = @ptrCast(*Task, @alignCast(@alignOf(Task), data)); - - ext.SDL_ClearError(); - - if (ext.SDL_RWseek(task.file_access.asRwOps(), 0, ext.RW_SEEK_END) < 0) { - task.result = error.FileInaccessible; - - return; - } - - task.result = {}; - } - }; - - var task = Task{ - .file_access = file_access, - .result = error.FileInaccessible, - }; - - var message = AppContext.Message{ - .frame = @frame(), - - .kind = .{.task = .{ - .data = &task, - .action = Task.process, - }}, - }; - - suspend app_context.schedule(&message); - - return task.result; + if (ext.SDL_RWseek(file_access.asRwOps(), 0, ext.RW_SEEK_END) < 0) + return error.FileInaccessible; } }; @@ -595,167 +438,72 @@ pub const FileSystem = union(enum) { /// /// Attempts to open the file identified by `path` with `mode` as the mode for opening the - /// file and `app_context` as the execution context. + /// file. /// /// Returns a [FileAccess] reference that provides access to the file referenced by `path` /// or a [OpenError] if it failed. /// - pub fn open(path: Path, app_context: *AppContext, mode: OpenMode) OpenError!*FileAccess { - const Task = struct { - path: *const FileSystem.Path, - app_context: *AppContext, - mode: OpenMode, - result: OpenError!*FileAccess, + pub fn open(path: Path, mode: OpenMode) OpenError!*FileAccess { + switch (path.file_system.*) { + .archive => |archive| { + if (archive.len == 0) return error.FileNotFound; - const Task = @This(); + if (mode != .readonly) return error.ModeUnsupported; - fn process(data: *anyopaque) void { - const task = @ptrCast(*Task, @alignCast(@alignOf(Task), data)); + var path_buffer = std.mem.zeroes([4096]u8); - switch (task.path.file_system.*) { - .archive => |archive| { - if (archive.len == 0) { - task.result = error.FileNotFound; + if (archive.len >= path_buffer.len) return error.FileNotFound; - return; - } + std.mem.copy(u8, path_buffer[0 ..], archive); - if (task.mode != .readonly) { - task.result = error.ModeUnsupported; + const file_access = @ptrCast(*FileAccess, ext.SDL_RWFromFile( + &path_buffer, "rb") orelse return error.FileNotFound); - return; - } + while (true) { + var entry = std.mem.zeroes(oar.Entry); + const entry_buffer = std.mem.asBytes(&entry); - var path_buffer = std.mem.zeroes([4096]u8); + if ((file_access.read(entry_buffer) catch return + error.FileNotFound) != entry_buffer.len) return error.FileNotFound; - if (archive.len >= path_buffer.len) { - task.result = error.FileNotFound; + if (std.mem.eql(u8, entry.name_buffer[0 .. entry. + name_length], path.buffer[0 .. path.length])) { - return; - } + return file_access; + } - std.mem.copy(u8, path_buffer[0 ..], archive); - - ext.SDL_ClearError(); - - const rw_ops = ext.SDL_RWFromFile(&path_buffer, "rb") orelse { - task.result = error.FileNotFound; - - return; - }; - - while (true) { - var entry = std.mem.zeroes(oar.Entry); - const entry_buffer = std.mem.asBytes(&entry); - - ext.SDL_ClearError(); - - if (ext.SDL_RWread(rw_ops, entry_buffer, @sizeOf(u8), - entry_buffer.len) != entry_buffer.len) { - - task.result = error.FileNotFound; - - return; - } - - if (std.mem.eql(u8, entry.name_buffer[0 .. entry.name_length], - task.path.buffer[0 .. task.path.length])) { - - task.result = @ptrCast(*FileAccess, rw_ops); - - return; - } - - { - var to_read = math.roundUp(u64, - entry.file_size, entry_buffer.len); - - while (to_read != 0) { - const read = @intCast(i64, std.math.min( - to_read, std.math.maxInt(i64))); - - ext.SDL_ClearError(); - - if (ext.SDL_RWseek(rw_ops, read, ext.RW_SEEK_CUR) < 0) { - task.result = error.FileNotFound; - - return; - } - - // Cannot be less than zero because it is derived from - // `read`. - to_read -= @intCast(u64, read); - } - } - } - }, - - .native => |native| { - if (native.len == 0) { - task.result = error.FileNotFound; - - return; - } - - var path_buffer = std.mem.zeroes([4096]u8); - const seperator = '/'; - - const seperator_length = - @boolToInt(native[native.len - 1] != seperator); - - if ((native.len + seperator_length + - task.path.length) >= path_buffer.len) { - - task.result = error.FileNotFound; - - return; - } - - std.mem.copy(u8, path_buffer[0 ..], native); - - if (seperator_length != 0) - path_buffer[native.len] = seperator; - - std.mem.copy(u8, path_buffer[native.len .. path_buffer.len], - task.path.buffer[0 .. task.path.length]); - - ext.SDL_ClearError(); - - task.result = @ptrCast(*FileAccess, ext.SDL_RWFromFile( - &path_buffer, switch (task.mode) { - .readonly => "rb", - .overwrite => "wb", - .append => "ab", - }) orelse { - - task.result = error.FileNotFound; - - return; - }); - }, + file_access.seek(math.roundUp(u64, entry.file_size, + entry_buffer.len)) catch return error.FileNotFound; } - } - }; + }, - var task = Task{ - .mode = mode, - .path = &path, - .app_context = app_context, - .result = error.FileNotFound, - }; + .native => |native| { + if (native.len == 0) return error.FileNotFound; - var message = AppContext.Message{ - .frame = @frame(), + var path_buffer = std.mem.zeroes([4096]u8); + const seperator = '/'; + const seperator_length = @boolToInt(native[native.len - 1] != seperator); - .kind = .{.task = .{ - .data = &task, - .action = Task.process, - }}, - }; + if ((native.len + seperator_length + path.length) >= path_buffer.len) + return error.FileNotFound; - suspend app_context.schedule(&message); + std.mem.copy(u8, path_buffer[0 ..], native); - return task.result; + if (seperator_length != 0) + path_buffer[native.len] = seperator; + + std.mem.copy(u8, path_buffer[native.len .. path_buffer.len], + path.buffer[0 .. path.length]); + + ext.SDL_ClearError(); + + return @ptrCast(*FileAccess, ext.SDL_RWFromFile(&path_buffer, switch (mode) { + .readonly => "rb", + .overwrite => "wb", + .append => "ab", + }) orelse return error.FileNotFound); + }, + } } }; @@ -884,39 +632,11 @@ pub const Log = enum(u32) { warning = ext.SDL_LOG_PRIORITY_WARN, /// - /// Writes `utf8_message` as the log kind identified by `log` with `app_context` as the execution - /// context. + /// Writes `utf8_message` as the log kind identified by `log`. /// - pub fn write(log: Log, app_context: *AppContext, utf8_message: []const u8) void { - const Task = struct { - log: Log, - utf8_message: []const u8, - - const Task = @This(); - - fn process(data: *anyopaque) void { - const task = @ptrCast(*Task, @alignCast(@alignOf(Task), data)); - - ext.SDL_LogMessage(ext.SDL_LOG_CATEGORY_APPLICATION, @enumToInt(task.log), - "%.*s", task.utf8_message.len, task.utf8_message.ptr); - } - }; - - var task = Task{ - .log = log, - .utf8_message = utf8_message, - }; - - var message = AppContext.Message{ - .frame = @frame(), - - .kind = .{.task = .{ - .data = &task, - .action = Task.process, - }} - }; - - suspend app_context.schedule(&message); + pub fn write(log: Log, utf8_message: []const u8) void { + ext.SDL_LogMessage(ext.SDL_LOG_CATEGORY_APPLICATION, + @enumToInt(log), "%.*s", utf8_message.len, utf8_message.ptr); } };