Application Context Implementation #4

Closed
kayomn wants to merge 93 commits from event-loop-dev into main
10 changed files with 198 additions and 143 deletions
Showing only changes of commit 489ece4b7b - Show all commits

View File

@ -1,21 +1,28 @@
const std = @import("std"); const std = @import("std");
///
/// Builds the engine, tools, and dependencies of all.
///
pub fn build(builder: *std.build.Builder) void { pub fn build(builder: *std.build.Builder) void {
const target = builder.standardTargetOptions(.{}); const target = builder.standardTargetOptions(.{});
const mode = builder.standardReleaseOptions(); const mode = builder.standardReleaseOptions();
const ona_pkg = projectPkg("ona", &.{});
// Ona executable. // Engine executable.
{ {
const ona_exe = builder.addExecutable("ona", "./src/main.zig"); const engine_exe = builder.addExecutable("ona", "./src/engine/main.zig");
const oar_pkg = projectPkg("oar", &.{ona_pkg});
ona_exe.setTarget(target); engine_exe.addPackage(oar_pkg);
ona_exe.setBuildMode(mode); engine_exe.addPackage(ona_pkg);
ona_exe.install(); engine_exe.setTarget(target);
ona_exe.addIncludeDir("./ext"); engine_exe.setBuildMode(mode);
ona_exe.linkSystemLibrary("SDL2"); engine_exe.install();
ona_exe.linkLibC(); engine_exe.addIncludeDir("./ext");
engine_exe.linkSystemLibrary("SDL2");
engine_exe.linkLibC();
const run_cmd = ona_exe.run(); const run_cmd = engine_exe.run();
run_cmd.step.dependOn(builder.getInstallStep()); run_cmd.step.dependOn(builder.getInstallStep());
@ -24,6 +31,11 @@ pub fn build(builder: *std.build.Builder) void {
builder.step("run", "Run Ona application").dependOn(&run_cmd.step); builder.step("run", "Run Ona application").dependOn(&run_cmd.step);
} }
// Oar executable.
{
}
// Ona tests. // Ona tests.
{ {
const ona_tests = builder.addTestExe("test", "./src/main.zig"); const ona_tests = builder.addTestExe("test", "./src/main.zig");
@ -33,3 +45,15 @@ pub fn build(builder: *std.build.Builder) void {
builder.step("test", "Run Ona unit tests").dependOn(&ona_tests.step); builder.step("test", "Run Ona unit tests").dependOn(&ona_tests.step);
} }
} }
///
/// Returns a [std.build.Pkg] within the project codebase path at `name` with `dependencies` as its
/// dependencies.
///
fn projectPkg(comptime name: []const u8, dependencies: []const std.build.Pkg) std.build.Pkg {
return std.build.Pkg{
.name = name,
.path = .{.path = "./src/" ++ name ++ "/main.zig"},
.dependencies = dependencies,
};
}

View File

@ -1,9 +1,4 @@
const ext = @cImport({ const ona = @import("ona");
@cInclude("SDL2/SDL.h");
});
const io = @import("./io.zig");
const stack = @import("./stack.zig");
const std = @import("std"); const std = @import("std");
const sys = @import("./sys.zig"); const sys = @import("./sys.zig");
@ -14,13 +9,6 @@ pub fn main() anyerror!void {
return nosuspend await async sys.runGraphics(anyerror, run); return nosuspend await async sys.runGraphics(anyerror, run);
} }
test {
_ = io;
_ = stack;
_ = std;
_ = sys;
}
fn run(app: *sys.AppContext, graphics: *sys.GraphicsContext) anyerror!void { fn run(app: *sys.AppContext, graphics: *sys.GraphicsContext) anyerror!void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
@ -37,8 +25,7 @@ fn run(app: *sys.AppContext, graphics: *sys.GraphicsContext) anyerror!void {
defer allocator.free(buffer); defer allocator.free(buffer);
if ((try file_access.read(buffer)) != file_size) if ((try file_access.read(buffer)) != file_size) return error.ScriptLoadFailure;
return error.ScriptLoadFailure;
sys.Log.debug.write(buffer); sys.Log.debug.write(buffer);
} }
@ -47,3 +34,7 @@ fn run(app: *sys.AppContext, graphics: *sys.GraphicsContext) anyerror!void {
graphics.present(); graphics.present();
} }
} }
test {
_ = sys;
}

View File

