Refactor SDL2 interface code
This commit is contained in:
parent
1a28dc2404
commit
32bb049f73
|
@ -6,16 +6,16 @@ const sys = @import("./sys.zig");
|
|||
/// Starts the the game engine.
|
||||
///
|
||||
pub fn main() anyerror!void {
|
||||
return nosuspend await async sys.runGraphics(anyerror, run);
|
||||
return nosuspend await async sys.display(anyerror, run);
|
||||
}
|
||||
|
||||
fn run(app: *sys.AppContext, graphics: *sys.GraphicsContext) anyerror!void {
|
||||
fn run(app: *sys.App, graphics: *sys.Graphics) anyerror!void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
{
|
||||
var file_access = try app.data().open(try sys.Path.joined(&.{"ona.lua"}), .readonly);
|
||||
var file_access = try app.data.open(try sys.Path.joined(&.{"ona.lua"}), .readonly);
|
||||
|
||||
defer file_access.close();
|
||||
|
||||
|
@ -27,7 +27,7 @@ fn run(app: *sys.AppContext, graphics: *sys.GraphicsContext) anyerror!void {
|
|||
|
||||
if ((try file_access.read(buffer)) != file_size) return error.ScriptLoadFailure;
|
||||
|
||||
sys.Log.debug.write(buffer);
|
||||
app.log(.debug, buffer);
|
||||
}
|
||||
|
||||
while (graphics.poll()) |_| {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
const Archive = @import("./sys/Archive.zig");
|
||||
|
||||
const ext = @cImport({
|
||||
@cInclude("SDL2/SDL.h");
|
||||
});
|
||||
|
@ -9,222 +7,43 @@ const ona = @import("ona");
|
|||
const std = @import("std");
|
||||
|
||||
///
|
||||
/// A thread-safe platform abstraction over multiplexing system I/O processing and event handling.
|
||||
/// Thread-safe platform abstraction over multiplexing system I/O processing and event handling.
|
||||
///
|
||||
pub const AppContext = opaque {
|
||||
///
|
||||
/// Linked list of asynchronous messages chained together to be processed by the work processor.
|
||||
///
|
||||
const Message = struct {
|
||||
next: ?*Message = null,
|
||||
|
||||
kind: union(enum) {
|
||||
quit,
|
||||
|
||||
task: struct {
|
||||
data: *anyopaque,
|
||||
action: fn (*anyopaque) void,
|
||||
frame: anyframe,
|
||||
},
|
||||
},
|
||||
};
|
||||
pub const App = struct {
|
||||
message_chain: ?*Message = null,
|
||||
message_semaphore: *ext.SDL_sem,
|
||||
message_mutex: *ext.SDL_mutex,
|
||||
data: FileSystem,
|
||||
user: FileSystem,
|
||||
|
||||
///
|
||||
/// Internal state of the event loop hidden from the API consumer.
|
||||
/// Enqueues `message` to the message chain in `app`.
|
||||
///
|
||||
const Implementation = struct {
|
||||
user_path_prefix: [*]u8,
|
||||
data_file_system: FileSystem,
|
||||
user_file_system: FileSystem,
|
||||
message_semaphore: *ext.SDL_sem,
|
||||
message_mutex: *ext.SDL_mutex,
|
||||
message_thread: ?*ext.SDL_Thread,
|
||||
messages: ?*Message = null,
|
||||
fn enqueue(app: *App, message: *Message) void {
|
||||
{
|
||||
// TODO: Error check these.
|
||||
_ = ext.SDL_LockMutex(app.message_mutex);
|
||||
|
||||
///
|
||||
/// [StartError.OutOfSemaphores] indicates that the process has no more semaphores available
|
||||
/// to it for use, meaning an [Implementation] may not be initialized at this time.
|
||||
///
|
||||
/// [StartError.OutOfMutexes] indicates that the process has no more mutexes available to it
|
||||
/// for use, meaning an [Implementation] may not be initialized at this time.
|
||||
///
|
||||
/// [StartError.OutOfMemory] indicates that the process has no more memory available to it
|
||||
/// for use, meaning an [Implementation] may not be initialized at this time.
|
||||
///
|
||||
const InitError = error {
|
||||
OutOfSemaphores,
|
||||
OutOfMutexes,
|
||||
OutOfMemory,
|
||||
};
|
||||
defer _ = ext.SDL_UnlockMutex(app.message_mutex);
|
||||
|
||||
///
|
||||
/// [StartError.OutOfThreads] indicates that the process has no more threads available to it
|
||||
/// to use, meaning that no asynchronous work may be started on an [Implementation] at this
|
||||
/// time.
|
||||
///
|
||||
/// [StartError.AlreadyStarted] is occurs when a request to start work processing happens on
|
||||
/// an [Implementation] that is already processing work.
|
||||
///
|
||||
const StartError = error {
|
||||
OutOfThreads,
|
||||
AlreadyStarted,
|
||||
};
|
||||
|
||||
///
|
||||
/// Casts `app_context` to a [Implementation] reference.
|
||||
///
|
||||
/// *Note* that if `app_context` does not have the same alignment as [Implementation], safety-
|
||||
/// checked undefined behavior will occur.
|
||||
///
|
||||
fn cast(app_context: *AppContext) *Implementation {
|
||||
return @ptrCast(*Implementation, @alignCast(@alignOf(Implementation), app_context));
|
||||
}
|
||||
|
||||
///
|
||||
/// Deinitializes the `implementation`, requesting any running asynchronous workload
|
||||
/// processes quit and waits for them to do so before freeing any resources.
|
||||
///
|
||||
fn deinit(implementation: *Implementation) void {
|
||||
var message = Message{.kind = .quit};
|
||||
|
||||
implementation.enqueue(&message);
|
||||
|
||||
{
|
||||
var status = @as(c_int, 0);
|
||||
|
||||
// SDL2 defines waiting on a null thread reference as a no-op. See
|
||||
// https://wiki.libsdl.org/SDL_WaitThread for more information
|
||||
ext.SDL_WaitThread(implementation.message_thread, &status);
|
||||
|
||||
if (status != 0) {
|
||||
// TODO: Error check this.
|
||||
}
|
||||
}
|
||||
|
||||
implementation.data_file_system.archive.index_cache.deinit();
|
||||
ext.SDL_free(implementation.user_path_prefix);
|
||||
ext.SDL_DestroyMutex(implementation.message_mutex);
|
||||
ext.SDL_DestroySemaphore(implementation.message_semaphore);
|
||||
}
|
||||
|
||||
///
|
||||
/// Enqueues `message` to the message processor of `implementation` to be processed at a
|
||||
/// later, non-deterministic point in time.
|
||||
///
|
||||
fn enqueue(implementation: *Implementation, message: *Message) void {
|
||||
{
|
||||
// TODO: Error check these.
|
||||
_ = ext.SDL_LockMutex(implementation.message_mutex);
|
||||
|
||||
defer _ = ext.SDL_UnlockMutex(implementation.message_mutex);
|
||||
|
||||
if (implementation.messages) |messages| {
|
||||
messages.next = message;
|
||||
} else {
|
||||
implementation.messages = message;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Error check this.
|
||||
_ = ext.SDL_SemPost(implementation.message_semaphore);
|
||||
}
|
||||
|
||||
///
|
||||
/// Initializes a new [Implemenation] with `data_archive_file_access` as the data archive to
|
||||
/// read from and `user_path_prefix` as the native writable user data directory.
|
||||
///
|
||||
/// Returns the created [Implementation] value on success or [InitError] on failure.
|
||||
///
|
||||
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
|
||||
return error.OutOfMemory;
|
||||
|
||||
return Implementation{
|
||||
.user_file_system = .{.native =
|
||||
user_path_prefix[0 .. std.mem.len(user_path_prefix)]},
|
||||
|
||||
.message_semaphore = ext.SDL_CreateSemaphore(0) orelse return error.OutOfSemaphores,
|
||||
.message_mutex = ext.SDL_CreateMutex() orelse return error.OutOfMutexes,
|
||||
.user_path_prefix = user_path_prefix,
|
||||
|
||||
.data_file_system = .{.archive = .{
|
||||
.index_cache = try Archive.IndexCache.init(allocator),
|
||||
.file_access = data_archive_file_access,
|
||||
}},
|
||||
|
||||
.message_thread = null,
|
||||
};
|
||||
}
|
||||
|
||||
///
|
||||
/// [FileSystemMessage] processing function used by a dedicated worker thread, where `data`
|
||||
/// is a type-erased reference to a [EventLoop].
|
||||
///
|
||||
/// The processor returns `0` if it exited normally or any other value if an erroneous exit
|
||||
/// occured.
|
||||
///
|
||||
fn processTasks(userdata: ?*anyopaque) callconv(.C) c_int {
|
||||
const implementation = Implementation.cast(
|
||||
@ptrCast(*AppContext, userdata orelse unreachable));
|
||||
|
||||
while (true) {
|
||||
// TODO: Error check these.
|
||||
_ = ext.SDL_SemWait(implementation.message_semaphore);
|
||||
_ = ext.SDL_LockMutex(implementation.message_mutex);
|
||||
|
||||
defer _ = ext.SDL_UnlockMutex(implementation.message_mutex);
|
||||
|
||||
while (implementation.messages) |messages| {
|
||||
switch (messages.kind) {
|
||||
.quit => {
|
||||
return 0;
|
||||
},
|
||||
|
||||
.task => |task| {
|
||||
task.action(task.data);
|
||||
|
||||
resume task.frame;
|
||||
},
|
||||
}
|
||||
|
||||
implementation.messages = messages.next;
|
||||
}
|
||||
if (app.message_chain) |message_chain| {
|
||||
message_chain.next = message;
|
||||
} else {
|
||||
app.message_chain = message;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Attempts to start the asynchronous worker thread of `implementation` if it hasn't been
|
||||
/// already.
|
||||
///
|
||||
/// [StartError] is returned on failure.
|
||||
///
|
||||
fn start(implementation: *Implementation) StartError!void {
|
||||
if (implementation.message_thread != null) return error.AlreadyStarted;
|
||||
|
||||
implementation.message_thread = ext.SDL_CreateThread(processTasks,
|
||||
"File System Worker", implementation) orelse {
|
||||
|
||||
ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create file-system work processor");
|
||||
|
||||
return error.OutOfThreads;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
///
|
||||
/// Returns a reference to the currently loaded data file-system.
|
||||
///
|
||||
pub fn data(app_context: *AppContext) *FileSystem {
|
||||
return &Implementation.cast(app_context).data_file_system;
|
||||
// TODO: Error check this.
|
||||
_ = ext.SDL_SemPost(app.message_semaphore);
|
||||
}
|
||||
|
||||
///
|
||||
/// Asynchronously executes `procedure` with `arguments` as an anonymous struct of its arguments
|
||||
/// and `app` as its execution context.
|
||||
///
|
||||
/// Once the execution frame resumes, the value returned by executing `procedure` is returned.
|
||||
///
|
||||
pub fn schedule(app_context: *AppContext, procedure: anytype,
|
||||
pub fn schedule(app: *App, procedure: anytype,
|
||||
arguments: anytype) ona.meta.FnReturn(@TypeOf(procedure)) {
|
||||
|
||||
const Task = struct {
|
||||
|
@ -246,7 +65,7 @@ pub const AppContext = opaque {
|
|||
.arguments = &arguments,
|
||||
};
|
||||
|
||||
var message = AppContext.Message{
|
||||
var message = Message{
|
||||
.kind = .{.task = .{
|
||||
.data = &task,
|
||||
.action = Task.process,
|
||||
|
@ -254,14 +73,22 @@ pub const AppContext = opaque {
|
|||
}},
|
||||
};
|
||||
|
||||
suspend Implementation.cast(app_context).enqueue(&message);
|
||||
suspend app.enqueue(&message);
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns a reference to the currently loaded user file-system.
|
||||
/// Asynchronously logs `info` with `logger` as the logging method and `app` as the execution
|
||||
/// context.
|
||||
///
|
||||
pub fn user(app_context: *AppContext) *FileSystem {
|
||||
return &Implementation.cast(app_context).user_file_system;
|
||||
pub fn log(app: *App, logger: Logger, info: []const u8) void {
|
||||
var message = Message{
|
||||
.kind = .{.log = .{
|
||||
.logger = logger,
|
||||
.info = info,
|
||||
}},
|
||||
};
|
||||
|
||||
app.enqueue(&message);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -274,17 +101,48 @@ pub const FileSystem = union(enum) {
|
|||
archive: Archive,
|
||||
|
||||
///
|
||||
/// 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.
|
||||
/// Archive file system information.
|
||||
///
|
||||
const Archive = struct {
|
||||
file_access: ona.io.FileAccess,
|
||||
index_cache: IndexCache,
|
||||
entry_table: [max_open_entries]Entry = std.mem.zeroes([max_open_entries]Entry),
|
||||
|
||||
///
|
||||
/// Hard limit on the maximum number of entries open at once.
|
||||
///
|
||||
const max_open_entries = 16;
|
||||
|
||||
///
|
||||
/// Stateful extension of an [oar.Entry].
|
||||
///
|
||||
const Entry = struct {
|
||||
owner: ?*ona.io.FileAccess,
|
||||
cursor: u64,
|
||||
header: oar.Entry,
|
||||
};
|
||||
|
||||
///
|
||||
/// Table cache for associating [oar.Path] values with offsets to entries in a given file.
|
||||
///
|
||||
const IndexCache = ona.table.Hashed(oar.Path, u64, .{
|
||||
.equals = oar.Path.equals,
|
||||
.hash = oar.Path.hash,
|
||||
});
|
||||
};
|
||||
|
||||
///
|
||||
/// 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.
|
||||
/// If the number of known [FileAccess] handles has been exhausted, [OpenError.OutOfFiles] is
|
||||
/// used to communicate this.
|
||||
///
|
||||
pub const OpenError = error {
|
||||
FileNotFound,
|
||||
|
@ -380,12 +238,13 @@ pub const FileSystem = union(enum) {
|
|||
}
|
||||
|
||||
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) {
|
||||
const math = std.math;
|
||||
|
||||
archive_entry.cursor = math.max(0,
|
||||
archive_entry.cursor - math.absCast(offset));
|
||||
} else {
|
||||
|
@ -447,17 +306,18 @@ pub const FileSystem = union(enum) {
|
|||
.native => |native| {
|
||||
if (native.len == 0) return error.FileNotFound;
|
||||
|
||||
var path_buffer = std.mem.zeroes([4096]u8);
|
||||
const mem = std.mem;
|
||||
var path_buffer = 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);
|
||||
mem.copy(u8, &path_buffer, native);
|
||||
|
||||
if (seperator_length != 0) path_buffer[native.len] = oar.Path.seperator;
|
||||
|
||||
std.mem.copy(u8, path_buffer[native.len .. path_buffer.
|
||||
mem.copy(u8, path_buffer[native.len .. path_buffer.
|
||||
len], path.buffer[0 .. path.length]);
|
||||
|
||||
const FileAccess = ona.io.FileAccess;
|
||||
|
@ -565,7 +425,7 @@ pub const FileSystem = union(enum) {
|
|||
///
|
||||
///
|
||||
///
|
||||
pub const GraphicsContext = opaque {
|
||||
pub const Graphics = opaque {
|
||||
///
|
||||
///
|
||||
///
|
||||
|
@ -587,8 +447,8 @@ pub const GraphicsContext = opaque {
|
|||
///
|
||||
///
|
||||
///
|
||||
pub fn poll(graphics_context: *GraphicsContext) ?*const Event {
|
||||
_ = graphics_context;
|
||||
pub fn poll(graphics: *Graphics) ?*const Event {
|
||||
_ = graphics;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -596,41 +456,46 @@ pub const GraphicsContext = opaque {
|
|||
///
|
||||
///
|
||||
///
|
||||
pub fn present(graphics_context: *GraphicsContext) void {
|
||||
pub fn present(graphics: *Graphics) void {
|
||||
// TODO: Implement;
|
||||
_ = graphics_context;
|
||||
_ = graphics;
|
||||
}
|
||||
};
|
||||
|
||||
///
|
||||
/// Returns a graphics runner that uses `Errors` as its error set.
|
||||
/// [Logger.info] logs information that isn't necessarily an error but indicates something useful to
|
||||
/// be logged.
|
||||
///
|
||||
pub fn GraphicsRunner(comptime Errors: type) type {
|
||||
return fn (*AppContext, *GraphicsContext) callconv(.Async) Errors!void;
|
||||
}
|
||||
|
||||
/// [Logger.debug] logs information only when the engine is in debug mode.
|
||||
///
|
||||
/// [Log.info] represents a log message which is purely informative and does not indicate any kind
|
||||
/// of issue.
|
||||
/// [Logger.warning] logs information to indicate a non-critical error has occured.
|
||||
///
|
||||
/// [Log.debug] represents a log message which is purely for debugging purposes and will only occurs
|
||||
/// in debug builds.
|
||||
///
|
||||
/// [Log.warning] represents a log message which is a warning about a issue that does not break
|
||||
/// anything important but is not ideal.
|
||||
///
|
||||
pub const Log = enum(u32) {
|
||||
pub const Logger = enum(u32) {
|
||||
info = ext.SDL_LOG_PRIORITY_INFO,
|
||||
debug = ext.SDL_LOG_PRIORITY_DEBUG,
|
||||
warning = ext.SDL_LOG_PRIORITY_WARN,
|
||||
};
|
||||
|
||||
///
|
||||
/// Writes `utf8_message` as the log kind identified by `log`.
|
||||
///
|
||||
pub fn write(log: Log, utf8_message: []const u8) void {
|
||||
ext.SDL_LogMessage(ext.SDL_LOG_CATEGORY_APPLICATION,
|
||||
@enumToInt(log), "%.*s", utf8_message.len, utf8_message.ptr);
|
||||
}
|
||||
///
|
||||
/// Linked list of asynchronous messages chained together to be processed by the work processor.
|
||||
///
|
||||
pub const Message = struct {
|
||||
next: ?*Message = null,
|
||||
|
||||
kind: union(enum) {
|
||||
quit,
|
||||
|
||||
log: struct {
|
||||
logger: Logger,
|
||||
info: []const u8,
|
||||
},
|
||||
|
||||
task: struct {
|
||||
data: *anyopaque,
|
||||
action: fn (*anyopaque) void,
|
||||
frame: anyframe,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
///
|
||||
|
@ -639,13 +504,10 @@ pub const Log = enum(u32) {
|
|||
pub const Path = oar.Path;
|
||||
|
||||
///
|
||||
/// [RunError.InitFailure] occurs if a necessary resource fails to be acquired or allocated.
|
||||
///
|
||||
/// [RunError.AlreadyRunning] occurs if a runner has already been started.
|
||||
/// [RunError.InitFailure] occurs when the runtime fails to initialize.
|
||||
///
|
||||
pub const RunError = error {
|
||||
InitFailure,
|
||||
AlreadyRunning,
|
||||
};
|
||||
|
||||
///
|
||||
|
@ -654,8 +516,76 @@ pub const RunError = error {
|
|||
/// Should an error from `run` occur, an `Error` is returned, otherwise a [RunError] is returned if
|
||||
/// the underlying runtime fails and is logged.
|
||||
///
|
||||
pub fn runGraphics(comptime Error: anytype,
|
||||
comptime run: GraphicsRunner(Error)) (RunError || Error)!void {
|
||||
pub fn display(comptime Error: anytype,
|
||||
comptime run: fn (*App, *Graphics) callconv(.Async) Error!void) (RunError || Error)!void {
|
||||
|
||||
var cwd = FileSystem{.native = "./"};
|
||||
const user_prefix = ext.SDL_GetPrefPath("ona", "ona") orelse return error.InitFailure;
|
||||
|
||||
defer ext.SDL_free(user_prefix);
|
||||
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
|
||||
defer if (gpa.deinit())
|
||||
ext.SDL_LogWarn(ext.SDL_LOG_CATEGORY_APPLICATION, "Runtime allocator leaked memory");
|
||||
|
||||
var app = App{
|
||||
.user = .{.native = std.mem.sliceTo(user_prefix, 0)},
|
||||
|
||||
.message_semaphore = ext.SDL_CreateSemaphore(0) orelse {
|
||||
ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to create message semaphore");
|
||||
|
||||
return error.InitFailure;
|
||||
},
|
||||
|
||||
.message_mutex = ext.SDL_CreateMutex() orelse {
|
||||
ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to create message mutex");
|
||||
|
||||
return error.InitFailure;
|
||||
},
|
||||
|
||||
.data = .{.archive = .{
|
||||
.index_cache = FileSystem.Archive.IndexCache.init(gpa.allocator()) catch
|
||||
return error.InitFailure,
|
||||
|
||||
.file_access = cwd.open(try Path.joined(&.{"./data.oar"}), .readonly) catch {
|
||||
ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to load ./data.oar");
|
||||
|
||||
return error.InitFailure;
|
||||
},
|
||||
}},
|
||||
};
|
||||
|
||||
defer {
|
||||
app.data.archive.file_access.close();
|
||||
app.data.archive.index_cache.deinit();
|
||||
ext.SDL_DestroySemaphore(app.message_semaphore);
|
||||
ext.SDL_DestroyMutex(app.message_mutex);
|
||||
}
|
||||
|
||||
const message_thread = ext.SDL_CreateThread(processMessages, "Message Processor", &app) orelse {
|
||||
ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to create message processor");
|
||||
|
||||
return error.InitFailure;
|
||||
};
|
||||
|
||||
defer {
|
||||
var message = Message{.kind = .quit};
|
||||
|
||||
app.enqueue(&message);
|
||||
|
||||
{
|
||||
var status = std.mem.zeroes(c_int);
|
||||
|
||||
// SDL2 defines waiting on a null thread reference as a no-op. See
|
||||
// https://wiki.libsdl.org/SDL_WaitThread for more information
|
||||
ext.SDL_WaitThread(message_thread, &status);
|
||||
|
||||
if (status != 0) {
|
||||
// TODO: Error check this.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) {
|
||||
ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize runtime");
|
||||
|
@ -690,42 +620,47 @@ pub fn runGraphics(comptime Error: anytype,
|
|||
|
||||
defer ext.SDL_DestroyRenderer(renderer);
|
||||
|
||||
var cwd_file_system = FileSystem{.native ="./"};
|
||||
var data_access = try cwd_file_system.open(try Path.joined(&.{"./data.oar"}), .readonly);
|
||||
|
||||
defer data_access.close();
|
||||
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
var app_context = AppContext.Implementation.init(gpa.allocator(), data_access) catch |err| {
|
||||
ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, switch (err) {
|
||||
error.OutOfMemory => "Failed to allocate necessary memory",
|
||||
error.OutOfMutexes => "Failed to create file-system work lock",
|
||||
error.OutOfSemaphores => "Failed to create file-system work scheduler",
|
||||
});
|
||||
|
||||
return error.InitFailure;
|
||||
};
|
||||
|
||||
defer app_context.deinit();
|
||||
|
||||
app_context.start() catch |err| {
|
||||
ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, switch (err) {
|
||||
// Not possible for it to have already been started.
|
||||
error.AlreadyStarted => unreachable,
|
||||
error.OutOfThreads => "Failed to start file-system work processor",
|
||||
});
|
||||
|
||||
return error.InitFailure;
|
||||
};
|
||||
|
||||
var graphics_context = GraphicsContext.Implementation{
|
||||
var graphics = Graphics.Implementation{
|
||||
.event = .{
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
return run(@ptrCast(*AppContext, &app_context), @ptrCast(*GraphicsContext, &graphics_context));
|
||||
return run(@ptrCast(*App, &app), @ptrCast(*Graphics, &graphics));
|
||||
}
|
||||
|
||||
///
|
||||
/// [FileSystemMessage] processing function used by a dedicated worker thread, where `data`
|
||||
/// is a type-erased reference to a [EventLoop].
|
||||
///
|
||||
/// The processor returns `0` if it exited normally or any other value if an erroneous exit
|
||||
/// occured.
|
||||
///
|
||||
pub fn processMessages(userdata: ?*anyopaque) callconv(.C) c_int {
|
||||
const app = @ptrCast(*App, @alignCast(@alignOf(App), userdata orelse unreachable));
|
||||
|
||||
while (true) {
|
||||
// TODO: Error check these.
|
||||
_ = ext.SDL_SemWait(app.message_semaphore);
|
||||
_ = ext.SDL_LockMutex(app.message_mutex);
|
||||
|
||||
defer _ = ext.SDL_UnlockMutex(app.message_mutex);
|
||||
|
||||
while (app.message_chain) |message| {
|
||||
switch (message.kind) {
|
||||
.quit => return 0,
|
||||
|
||||
.log => |log| ext.SDL_LogMessage(ext.SDL_LOG_CATEGORY_APPLICATION,
|
||||
@enumToInt(log.logger), "%.*s", log.info.len, log.info.ptr),
|
||||
|
||||
.task => |task| {
|
||||
task.action(task.data);
|
||||
|
||||
resume task.frame;
|
||||
},
|
||||
}
|
||||
|
||||
app.message_chain = message.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
const oar = @import("oar");
|
||||
const ona = @import("ona");
|
||||
const std = @import("std");
|
||||
|
||||
file_access: ona.io.FileAccess,
|
||||
index_cache: IndexCache,
|
||||
entry_table: [max_open_entries]Entry = std.mem.zeroes([max_open_entries]Entry),
|
||||
|
||||
///
|
||||
/// Hard limit on the maximum number of entries open at once.
|
||||
///
|
||||
const max_open_entries = 16;
|
||||
|
||||
///
|
||||
/// Stateful extension of an [oar.Entry].
|
||||
///
|
||||
pub const Entry = struct {
|
||||
owner: ?*ona.io.FileAccess,
|
||||
cursor: u64,
|
||||
header: oar.Entry,
|
||||
};
|
||||
|
||||
///
|
||||
/// Table cache for associating [oar.Path] values with offsets to entries in a given file.
|
||||
///
|
||||
pub const IndexCache = ona.table.Hashed(oar.Path, u64, .{
|
||||
.equals = oar.Path.equals,
|
||||
.hash = oar.Path.hash,
|
||||
});
|
|
@ -30,7 +30,7 @@ pub const Entry = extern struct {
|
|||
const origin = try file_access.queryCursor();
|
||||
|
||||
if (((try file_access.read(std.mem.asBytes(&entry))) != @sizeOf(Entry)) and
|
||||
ona.io.equalsBytes(entry.signature[0 ..], signature_magic[0 ..])) {
|
||||
ona.io.equalsBytes(&entry.signature, &signature_magic)) {
|
||||
|
||||
try file_access.seek(origin);
|
||||
|
||||
|
|
Loading…
Reference in New Issue