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 = opaque { const Implementation = struct { current_message: ?*Message, }; /// /// /// const Message = struct { next: ?*Message, frame: anyframe, request: union(enum) { open: struct { mode: OpenMode, path: *const FileSystem.Path, }, }, response: union(enum) { none, file_access: *FileAccess, file_error: FileError, } = .none, }; /// /// /// pub const OpenError = error { NotFound, }; /// /// /// pub const OpenMode = enum { readonly, overwrite, append, }; /// /// /// pub fn close(event_loop: *EventLoop, file_access: *FileAccess) void { var message = Message{ .frame = @frame(), .request = .{ .close = .{ .file_access = file_access, }, }, }; suspend event_loop.enqueueMessage(&message); } /// /// /// fn enqueueMessage(event_loop: *EventLoop, message: *Message) void { const implementation = @ptrCast(*Implementation, @alignCast(@alignOf(Implementation), event_loop)); if (implementation.current_message) |current_message| { current_message.next = message; } else { implementation.current_message = message; } } pub fn log(event_loop: *EventLoop, message: []const u8) void { // TODO: Implement. _ = event_loop; _ = message; } /// /// /// pub fn open(event_loop: *EventLoop, mode: OpenMode, path: FileSystem.Path) OpenError!*FileAccess { var message = Message{ .frame = @frame(), .request = .{ .open = .{ .mode = mode, .path = &path, }, }, }; suspend event_loop.enqueueMessage(&message); switch (message.response) { .file_access => |file_access| return file_access, .open_error => |open_error| return open_error, else => unreachable, } } /// /// /// pub fn readFile(event_loop: *EventLoop, file_access: *FileAccess, buffer: []const u8) FileError!usize { var message = Message{ .frame = @frame(), .request = .{ .read_file = .{ .file_access = file_access, .buffer = buffer, }, }, }; suspend event_loop.enqueueMessage(&message); switch (message.response) { .size => |size| return size, .file_error => |file_error| return file_error, else => unreachable, } } }; /// /// /// pub const FileAccess = opaque { /// /// Scans the number of bytes in the file referenced by `file_access` via `event_loop`, returing /// its byte size or a [FileError] if it failed. /// pub fn size(file_access: *FileAccess, event_loop: *EventLoop) FileError!usize { // Save cursor to return to it later. const origin_cursor = try event_loop.tellFile(file_access); try event_loop.seekFile(file_access, .tail, 0); const ending_cursor = try event_loop.tellFile(file_access); // Return to original cursor. try event_loop.seekFile(file_access, .head, origin_cursor); return ending_cursor; } }; /// /// 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 referencing files and directories on a [FileSystem] /// pub const Path = struct { file_system: FileSystem, length: u16, buffer: [max]u8, /// /// 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 write(path: Path, writer: io.Writer) bool { return (writer.write(path.buffer[0 .. path.length]) == path.length); } }; /// /// [PathError.TooLong] occurs when creating a path that is greater than the maximum size **in /// bytes**. /// pub const PathError = error { TooLong, }; /// /// Creates and returns a [Path] value in the file system to the location specified by the /// joining of the `component_groups` path values. /// pub fn pathJoin(file_system: FileSystem, component_groups: []const []const u8) PathError!Path { var path = Path{ .file_system = file_system, .buffer = std.mem.zeroes([Path.max]u8), .length = 0, }; for (component_groups) |component_group| if (component_group.len != 0) { var components = mem.Spliterator(u8){ .source = component_group, .delimiter = "/", }; while (components.next()) |component| if (component.len != 0) { for (component) |byte| { if (path.length == Path.max) return error.TooLong; path.buffer[path.length] = byte; path.length += 1; } if (path.length == Path.max) return error.TooLong; path.buffer[path.length] = '/'; path.length += 1; }; }; return path; } }; /// /// /// pub const GraphicsContext = opaque { /// /// /// pub const Event = struct { }; const Implementation = struct { event: Event, }; /// /// /// pub fn poll(graphics_context: *GraphicsContext) ?*const Event { // TODO: Implement. _ = graphics_context; return null; } /// /// /// pub fn present(graphics_context: *GraphicsContext) void { // TODO: Implement; _ = graphics_context; } }; /// /// /// pub fn GraphicsRunner(comptime Errors: type) type { return fn (*EventLoop, *GraphicsContext) Errors!void; } /// /// /// pub fn runGraphics(comptime Errors: anytype, run: GraphicsRunner(Errors)) Errors!void { if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) { ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize SDL2 runtime"); return error.InitFailure; } defer ext.SDL_Quit(); const pref_path = 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)]; }; defer ext.SDL_free(pref_path.ptr); const window = create_window: { const pos = ext.SDL_WINDOWPOS_UNDEFINED; var flags = @as(u32, 0); break: create_window ext.SDL_CreateWindow("Ona", pos, pos, 640, 480, flags) orelse { ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to load SDL2 window"); return error.InitFailure; }; }; defer ext.SDL_DestroyWindow(window); const renderer = create_renderer: { var flags = @as(u32, 0); break: create_renderer ext.SDL_CreateRenderer(window, -1, flags) orelse { ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to load SDL2 renderer"); return error.InitFailure; }; }; defer ext.SDL_DestroyRenderer(renderer); var event_loop = EventLoop.Implementation{ .current_message = null, }; var graphics_context = GraphicsContext.Implementation{ .event = .{ }, }; return run(@ptrCast(*EventLoop, &event_loop), @ptrCast(*GraphicsContext, &graphics_context)); }