diff --git a/src/main.zig b/src/main.zig index 1cd1926..df78f01 100644 --- a/src/main.zig +++ b/src/main.zig @@ -21,30 +21,29 @@ test { _ = sys; } -fn run(event_loop: *sys.EventLoop, graphics: *sys.GraphicsContext) anyerror!void { +fn run(ev: *sys.EventLoop, fs: *const sys.FileSystem, gr: *sys.GraphicsContext) anyerror!void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); { - const file_access = try event_loop.open(.readonly, - try sys.FileSystem.data.joinedPath(&.{"data", "ona.lua"})); + const file_access = try ev.open(.readonly, try fs.data.joinedPath(&.{"ona.lua"})); - defer event_loop.close(file_access); + defer ev.close(file_access); - const file_size = try file_access.size(event_loop); + const file_size = try file_access.size(ev); const allocator = gpa.allocator(); const buffer = try allocator.alloc(u8, file_size); defer allocator.free(buffer); - if ((try event_loop.readFile(file_access, buffer)) != file_size) + if ((try ev.readFile(file_access, buffer)) != file_size) return error.ScriptLoadFailure; - event_loop.log(.debug, buffer); + ev.log(.debug, buffer); } - while (graphics.poll()) |_| { - graphics.present(); + while (gr.poll()) |_| { + gr.present(); } } diff --git a/src/sys.zig b/src/sys.zig index cd0cadd..973a81c 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -61,7 +61,6 @@ pub const EventLoop = opaque { /// Internal state of the event loop hidden from the API consumer. /// const Implementation = struct { - user_prefix: []u8, file_system_semaphore: *ext.SDL_sem, file_system_mutex: *ext.SDL_mutex, file_system_thread: ?*ext.SDL_Thread, @@ -71,6 +70,8 @@ pub const EventLoop = opaque { /// /// const InitError = error { + DataFileNotFound, + DataFileInvalid, OutOfSemaphores, OutOfMutexes, OutOfMemory, @@ -147,22 +148,19 @@ pub const EventLoop = opaque { /// /// fn init() InitError!Implementation { + const data_file_access = @ptrCast(*FileAccess, + ext.SDL_RWFromFile("./data.tar", "r+") orelse return error.DataFileNotFound); + return Implementation{ - .user_prefix = create_pref_path: { - const path = ext.SDL_GetPrefPath("ona", "ona") orelse { - ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, - "Failed to load user path"); - - return error.OutOfMemory; - }; - - break: create_pref_path path[0 .. std.mem.len(path)]; + .data_archive = tar.Archive.init(data_file_access) catch |err| switch (err) { + error.Invalid, error.Inaccessible => return error.DataFileInvalid, }, .file_system_semaphore = ext.SDL_CreateSemaphore(0) orelse return error.OutOfSemaphores, .file_system_mutex = ext.SDL_CreateMutex() orelse return error.OutOfMutexes, + .data_file = data_file_access, .file_system_thread = null, }; } @@ -181,51 +179,12 @@ pub const EventLoop = opaque { while (implementation.file_system_messages) |messages| { switch (messages.request) { .exit => return 0, + .log => |*log_request| .log(log_request.kind, log_request.message), - .log => |*log_request| ext.SDL_LogMessage(ext.SDL_LOG_CATEGORY_APPLICATION, - @enumToInt(log_request.kind), log_request.message.ptr), + .open => |*open_request| open_request.result = + .open(open_request.mode, open_request.file_system_path), - .open => |*open_request| { - switch (open_request.file_system_path.file_system) { - .data => { - // TODO: Implement - open_request.result = error.NotFound; - }, - - .user => { - var path_buffer = std.mem.zeroes([4096]u8); - var path = stack.Fixed(u8){.buffer = path_buffer[0 .. ]}; - - path.pushAll(implementation.user_prefix) catch { - open_request.result = error.NotFound; - - continue; - }; - - if (!open_request.file_system_path.write(path.writer())) { - open_request.result = error.NotFound; - - continue; - } - - if (ext.SDL_RWFromFile(&path_buffer, switch (open_request.mode) { - .readonly => "rb", - .overwrite => "wb", - .append => "ab", - })) |rw_ops| { - open_request.result = @ptrCast(*FileAccess, rw_ops); - } else { - open_request.result = error.NotFound; - } - }, - } - }, - - .close => |*close_request| { - // TODO: Use this result somehow. - _ = ext.SDL_RWclose(@ptrCast(*ext.SDL_RWops, @alignCast( - @alignOf(ext.SDL_RWops), close_request.file_access))); - }, + .close => |*close_request| .close(close_request.file_access), .read_file => |read_request| { // TODO: Implement. @@ -270,63 +229,6 @@ pub const EventLoop = opaque { } }; - /// - /// [LogKind.info] represents a log message which is purely informative and does not indicate - /// any kind of issue. - /// - /// [LogKind.debug] represents a log message which is purely for debugging purposes and will - /// only occurs in debug builds. - /// - /// [LogKind.warning] represents a log message which is a warning about a issue that does not - /// break anything important but is not ideal. - /// - pub const LogKind = enum(u32) { - info = ext.SDL_LOG_PRIORITY_INFO, - debug = ext.SDL_LOG_PRIORITY_DEBUG, - warning = ext.SDL_LOG_PRIORITY_WARN, - }; - - /// - /// [OpenError.NotFound] is a catch-all for when a file could not be located to be opened. This - /// may be as simple as it doesn't exist or the because the underlying file-system will not / - /// cannot give access to it at this time. - /// - pub const OpenError = error { - NotFound, - }; - - /// - /// [OpenMode.readonly] indicates that an existing file is opened in a read-only state, - /// disallowing write access. - /// - /// [OpenMode.overwrite] indicates that an empty file has been created or an existing file has - /// been completely overwritten into. - /// - /// [OpenMode.append] indicates that an existing file that has been opened for reading from and - /// writing to on the end of existing data. - /// - pub const OpenMode = enum { - readonly, - overwrite, - append, - }; - - /// - /// [SeekOrigin.head] indicates that a seek operation will seek from the offset origin of the - /// file beginning, or "head". - /// - /// [SeekOrigin.tail] indicates that a seek operation will seek from the offset origin of the - /// file end, or "tail". - /// - /// [SeekOrigin.cursor] indicates that a seek operation will seek from the current position of - /// the file cursor. - /// - pub const SeekOrigin = enum { - head, - tail, - cursor, - }; - /// /// Closes access to the file referenced by `file_access` via `event_loop`. /// @@ -488,15 +390,15 @@ pub const FileError = error { /// Platform-agnostic mechanism for working with an abstraction of the underlying file-system(s) /// available to the application in a sandboxed environment. /// -pub const FileSystem = enum { - data, - user, +pub const FileSystem = struct { + data: Root, + user: Root, /// /// Platform-agnostic mechanism for referencing files and directories on a [FileSystem]. /// pub const Path = struct { - file_system: FileSystem, + root: *const Root, length: u16, buffer: [max]u8, @@ -521,7 +423,7 @@ pub const FileSystem = enum { /// byte. Because of this, it is not safe to asume that a path may hold [max] individual /// characters. /// - pub const max = 1000; + pub const max = 512; /// /// @@ -540,52 +442,58 @@ pub const FileSystem = enum { }; /// - /// Creates and returns a [Path] value in the file system to the location specified by the - /// joining of the `sequences` path values. /// - pub fn joinedPath(file_system: FileSystem, sequences: []const []const u8) PathError!Path { - var path = Path{ - .file_system = file_system, - .buffer = std.mem.zeroes([Path.max]u8), - .length = 0, - }; + /// + pub const Root = struct { + prefix: []const u8, - if (sequences.len != 0) { - const last_sequence_index = sequences.len - 1; + /// + /// + /// + pub fn joinedPath(root: Root, sequences: []const []const u8) PathError!Path { + var path = Path{ + .root = root, + .buffer = std.mem.zeroes([Path.max]u8), + .length = 0, + }; - for (sequences) |sequence, index| if (sequence.len != 0) { - var components = mem.Spliterator(u8){ - .source = sequence, - .delimiter = "/", - }; + if (sequences.len != 0) { + const last_sequence_index = sequences.len - 1; - while (components.next()) |component| if (component.len != 0) { - for (component) |byte| { - if (path.length == Path.max) return error.TooLong; + for (sequences) |sequence, index| if (sequence.len != 0) { + var components = mem.Spliterator(u8){ + .source = sequence, + .delimiter = "/", + }; - path.buffer[path.length] = byte; - path.length += 1; - } + while (components.next()) |component| if (component.len != 0) { + for (component) |byte| { + if (path.length == Path.max) return error.TooLong; - if (components.hasNext()) { + path.buffer[path.length] = byte; + path.length += 1; + } + + if (components.hasNext()) { + if (path.length == Path.max) return error.TooLong; + + path.buffer[path.length] = '/'; + path.length += 1; + } + }; + + if (index < last_sequence_index) { if (path.length == Path.max) return error.TooLong; path.buffer[path.length] = '/'; path.length += 1; } }; + } - if (index < last_sequence_index) { - if (path.length == Path.max) return error.TooLong; - - path.buffer[path.length] = '/'; - path.length += 1; - } - }; + return path; } - - return path; - } + }; }; /// @@ -629,13 +537,125 @@ pub const GraphicsContext = opaque { /// /// pub fn GraphicsRunner(comptime Errors: type) type { - return fn (*EventLoop, *GraphicsContext) callconv(.Async) Errors!void; + return fn (*EventLoop, *FileSystem, *GraphicsContext) callconv(.Async) Errors!void; +} + +/// +/// [LogKind.info] represents a log message which is purely informative and does not indicate +/// any kind of issue. +/// +/// [LogKind.debug] represents a log message which is purely for debugging purposes and will +/// only occurs in debug builds. +/// +/// [LogKind.warning] represents a log message which is a warning about a issue that does not +/// break anything important but is not ideal. +/// +pub const LogKind = enum(u32) { + info = ext.SDL_LOG_PRIORITY_INFO, + debug = ext.SDL_LOG_PRIORITY_DEBUG, + warning = ext.SDL_LOG_PRIORITY_WARN, +}; + +/// +/// [OpenError.NotFound] is a catch-all for when a file could not be located to be opened. This +/// may be as simple as it doesn't exist or the because the underlying file-system will not / +/// cannot give access to it at this time. +/// +pub const OpenError = error { + NotFound, +}; + +/// +/// [OpenMode.readonly] indicates that an existing file is opened in a read-only state, +/// disallowing write access. +/// +/// [OpenMode.overwrite] indicates that an empty file has been created or an existing file has +/// been completely overwritten into. +/// +/// [OpenMode.append] indicates that an existing file that has been opened for reading from and +/// writing to on the end of existing data. +/// +pub const OpenMode = enum { + readonly, + overwrite, + append, +}; + +/// +/// +/// +pub const RunError = error { + InitFailure, + AlreadyRunning, +}; + +/// +/// [SeekOrigin.head] indicates that a seek operation will seek from the offset origin of the +/// file beginning, or "head". +/// +/// [SeekOrigin.tail] indicates that a seek operation will seek from the offset origin of the +/// file end, or "tail". +/// +/// [SeekOrigin.cursor] indicates that a seek operation will seek from the current position of +/// the file cursor. +/// +pub const SeekOrigin = enum { + head, + tail, + cursor, +}; + +/// +/// +/// +pub fn close(file_access: *FileAccess) void { + if (!ext.SDL_RWclose(@ptrCast(*ext.SDL_RWops, + @alignCast(@alignOf(ext.SDL_RWops), file_access)))) { + + ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, + "Failed to close file - may have been already closed"); + } } /// /// /// -pub fn runGraphics(comptime Errors: anytype, comptime run: GraphicsRunner(Errors)) Errors!void { +pub fn log(kind: LogKind, message: []const u8) void { + ext.SDL_LogMessage(ext.SDL_LOG_CATEGORY_APPLICATION, + @enumToInt(kind), "%.*s", message.len, message.ptr); +} + +/// +/// +/// +pub fn open(mode: OpenMode, file_system_path: FileSystem.Path) OpenError!*FileAccess { + switch (file_system_path.file_system) { + .data => { + // TODO: Implement + return error.NotFound; + }, + + .user => { + var path_buffer = std.mem.zeroes([4096]u8); + var path = stack.Fixed(u8){.buffer = path_buffer[0 .. ]}; + + path.pushAll("/home/kayomn/.local/share") catch return error.NotFound; + + if (file_system_path.write(path.writer())) return error.NotFound; + + return @ptrCast(*FileAccess, ext.SDL_RWFromFile(&path_buffer, switch (mode) { + .readonly => "rb", + .overwrite => "wb", + .append => "ab", + })) orelse error.NotFound; + }, + } +} + +/// +/// +/// +pub fn runGraphics(comptime Errors: anytype, comptime run: GraphicsRunner(Errors)) (RunError || Errors)!void { if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) { ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize runtime"); @@ -669,6 +689,25 @@ pub fn runGraphics(comptime Errors: anytype, comptime run: GraphicsRunner(Errors defer ext.SDL_DestroyRenderer(renderer); + var file_system = FileSystem{ + .user = .{.prefix = create_pref_path: { + const path = ext.SDL_GetPrefPath("ona", "ona") orelse { + ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, + "Failed to load user path"); + + return error.InitFailure; + }; + + break: create_pref_path path[0 .. std.mem.len(path)]; + }}, + + .data = .{.prefix = "./"}, + }; + + defer { + ext.SDL_free(file_system.user.prefix); + } + var event_loop = EventLoop.Implementation.init() catch |err| { switch (err) { error.OutOfMemory => ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION,