Application Context Implementation #4
| @ -15,7 +15,7 @@ fn run(app: *sys.AppContext, graphics: *sys.GraphicsContext) anyerror!void { | ||||
|     defer _ = gpa.deinit(); | ||||
| 
 | ||||
|     { | ||||
|         var file_access = try (try app.data().joinedPath(&.{"ona.lua"})).open(.readonly); | ||||
|         var file_access = try app.data().open(try sys.Path.joined(&.{"ona.lua"}), .readonly); | ||||
| 
 | ||||
|         defer file_access.close(); | ||||
| 
 | ||||
|  | ||||
| @ -274,305 +274,160 @@ pub const FileSystem = union(enum) { | ||||
|     archive: Archive, | ||||
| 
 | ||||
|     /// | ||||
|     /// Platform-agnostic mechanism for referencing files and directories on a [FileSystem]. | ||||
|     /// 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 [OpenError.FileNotFound] errors. | ||||
|     /// | ||||
|     pub const Path = struct { | ||||
|         file_system: *FileSystem, | ||||
|         path: oar.Path, | ||||
|     /// When a given [FileSystem] does not support a specified [OpenMode], | ||||
|     /// [OpenError.ModeUnsupported] is used to inform the consuming code that another [OpenMode] | ||||
|     /// 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 used to communicate this. | ||||
|     /// | ||||
|     pub const OpenError = error { | ||||
|         FileNotFound, | ||||
|         ModeUnsupported, | ||||
|         OutOfFiles, | ||||
|     }; | ||||
| 
 | ||||
|         /// | ||||
|         /// 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 [OpenError.FileNotFound] errors. | ||||
|         /// | ||||
|         /// When a given [FileSystem] does not support a specified [OpenMode], | ||||
|         /// [OpenError.ModeUnsupported] is used to inform the consuming code that another [OpenMode] | ||||
|         /// 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 used to communicate this. | ||||
|         /// | ||||
|         pub const OpenError = error { | ||||
|             FileNotFound, | ||||
|             ModeUnsupported, | ||||
|             OutOfFiles, | ||||
|         }; | ||||
|     /// | ||||
|     /// [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, | ||||
|     }; | ||||
| 
 | ||||
|         /// | ||||
|         /// [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, | ||||
|         }; | ||||
|     /// | ||||
|     /// Attempts to open the file identified by `path` with `mode` as the mode for opening the file. | ||||
|     /// | ||||
|     /// Returns a [FileAccess] reference that provides access to the file referenced by `path`or a | ||||
|     /// [OpenError] if it failed. | ||||
|     /// | ||||
|     pub fn open(file_system: *FileSystem, path: Path, mode: OpenMode) OpenError!ona.io.FileAccess { | ||||
|         switch (file_system.*) { | ||||
|             .archive => |*archive| { | ||||
|                 if (mode != .readonly) return error.ModeUnsupported; | ||||
| 
 | ||||
|         /// | ||||
|         /// 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 (this.file_system == that.file_system) and | ||||
|                 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 = 255; | ||||
| 
 | ||||
|         /// | ||||
|         /// Attempts to open the file identified by `path` with `mode` as the mode for opening the | ||||
|         /// 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, mode: OpenMode) OpenError!ona.io.FileAccess { | ||||
|             switch (path.file_system.*) { | ||||
|                 .archive => |*archive| { | ||||
|                     if (mode != .readonly) return error.ModeUnsupported; | ||||
| 
 | ||||
|                     const FileAccess = ona.io.FileAccess; | ||||
| 
 | ||||
|                     for (archive.entry_table) |*entry| if (entry.owner == null) { | ||||
|                         const Implementation = struct { | ||||
|                             fn archiveEntryCast(context: *anyopaque) *Archive.Entry { | ||||
|                                 return @ptrCast(*Archive.Entry, @alignCast( | ||||
|                                     @alignOf(Archive.Entry), context)); | ||||
|                             } | ||||
| 
 | ||||
|                             fn close(context: *anyopaque) void { | ||||
|                                 archiveEntryCast(context).owner = null; | ||||
|                             } | ||||
| 
 | ||||
|                             fn queryCursor(context: *anyopaque) FileAccess.Error!u64 { | ||||
|                                 const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                                 if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
|                                 return archive_entry.cursor; | ||||
|                             } | ||||
| 
 | ||||
|                             fn queryLength(context: *anyopaque) FileAccess.Error!u64 { | ||||
|                                 const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                                 if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
|                                 return archive_entry.header.file_size; | ||||
|                             } | ||||
| 
 | ||||
|                             fn read(context: *anyopaque, buffer: []u8) FileAccess.Error!usize { | ||||
|                                 const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                                 const file_access = archive_entry.owner orelse | ||||
|                                     return error.FileInaccessible; | ||||
| 
 | ||||
|                                 if (archive_entry.cursor >= archive_entry.header.file_size) | ||||
|                                     return error.FileInaccessible; | ||||
| 
 | ||||
|                                 try file_access.seek(archive_entry.header.absolute_offset); | ||||
| 
 | ||||
|                                 return file_access.read(buffer[0 .. std.math.min( | ||||
|                                     buffer.len, archive_entry.header.file_size)]); | ||||
|                             } | ||||
| 
 | ||||
|                             fn seek(context: *anyopaque, cursor: usize) FileAccess.Error!void { | ||||
|                                 const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                                 if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
|                                 archive_entry.cursor = cursor; | ||||
|                             } | ||||
| 
 | ||||
|                             fn seekToEnd(context: *anyopaque) FileAccess.Error!void { | ||||
|                                 const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                                 if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
|                                 archive_entry.cursor = archive_entry.header.file_size; | ||||
|                             } | ||||
| 
 | ||||
|                             fn skip(context: *anyopaque, offset: i64) FileAccess.Error!void { | ||||
|                                 const math = std.math; | ||||
|                                 const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                                 if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
|                                 if (offset < 0) { | ||||
|                                     archive_entry.cursor = math.max(0, | ||||
|                                         archive_entry.cursor - math.absCast(offset)); | ||||
|                                 } else { | ||||
|                                     archive_entry.cursor += @intCast(u64, offset); | ||||
|                                 } | ||||
|                             } | ||||
|                         }; | ||||
| 
 | ||||
|                         if (archive.index_cache.lookup(path.path)) |index| { | ||||
|                             archive.file_access.seek(index) catch return error.FileNotFound; | ||||
| 
 | ||||
|                             entry.* = .{ | ||||
|                                 .owner = &archive.file_access, | ||||
|                                 .cursor = 0, | ||||
| 
 | ||||
|                                 .header = (oar.Entry.next(archive.file_access) catch return error.FileNotFound) orelse { | ||||
|                                     // Remove cannot fail if lookup succeeded. | ||||
|                                     std.debug.assert(archive.index_cache.remove(path.path) != null); | ||||
| 
 | ||||
|                                     return error.FileNotFound; | ||||
|                                 }, | ||||
|                             }; | ||||
|                         } else { | ||||
|                             while (oar.Entry.next(archive.file_access) catch return error.FileNotFound) |entry_header| { | ||||
|                                 if (entry.header.path.equals(path.path)) | ||||
|                                     entry.* = .{ | ||||
|                                         .owner = &archive.file_access, | ||||
|                                         .cursor = 0, | ||||
|                                         .header = entry_header, | ||||
|                                     }; | ||||
|                             } | ||||
| 
 | ||||
|                             return error.FileNotFound; | ||||
|                         } | ||||
| 
 | ||||
|                         return FileAccess{ | ||||
|                             .context = entry, | ||||
| 
 | ||||
|                             .implementation = &.{ | ||||
|                                 .close = Implementation.close, | ||||
|                                 .queryCursor = Implementation.queryCursor, | ||||
|                                 .queryLength = Implementation.queryLength, | ||||
|                                 .read = Implementation.read, | ||||
|                                 .seek = Implementation.seek, | ||||
|                                 .seekToEnd = Implementation.seekToEnd, | ||||
|                                 .skip = Implementation.skip, | ||||
|                             }, | ||||
|                         }; | ||||
|                     }; | ||||
| 
 | ||||
|                     return error.OutOfFiles; | ||||
|                 }, | ||||
| 
 | ||||
|                 .native => |native| { | ||||
|                     if (native.len == 0) return error.FileNotFound; | ||||
| 
 | ||||
|                     var path_buffer = std.mem.zeroes([4096]u8); | ||||
|                     const seperator_length = @boolToInt(native[native.len - 1] != seperator); | ||||
| 
 | ||||
|                     if ((native.len + seperator_length + path.path.length) >= | ||||
|                         path_buffer.len) return error.FileNotFound; | ||||
| 
 | ||||
|                     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], path.path.buffer[0 .. path.path.length]); | ||||
| 
 | ||||
|                     ext.SDL_ClearError(); | ||||
| 
 | ||||
|                     const FileAccess = ona.io.FileAccess; | ||||
|                 const FileAccess = ona.io.FileAccess; | ||||
| 
 | ||||
|                 for (archive.entry_table) |*entry| if (entry.owner == null) { | ||||
|                     const Implementation = struct { | ||||
|                         fn rwOpsCast(context: *anyopaque) *ext.SDL_RWops { | ||||
|                             return @ptrCast(*ext.SDL_RWops, @alignCast( | ||||
|                                 @alignOf(ext.SDL_RWops), context)); | ||||
|                         fn archiveEntryCast(context: *anyopaque) *Archive.Entry { | ||||
|                             return @ptrCast(*Archive.Entry, @alignCast( | ||||
|                                 @alignOf(Archive.Entry), context)); | ||||
|                         } | ||||
| 
 | ||||
|                         fn close(context: *anyopaque) void { | ||||
|                             ext.SDL_ClearError(); | ||||
| 
 | ||||
|                             if (ext.SDL_RWclose(rwOpsCast(context)) != 0) | ||||
|                                 ext.SDL_LogWarn(ext.SDL_LOG_CATEGORY_APPLICATION, ext.SDL_GetError()); | ||||
|                             archiveEntryCast(context).owner = null; | ||||
|                         } | ||||
| 
 | ||||
|                         fn queryCursor(context: *anyopaque) FileAccess.Error!u64 { | ||||
|                             ext.SDL_ClearError(); | ||||
|                             const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                             const sought = ext.SDL_RWtell(rwOpsCast(context)); | ||||
|                             if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
|                             if (sought < 0) return error.FileInaccessible; | ||||
| 
 | ||||
|                             return @intCast(u64, sought); | ||||
|                             return archive_entry.cursor; | ||||
|                         } | ||||
| 
 | ||||
|                         fn queryLength(context: *anyopaque) FileAccess.Error!u64 { | ||||
|                             ext.SDL_ClearError(); | ||||
|                             const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                             const sought = ext.SDL_RWsize(rwOpsCast(context)); | ||||
|                             if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
|                             if (sought < 0) return error.FileInaccessible; | ||||
| 
 | ||||
|                             return @intCast(u64, sought); | ||||
|                             return archive_entry.header.file_size; | ||||
|                         } | ||||
| 
 | ||||
|                         fn read(context: *anyopaque, buffer: []u8) FileAccess.Error!usize { | ||||
|                             ext.SDL_ClearError(); | ||||
|                             const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                             const buffer_read = ext.SDL_RWread(rwOpsCast( | ||||
|                                 context), buffer.ptr, @sizeOf(u8), buffer.len); | ||||
| 
 | ||||
|                             if ((buffer_read == 0) and (ext.SDL_GetError() != null)) | ||||
|                             const file_access = archive_entry.owner orelse | ||||
|                                 return error.FileInaccessible; | ||||
| 
 | ||||
|                             return buffer_read; | ||||
|                             if (archive_entry.cursor >= archive_entry.header.file_size) | ||||
|                                 return error.FileInaccessible; | ||||
| 
 | ||||
|                             try file_access.seek(archive_entry.header.absolute_offset); | ||||
| 
 | ||||
|                             return file_access.read(buffer[0 .. std.math.min( | ||||
|                                 buffer.len, archive_entry.header.file_size)]); | ||||
|                         } | ||||
| 
 | ||||
|                         fn seek(context: *anyopaque, cursor: usize) FileAccess.Error!void { | ||||
|                             var to_seek = cursor; | ||||
|                             const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                             while (to_seek != 0) { | ||||
|                                 const math = std.math; | ||||
|                                 const sought = math.min(to_seek, math.maxInt(i64)); | ||||
|                             if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
|                                 ext.SDL_ClearError(); | ||||
| 
 | ||||
|                                 if (ext.SDL_RWseek(rwOpsCast(context), @intCast(i64, sought), | ||||
|                                     ext.RW_SEEK_CUR) < 0) return error.FileInaccessible; | ||||
| 
 | ||||
|                                 to_seek -= sought; | ||||
|                             } | ||||
|                             archive_entry.cursor = cursor; | ||||
|                         } | ||||
| 
 | ||||
|                         fn seekToEnd(context: *anyopaque) FileAccess.Error!void { | ||||
|                             ext.SDL_ClearError(); | ||||
|                             const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                             if (ext.SDL_RWseek(rwOpsCast(context), 0, ext.RW_SEEK_END) < 0) | ||||
|                                 return error.FileInaccessible; | ||||
|                             if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
|                             archive_entry.cursor = archive_entry.header.file_size; | ||||
|                         } | ||||
| 
 | ||||
|                         fn skip(context: *anyopaque, offset: i64) FileAccess.Error!void { | ||||
|                             ext.SDL_ClearError(); | ||||
|                             const math = std.math; | ||||
|                             const archive_entry = archiveEntryCast(context); | ||||
| 
 | ||||
|                             if (ext.SDL_RWseek(rwOpsCast(context), offset, ext.RW_SEEK_SET) < 0) | ||||
|                                 return error.FileInaccessible; | ||||
|                             if (archive_entry.owner == null) return error.FileInaccessible; | ||||
| 
 | ||||
|                             if (offset < 0) { | ||||
|                                 archive_entry.cursor = math.max(0, | ||||
|                                     archive_entry.cursor - math.absCast(offset)); | ||||
|                             } else { | ||||
|                                 archive_entry.cursor += @intCast(u64, offset); | ||||
|                             } | ||||
|                         } | ||||
|                     }; | ||||
| 
 | ||||
|                     const Header = oar.Entry; | ||||
| 
 | ||||
|                     if (archive.index_cache.lookup(path)) |index| { | ||||
|                         archive.file_access.seek(index) catch return error.FileNotFound; | ||||
| 
 | ||||
|                         entry.* = .{ | ||||
|                             .owner = &archive.file_access, | ||||
|                             .cursor = 0, | ||||
| 
 | ||||
|                             .header = (Header.next(archive.file_access) catch | ||||
|                                 return error.FileNotFound) orelse { | ||||
| 
 | ||||
|                                 // Remove cannot fail if lookup succeeded. | ||||
|                                 std.debug.assert(archive.index_cache.remove(path) != null); | ||||
| 
 | ||||
|                                 return error.FileNotFound; | ||||
|                             }, | ||||
|                         }; | ||||
|                     } else { | ||||
|                         while (Header.next(archive.file_access) catch | ||||
|                             return error.FileNotFound) |entry_header| { | ||||
| 
 | ||||
|                             if (entry.header.path.equals(path)) entry.* = .{ | ||||
|                                 .owner = &archive.file_access, | ||||
|                                 .cursor = 0, | ||||
|                                 .header = entry_header, | ||||
|                             }; | ||||
|                         } | ||||
| 
 | ||||
|                         return error.FileNotFound; | ||||
|                     } | ||||
| 
 | ||||
|                     return FileAccess{ | ||||
|                         .context = ext.SDL_RWFromFile(&path_buffer, switch (mode) { | ||||
|                             .readonly => "rb", | ||||
|                             .overwrite => "wb", | ||||
|                             .append => "ab", | ||||
|                         }) orelse return error.FileNotFound, | ||||
|                         .context = entry, | ||||
| 
 | ||||
|                         .implementation = &.{ | ||||
|                             .close = Implementation.close, | ||||
| @ -584,69 +439,126 @@ pub const FileSystem = union(enum) { | ||||
|                             .skip = Implementation.skip, | ||||
|                         }, | ||||
|                     }; | ||||
|                 }, | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         pub const seperator = '/'; | ||||
|     }; | ||||
| 
 | ||||
|     /// | ||||
|     /// [PathError.TooLong] occurs when creating a path that is greater than the maximum size **in | ||||
|     /// bytes**. | ||||
|     /// | ||||
|     pub const PathError = error { | ||||
|         TooLong, | ||||
|     }; | ||||
| 
 | ||||
|     /// | ||||
|     /// Attempts to create a [Path] with `file_system` as the file-system root and the path | ||||
|     /// components in `sequences` as a fully qualified path from the root. | ||||
|     /// | ||||
|     /// A [Path] value is returned containing the fully qualified path from the file-system root or | ||||
|     /// a [PathError] if it could not be created. | ||||
|     /// | ||||
|     pub fn joinedPath(file_system: *FileSystem, sequences: []const []const u8) PathError!Path { | ||||
|         var path = Path{ | ||||
|             .file_system = file_system, | ||||
|             .path = oar.Path.empty, | ||||
|         }; | ||||
| 
 | ||||
|         if (sequences.len != 0) { | ||||
|             const last_sequence_index = sequences.len - 1; | ||||
| 
 | ||||
|             for (sequences) |sequence, index| if (sequence.len != 0) { | ||||
|                 var components = ona.io.Spliterator(u8){ | ||||
|                     .source = sequence, | ||||
|                     .delimiter = "/", | ||||
|                 }; | ||||
| 
 | ||||
|                 while (components.next()) |component| if (component.len != 0) { | ||||
|                     for (component) |byte| { | ||||
|                         if (path.path.length == Path.max) return error.TooLong; | ||||
|                 return error.OutOfFiles; | ||||
|             }, | ||||
| 
 | ||||
|                         path.path.buffer[path.path.length] = byte; | ||||
|                         path.path.length += 1; | ||||
|             .native => |native| { | ||||
|                 if (native.len == 0) return error.FileNotFound; | ||||
| 
 | ||||
|                 var path_buffer = std.mem.zeroes([4096]u8); | ||||
|                 const seperator_length = @boolToInt(native[native.len - 1] != oar.Path.seperator); | ||||
| 
 | ||||
|                 if ((native.len + seperator_length + path.length) >= | ||||
|                     path_buffer.len) return error.FileNotFound; | ||||
| 
 | ||||
|                 std.mem.copy(u8, path_buffer[0 ..], native); | ||||
| 
 | ||||
|                 if (seperator_length != 0) path_buffer[native.len] = oar.Path.seperator; | ||||
| 
 | ||||
|                 std.mem.copy(u8, path_buffer[native.len .. path_buffer. | ||||
|                     len], path.buffer[0 .. path.length]); | ||||
| 
 | ||||
|                 const FileAccess = ona.io.FileAccess; | ||||
| 
 | ||||
|                 const Implementation = struct { | ||||
|                     fn rwOpsCast(context: *anyopaque) *ext.SDL_RWops { | ||||
|                         return @ptrCast(*ext.SDL_RWops, @alignCast( | ||||
|                             @alignOf(ext.SDL_RWops), context)); | ||||
|                     } | ||||
| 
 | ||||
|                     if (components.hasNext()) { | ||||
|                         if (path.path.length == Path.max) return error.TooLong; | ||||
|                     fn close(context: *anyopaque) void { | ||||
|                         ext.SDL_ClearError(); | ||||
| 
 | ||||
|                         path.path.buffer[path.path.length] = '/'; | ||||
|                         path.path.length += 1; | ||||
|                         if (ext.SDL_RWclose(rwOpsCast(context)) != 0) | ||||
|                             ext.SDL_LogWarn(ext.SDL_LOG_CATEGORY_APPLICATION, ext.SDL_GetError()); | ||||
|                     } | ||||
| 
 | ||||
|                     fn queryCursor(context: *anyopaque) FileAccess.Error!u64 { | ||||
|                         ext.SDL_ClearError(); | ||||
| 
 | ||||
|                         const sought = ext.SDL_RWtell(rwOpsCast(context)); | ||||
| 
 | ||||
|                         if (sought < 0) return error.FileInaccessible; | ||||
| 
 | ||||
|                         return @intCast(u64, sought); | ||||
|                     } | ||||
| 
 | ||||
|                     fn queryLength(context: *anyopaque) FileAccess.Error!u64 { | ||||
|                         ext.SDL_ClearError(); | ||||
| 
 | ||||
|                         const sought = ext.SDL_RWsize(rwOpsCast(context)); | ||||
| 
 | ||||
|                         if (sought < 0) return error.FileInaccessible; | ||||
| 
 | ||||
|                         return @intCast(u64, sought); | ||||
|                     } | ||||
| 
 | ||||
|                     fn read(context: *anyopaque, buffer: []u8) FileAccess.Error!usize { | ||||
|                         ext.SDL_ClearError(); | ||||
| 
 | ||||
|                         const buffer_read = ext.SDL_RWread(rwOpsCast( | ||||
|                             context), buffer.ptr, @sizeOf(u8), buffer.len); | ||||
| 
 | ||||
|                         if ((buffer_read == 0) and (ext.SDL_GetError() != null)) | ||||
|                             return error.FileInaccessible; | ||||
| 
 | ||||
|                         return buffer_read; | ||||
|                     } | ||||
| 
 | ||||
|                     fn seek(context: *anyopaque, cursor: usize) FileAccess.Error!void { | ||||
|                         var to_seek = cursor; | ||||
| 
 | ||||
|                         while (to_seek != 0) { | ||||
|                             const math = std.math; | ||||
|                             const sought = math.min(to_seek, math.maxInt(i64)); | ||||
| 
 | ||||
|                             ext.SDL_ClearError(); | ||||
| 
 | ||||
|                             if (ext.SDL_RWseek(rwOpsCast(context), @intCast(i64, sought), | ||||
|                                 ext.RW_SEEK_CUR) < 0) return error.FileInaccessible; | ||||
| 
 | ||||
|                             to_seek -= sought; | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     fn seekToEnd(context: *anyopaque) FileAccess.Error!void { | ||||
|                         ext.SDL_ClearError(); | ||||
| 
 | ||||
|                         if (ext.SDL_RWseek(rwOpsCast(context), 0, ext.RW_SEEK_END) < 0) | ||||
|                             return error.FileInaccessible; | ||||
|                     } | ||||
| 
 | ||||
|                     fn skip(context: *anyopaque, offset: i64) FileAccess.Error!void { | ||||
|                         ext.SDL_ClearError(); | ||||
| 
 | ||||
|                         if (ext.SDL_RWseek(rwOpsCast(context), offset, ext.RW_SEEK_SET) < 0) | ||||
|                             return error.FileInaccessible; | ||||
|                     } | ||||
|                 }; | ||||
| 
 | ||||
|                 if (index < last_sequence_index) { | ||||
|                     if (path.path.length == Path.max) return error.TooLong; | ||||
|                 ext.SDL_ClearError(); | ||||
| 
 | ||||
|                     path.path.buffer[path.path.length] = '/'; | ||||
|                     path.path.length += 1; | ||||
|                 } | ||||
|             }; | ||||
|                 return FileAccess{ | ||||
|                     .context = ext.SDL_RWFromFile(&path_buffer, switch (mode) { | ||||
|                         .readonly => "rb", | ||||
|                         .overwrite => "wb", | ||||
|                         .append => "ab", | ||||
|                     }) orelse return error.FileNotFound, | ||||
| 
 | ||||
|                     .implementation = &.{ | ||||
|                         .close = Implementation.close, | ||||
|                         .queryCursor = Implementation.queryCursor, | ||||
|                         .queryLength = Implementation.queryLength, | ||||
|                         .read = Implementation.read, | ||||
|                         .seek = Implementation.seek, | ||||
|                         .seekToEnd = Implementation.seekToEnd, | ||||
|                         .skip = Implementation.skip, | ||||
|                     }, | ||||
|                 }; | ||||
|             }, | ||||
|         } | ||||
| 
 | ||||
|         return path; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| @ -721,6 +633,11 @@ pub const Log = enum(u32) { | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
| /// Path to a file on a [FileSystem]. | ||||
| /// | ||||
| pub const Path = oar.Path; | ||||
| 
 | ||||
| /// | ||||
| /// [RunError.InitFailure] occurs if a necessary resource fails to be acquired or allocated. | ||||
| /// | ||||
| @ -774,7 +691,7 @@ pub fn runGraphics(comptime Error: anytype, | ||||
|     defer ext.SDL_DestroyRenderer(renderer); | ||||
| 
 | ||||
|     var cwd_file_system = FileSystem{.native ="./"}; | ||||
|     var data_access = try (try cwd_file_system.joinedPath(&.{"./data.oar"})).open(.readonly); | ||||
|     var data_access = try cwd_file_system.open(try Path.joined(&.{"./data.oar"}), .readonly); | ||||
| 
 | ||||
|     defer data_access.close(); | ||||
| 
 | ||||
|  | ||||
| @ -55,6 +55,14 @@ pub const Path = extern struct { | ||||
|     buffer: [255]u8, | ||||
|     length: u8, | ||||
| 
 | ||||
|     /// | ||||
|     /// [Error.TooLong] occurs when creating a path that is greater than the maximum path size **in | ||||
|     /// bytes**. | ||||
|     /// | ||||
|     pub const Error = error { | ||||
|         TooLong, | ||||
|     }; | ||||
| 
 | ||||
|     /// | ||||
|     /// An empty [Path] with a length of `0`. | ||||
|     /// | ||||
| @ -74,6 +82,63 @@ pub const Path = extern struct { | ||||
|     pub fn hash(path: Path) usize { | ||||
|         return ona.io.hashBytes(path.buffer[0 .. path.length]); | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// Attempts to create a [Path] with the path components in `sequences` as a fully qualified | ||||
|     /// path from root. | ||||
|     /// | ||||
|     /// A [Path] value is returned containing the fully qualified path from the file-system root or | ||||
|     /// a [Error] if it could not be created. | ||||
|     /// | ||||
|     pub fn joined(sequences: []const []const u8) Error!Path { | ||||
|         var path = empty; | ||||
| 
 | ||||
|         if (sequences.len != 0) { | ||||
|             const last_sequence_index = sequences.len - 1; | ||||
| 
 | ||||
|             for (sequences) |sequence, index| if (sequence.len != 0) { | ||||
|                 var components = ona.io.Spliterator(u8){ | ||||
|                     .source = sequence, | ||||
|                     .delimiter = "/", | ||||
|                 }; | ||||
| 
 | ||||
|                 while (components.next()) |component| if (component.len != 0) { | ||||
|                     for (component) |byte| { | ||||
|                         if (path.length == max) return error.TooLong; | ||||
| 
 | ||||
|                         path.buffer[path.length] = byte; | ||||
|                         path.length += 1; | ||||
|                     } | ||||
| 
 | ||||
|                     if (components.hasNext()) { | ||||
|                         if (path.length == max) return error.TooLong; | ||||
| 
 | ||||
|                         path.buffer[path.length] = '/'; | ||||
|                         path.length += 1; | ||||
|                     } | ||||
|                 }; | ||||
| 
 | ||||
|                 if (index < last_sequence_index) { | ||||
|                     if (path.length == max) return error.TooLong; | ||||
| 
 | ||||
|                     path.buffer[path.length] = '/'; | ||||
|                     path.length += 1; | ||||
|                 } | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         return path; | ||||
|     } | ||||
| 
 | ||||
|     /// | ||||
|     /// Maximum number of **bytes** in a [Path]. | ||||
|     /// | ||||
|     pub const max = 255; | ||||
| 
 | ||||
|     /// | ||||
|     /// Textual separator between components of a [Path]. | ||||
|     /// | ||||
|     pub const seperator = '/'; | ||||
| }; | ||||
| 
 | ||||
| /// | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user