const ext = @cImport({ @cInclude("SDL2/SDL.h"); }); const io = @import("./io.zig"); const mem = @import("./mem.zig"); const stack = @import("./stack.zig"); const std = @import("std"); /// /// /// pub const EventLoop = packed struct { current_request: ?*Request = null, /// /// [OpenError.NotFound] is used as a catch-all for any hardware or software-specific reason for /// failing to open a given file. This includes file-system restrictions surrounding a specific /// file as well as it simply not existing. /// /// [OpenError.OutOfFiles] occurs when there are no more resources available to open further /// files. As a result, some open files must be closed before more may be opened. /// pub const OpenError = error { NotFound, OutOfFiles, }; /// /// Indicates what kind of access the consumer logic has to a file. /// /// [OpenMode.read] is for reading from an existing file from the start. /// /// [OpenMode.overwrite] is for deleting the contents of a file, or creating an empty one if no /// such file exists, and writing to it from the start. /// /// [OpenMode.append] is for writing additional contents to a file, creating an empty one if no /// such file exists, on the end of whatever it already contains. /// pub const OpenMode = enum { readonly, overwrite, append, }; const Request = struct { next: ?*Request = null, frame: anyframe, message: union(enum) { close: struct { file: *FileAccess, }, open: struct { file_system: FileSystem, path: *const Path, mode: OpenMode, file: OpenError!*FileAccess = error.OutOfFiles, }, }, }; const max_files = 512; /// /// Asynchronously closes `file_access` via `event_loop`. /// /// *Note* that `file_access` must have been opened by `event_loop` for it to be closed by it, /// otherwise it will cause undefined behavior. /// pub fn close(event_loop: *EventLoop, file_access: *FileAccess) void { var request = Request{ .frame = @frame(), .message = .{.close = @ptrCast(*ext.SDL_RWops, file_access)}, }; suspend if (event_loop.current_request) |current_request| { current_request.next = &request; } else { event_loop.current_request = &request; }; } /// /// /// pub fn open(event_loop: *EventLoop, file_system: FileSystem, path: Path, mode: OpenMode) OpenError!*FileAccess { var request = Request{ .frame = @frame(), .message = .{ .open = .{ .file_system = file_system, .path = &path, .mode = mode, }, }, }; suspend if (event_loop.current_request) |current_request| { current_request.next = &request; } else { event_loop.current_request = &request; }; return @ptrCast(*FileAccess, try request.message.open.file); } /// /// /// pub fn tick(event_loop: *EventLoop) void { while (event_loop.current_request) |request| { switch (request.message) { .close => |*close| { // Swallow file close errors. _ = ext.SDL_RWclose(@ptrCast(*ext.SDL_RWops, @alignCast(@alignOf(ext.SDL_RWops), close.file))); }, .open => |*open| { switch (open.file_system) { .data => { var zeroed_path_buffer = std.mem.zeroes([4096]u8); var zeroed_path = stack.Fixed(u8){.buffer = &zeroed_path_buffer}; zeroed_path.push('.') catch continue; _ = open.path.write(zeroed_path.asWriter()); zeroed_path.push(0) catch continue; // TODO: Access TAR file. }, .user => { var zeroed_path_buffer = std.mem.zeroes([4096]u8); var zeroed_path = stack.Fixed(u8){.buffer = &zeroed_path_buffer}; zeroed_path.pushAll("/home/kayomn/.local/share") catch continue; _ = open.path.write(zeroed_path.asWriter()); zeroed_path.push(0) catch continue; open.file = @ptrCast(?*FileAccess, ext.SDL_RWFromFile(&zeroed_path_buffer, switch (open.mode) { .readonly => "rb", .overwrite => "wb", .append => "ab", })) orelse error.NotFound; }, } }, } resume request.frame; event_loop.current_request = request.next; } } }; /// /// /// pub const FileAccess = opaque {}; /// /// 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 /// [Error.Inaccessible] errors. /// pub const FileError = error { Inaccessible, }; /// /// /// pub const FileSystem = enum { data, user, }; /// /// Platform-agnostic mechanism for accessing files on any of the virtual file-systems supported by /// Ona. /// pub const Path = struct { length: u16, buffer: [max]u8, /// /// /// pub const Error = error { TooLong, }; /// /// Returns `true` if the length of `path` is empty, otherwise `false`. /// pub fn isEmpty(path: Path) bool { return (path.length == 0); } /// /// Returns `true` if `this` is equal to `that`, otherwise `false`. /// pub fn equals(this: Path, that: Path) bool { return std.mem.eql(u8, this.buffer[0 .. this.length], that.buffer[0 .. that.length]); } /// /// The maximum possible byte-length of a [Path]. /// /// Note that paths are encoded using UTF-8, meaning that a character may be bigger than one /// byte. Because of this, it is not safe to asume that a path may hold [max] individual /// characters. /// pub const max = 1000; /// /// /// pub fn parseJoins(path: Path, joins: []const []const u8) Error!Path { _ = path; _ = joins; return Path{ .buffer = std.mem.zeroes([max]u8), .length = 0 }; } /// /// /// pub fn write(path: Path, writer: io.Writer) bool { return (writer.write(path.buffer[0 .. path.length]) == path.length); } /// /// /// pub const root = Path{ .buffer = [_]u8{'/'} ** max, .length = 1, }; };