@ -2,11 +2,8 @@ const ext = @cImport({
@cInclude("SDL2/SDL.h"); @cInclude("SDL2/SDL.h");
}); });
const io = @import("./io.zig"); const oar = @import("oar");
const mem = @import("./mem.zig"); const ona = @import("ona");
const meta = @import("./meta.zig");
const oar = @import("./oar.zig");
const stack = @import("./stack.zig");
const std = @import("std"); const std = @import("std");
/// ///
@ -131,12 +128,14 @@ pub const AppContext = opaque {
} }
/// ///
/// Initializes a new [Implemenation] with `data_access` as the data archive to read from /// Initializes a new [Implemenation] with `data_archive_file_access` as the data archive to
/// and `user_path_prefix` as the native writable user data directory. /// read from and `user_path_prefix` as the native writable user data directory.
/// ///
/// Returns the created [Implementation] value on success or [InitError] on failure. /// Returns the created [Implementation] value on success or [InitError] on failure.
/// ///
fn init(allocator: std.mem.Allocator, data_access: FileAccess) InitError!Implementation { fn init(allocator: std.mem.Allocator,
data_archive_file_access: ona.io.FileAccess) InitError!Implementation {
const user_path_prefix = ext.SDL_GetPrefPath("ona", "ona") orelse const user_path_prefix = ext.SDL_GetPrefPath("ona", "ona") orelse
return error.OutOfMemory; return error.OutOfMemory;
@ -149,7 +148,7 @@ pub const AppContext = opaque {
.user_path_prefix = user_path_prefix, .user_path_prefix = user_path_prefix,
.data_file_system = .{.archive = .{ .data_file_system = .{.archive = .{
.instance = try oar.Archive.init(allocator, data_access), .instance = try oar.Archive.init(allocator, data_archive_file_access),
}}, }},
.message_thread = null, .message_thread = null,
@ -223,12 +222,12 @@ pub const AppContext = opaque {
/// ///
/// ///
pub fn schedule(app_context: *AppContext, procedure: anytype, pub fn schedule(app_context: *AppContext, procedure: anytype,
arguments: anytype) meta.FnReturn(@TypeOf(procedure)) { arguments: anytype) ona.meta.FnReturn(@TypeOf(procedure)) {
const Task = struct { const Task = struct {
procedure: @TypeOf(procedure), procedure: @TypeOf(procedure),
arguments: *@TypeOf(arguments), arguments: *@TypeOf(arguments),
result: meta.FnReturn(@TypeOf(procedure)), result: ona.meta.FnReturn(@TypeOf(procedure)),
const Task = @This(); const Task = @This();
@ -263,102 +262,6 @@ pub const AppContext = opaque {
} }
}; };
///
/// File-system agnostic abstraction for manipulating a file.
///
pub const FileAccess = struct {
context: *anyopaque,
implementation: *const Implementation,
///
/// Provides a set of implementation-specific behaviors to a [FileAccess] instance.
///
pub const Implementation = struct {
close: fn (*anyopaque) void,
queryCursor: fn (*anyopaque) Error!u64,
queryLength: fn (*anyopaque) Error!u64,
read: fn (*anyopaque, []u8) Error!usize,
seek: fn (*anyopaque, u64) Error!void,
seekToEnd: fn (*anyopaque) Error!void,
skip: fn (*anyopaque, i64) Error!void,
};
///
/// [Error.FileInaccessible] is a generic catch-all for a [FileAccess] reference no longer
/// pointing to a file or the file becomming invalid for whatever reason.
///
pub const Error = error {
FileInaccessible,
};
///
/// 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) void {
return file_access.implementation.close(file_access.context);
}
///
/// Attempts to query the current cursor position for the file referenced by `file_access`.
///
/// 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) Error!u64 {
return file_access.implementation.queryCursor(file_access.context);
}
///
/// Attempts to query the current length for the file referenced by `file_access`.
///
/// 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) Error!u64 {
return file_access.implementation.queryLength(file_access.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, buffer: []u8) Error!usize {
return file_access.implementation.read(file_access.context, buffer);
}
///
/// Attempts to seek `file_access` from the beginning of the file to `cursor` bytes.
///
/// Returns [Error] on failure.
///
pub fn seek(file_access: *FileAccess, cursor: u64) Error!void {
return file_access.implementation.seek(file_access.context, cursor);
}
///
/// Attempts to seek `file_access` to the end of the file.
///
/// Returns [Error] on failure.
///
pub fn seekToEnd(file_access: *FileAccess) Error!void {
return file_access.implementation.seekToEnd(file_access.context);
}
///
/// Attempts to seek `file_access` by `offset` from the current file position.
///
/// Returns [Error] on failure;
///
pub fn skip(file_access: *FileAccess, offset: i64) Error!void {
return file_access.implementation.skip(file_access.context, offset);
}
};
/// ///
/// Platform-agnostic mechanism for working with an abstraction of the underlying file-system(s) /// Platform-agnostic mechanism for working with an abstraction of the underlying file-system(s)
/// available to the application in a sandboxed environment. /// available to the application in a sandboxed environment.
@ -447,11 +350,13 @@ pub const FileSystem = union(enum) {
/// Returns a [FileAccess] reference that provides access to the file referenced by `path` /// Returns a [FileAccess] reference that provides access to the file referenced by `path`
/// or a [OpenError] if it failed. /// or a [OpenError] if it failed.
/// ///
pub fn open(path: Path, mode: OpenMode) OpenError!FileAccess { pub fn open(path: Path, mode: OpenMode) OpenError!ona.io.FileAccess {
switch (path.file_system.*) { switch (path.file_system.*) {
.archive => |*archive| { .archive => |*archive| {
if (mode != .readonly) return error.ModeUnsupported; if (mode != .readonly) return error.ModeUnsupported;
const FileAccess = ona.io.FileAccess;
for (archive.entry_table) |*entry| if (entry.owner == null) { for (archive.entry_table) |*entry| if (entry.owner == null) {
const Implementation = struct { const Implementation = struct {
fn close(context: *anyopaque) void { fn close(context: *anyopaque) void {
@ -567,6 +472,8 @@ pub const FileSystem = union(enum) {
ext.SDL_ClearError(); ext.SDL_ClearError();
const FileAccess = ona.io.FileAccess;
const Implementation = struct { const Implementation = struct {
fn rwOpsCast(context: *anyopaque) *ext.SDL_RWops { fn rwOpsCast(context: *anyopaque) *ext.SDL_RWops {
return @ptrCast(*ext.SDL_RWops, @alignCast( return @ptrCast(*ext.SDL_RWops, @alignCast(
@ -693,7 +600,7 @@ pub const FileSystem = union(enum) {
const last_sequence_index = sequences.len - 1; const last_sequence_index = sequences.len - 1;
for (sequences) |sequence, index| if (sequence.len != 0) { for (sequences) |sequence, index| if (sequence.len != 0) {
var components = mem.Spliterator(u8){ var components = ona.mem.Spliterator(u8){
.source = sequence, .source = sequence,
.delimiter = "/", .delimiter = "/",
}; };

View File

@ -1,18 +1,17 @@
const ona = @import("ona");
const std = @import("std"); const std = @import("std");
const sys = @import("./sys.zig");
const table = @import("./table.zig");
/// ///
/// Thin file-wrapper and in-memory layout cache of an OAR archive file. /// Thin file-wrapper and in-memory layout cache of an OAR archive file.
/// ///
pub const Archive = struct { pub const Archive = struct {
file_access: sys.FileAccess, file_access: ona.io.FileAccess,
index_cache: IndexCache, index_cache: IndexCache,
/// ///
/// [OpenError.EntryNotFound] happens when an entry could not be found. /// [OpenError.EntryNotFound] happens when an entry could not be found.
/// ///
pub const FindError = sys.FileAccess.Error || error { pub const FindError = ona.io.FileAccess.Error || error {
EntryNotFound, EntryNotFound,
}; };
@ -27,7 +26,7 @@ pub const Archive = struct {
/// As the archive is queried via [find], the cache is lazily assembled with the absolute /// As the archive is queried via [find], the cache is lazily assembled with the absolute
/// offsets of each queried file. /// offsets of each queried file.
/// ///
const IndexCache = table.Hashed([]const u8, u64, table.string_context); const IndexCache = ona.table.Hashed([]const u8, u64, ona.table.string_context);
/// ///
/// Deinitializes the index cache of `archive`, freeing all associated memory. /// Deinitializes the index cache of `archive`, freeing all associated memory.
@ -49,6 +48,7 @@ pub const Archive = struct {
.header = find_header: { .header = find_header: {
var header = Entry.Header{ var header = Entry.Header{
.revision = 0, .revision = 0,
.path = Path.empty,
.file_size = 0, .file_size = 0,
.absolute_offset = 0 .absolute_offset = 0
}; };
@ -73,7 +73,7 @@ pub const Archive = struct {
// Read first entry. // Read first entry.
while ((try archive.file_access.read(mem.asBytes(&header))) == header_size) { while ((try archive.file_access.read(mem.asBytes(&header))) == header_size) {
if (mem.eql(u8, entry_path, header.name_buffer[0 .. header.name_length])) { if (mem.eql(u8, entry_path, header.path.buffer[0 .. header.path.length])) {
// If caching fails... oh well... // If caching fails... oh well...
archive.index_cache.insert(entry_path, header.absolute_offset) catch {}; archive.index_cache.insert(entry_path, header.absolute_offset) catch {};
@ -110,7 +110,7 @@ pub const Archive = struct {
/// **Note** that `archive_file_access` does nothing to manage the lifetime of the open file. /// **Note** that `archive_file_access` does nothing to manage the lifetime of the open file.
/// ///
pub fn init(cache_allocator: std.mem.Allocator, pub fn init(cache_allocator: std.mem.Allocator,
archive_file_access: sys.FileAccess) InitError!Archive { archive_file_access: ona.io.FileAccess) InitError!Archive {
return Archive{ return Archive{
.index_cache = try IndexCache.init(cache_allocator), .index_cache = try IndexCache.init(cache_allocator),
@ -123,7 +123,7 @@ pub const Archive = struct {
/// Handles the state of an opened archive entry. /// Handles the state of an opened archive entry.
/// ///
pub const Entry = struct { pub const Entry = struct {
owner: ?*sys.FileAccess, owner: ?*ona.io.FileAccess,
cursor: u64, cursor: u64,
header: Header, header: Header,
@ -135,8 +135,7 @@ pub const Entry = struct {
pub const Header = extern struct { pub const Header = extern struct {
signature: [signature_magic.len]u8 = signature_magic, signature: [signature_magic.len]u8 = signature_magic,
revision: u8, revision: u8,
name_buffer: [255]u8 = std.mem.zeroes([255]u8), path: Path,
name_length: u8 = 0,
file_size: u64, file_size: u64,
absolute_offset: u64, absolute_offset: u64,
padding: [232]u8 = std.mem.zeroes([232]u8), padding: [232]u8 = std.mem.zeroes([232]u8),
@ -155,3 +154,16 @@ pub const Entry = struct {
pub const signature_magic = [3]u8{'o', 'a', 'r'}; pub const signature_magic = [3]u8{'o', 'a', 'r'};
}; };
}; };
///
///
///
pub const Path = extern struct {
buffer: [255]u8,
length: u8,
///
///
///
pub const empty = std.mem.zeroes(Path);
};

View File

@ -1,6 +1,102 @@
const stack = @import("./stack.zig"); const stack = @import("./stack.zig");
const std = @import("std"); const std = @import("std");
///
/// File-system agnostic abstraction for manipulating a file.
///
pub const FileAccess = struct {
context: *anyopaque,
implementation: *const Implementation,
///
/// Provides a set of implementation-specific behaviors to a [FileAccess] instance.
///
pub const Implementation = struct {
close: fn (*anyopaque) void,
queryCursor: fn (*anyopaque) Error!u64,
queryLength: fn (*anyopaque) Error!u64,
read: fn (*anyopaque, []u8) Error!usize,
seek: fn (*anyopaque, u64) Error!void,
seekToEnd: fn (*anyopaque) Error!void,
skip: fn (*anyopaque, i64) Error!void,
};
///
/// [Error.FileInaccessible] is a generic catch-all for a [FileAccess] reference no longer
/// pointing to a file or the file becomming invalid for whatever reason.
///
pub const Error = error {
FileInaccessible,
};
///
/// 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) void {
return file_access.implementation.close(file_access.context);
}
///
/// Attempts to query the current cursor position for the file referenced by `file_access`.
///
/// 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) Error!u64 {
return file_access.implementation.queryCursor(file_access.context);
}
///
/// Attempts to query the current length for the file referenced by `file_access`.
///
/// 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) Error!u64 {
return file_access.implementation.queryLength(file_access.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, buffer: []u8) Error!usize {
return file_access.implementation.read(file_access.context, buffer);
}
///
/// Attempts to seek `file_access` from the beginning of the file to `cursor` bytes.
///
/// Returns [Error] on failure.
///
pub fn seek(file_access: *FileAccess, cursor: u64) Error!void {
return file_access.implementation.seek(file_access.context, cursor);
}
///
/// Attempts to seek `file_access` to the end of the file.
///
/// Returns [Error] on failure.
///
pub fn seekToEnd(file_access: *FileAccess) Error!void {
return file_access.implementation.seekToEnd(file_access.context);
}
///
/// Attempts to seek `file_access` by `offset` from the current file position.
///
/// Returns [Error] on failure;
///
pub fn skip(file_access: *FileAccess, offset: i64) Error!void {
return file_access.implementation.skip(file_access.context, offset);
}
};
/// ///
/// Opaque interface to a "writable" resource, such as a block device, memory buffer, or network /// Opaque interface to a "writable" resource, such as a block device, memory buffer, or network
/// socket. /// socket.

25
src/ona/main.zig Normal file
View File

@ -0,0 +1,25 @@
///
///
///
pub const io = @import("./io.zig");
///
///
///
pub const mem = @import("./mem.zig");
///
///
///
pub const meta = @import("./meta.zig");
///
///
///
pub const stack = @import("./stack.zig");
///
///
///
pub const table = @import("./table.zig");