Improve App entry-point declaration (closes #61)
continuous-integration/drone/push Build is passing Details

This commit is contained in:
kayomn 2024-07-24 22:09:27 +01:00
parent f49906c714
commit 0f2950a630
14 changed files with 321 additions and 265 deletions

View File

@ -208,9 +208,9 @@ pub fn build(b: *std.Build) !void {
});
const ona_module = try project.add_module(b, "ona", .{});
const input_module = try project.add_module(b, "input", .{});
const hid_module = try project.add_module(b, "hid", .{});
const coral_module = try project.add_module(b, "coral", .{
const gfx_module = try project.add_module(b, "gfx", .{
.imports = &.{
.{
.name = "sokol",
@ -224,14 +224,14 @@ pub fn build(b: *std.Build) !void {
.{
.name = "input",
.module = input_module,
.module = hid_module,
},
},
});
coral_module.addIncludePath(b.path("ext/"));
gfx_module.addIncludePath(b.path("ext/"));
coral_module.linkLibrary(spirv_cross: {
gfx_module.linkLibrary(spirv_cross: {
const dir = "ext/spirv-cross/";
const sources = [_][]const u8{
@ -274,12 +274,12 @@ pub fn build(b: *std.Build) !void {
break: spirv_cross lib;
});
coral_module.linkSystemLibrary("SDL2", .{
gfx_module.linkSystemLibrary("SDL2", .{
.needed = true,
.preferred_link_mode = .dynamic,
});
coral_module.link_libc = true;
gfx_module.link_libc = true;
try project.find_tests(b);
try project.find_demos(b);

View File

@ -1,4 +1,4 @@
const coral = @import("coral");
const gfx = @import("gfx");
const ona = @import("ona");
@ -12,17 +12,11 @@ const CRT = extern struct {
};
const Effects = struct {
render_texture: coral.Texture = .default,
crt_effect: coral.Effect = .default,
render_texture: gfx.Texture = .default,
crt_effect: gfx.Effect = .default,
};
pub const main = ona.start(setup, .{
.tick_rate = 60,
.execution = .{.thread_share = 0.1},
.middleware = &.{coral.setup},
});
fn load(display: ona.Write(coral.Display), actors: ona.Write(Effects), assets: ona.Write(coral.Assets)) !void {
fn load(display: ona.Write(gfx.Display), actors: ona.Write(Effects), assets: ona.Write(gfx.Assets)) !void {
display.state.width, display.state.height = .{1280, 720};
actors.state.render_texture = try assets.state.load_texture(.{
@ -39,10 +33,16 @@ fn load(display: ona.Write(coral.Display), actors: ona.Write(Effects), assets: o
actors.state.crt_effect = try assets.state.load_effect_file(ona.files.bundle, "./crt.frag.spv");
}
fn render(commands: coral.Commands, effects: ona.Write(Effects), app: ona.Read(ona.App), display: ona.Write(coral.Display)) !void {
pub const main = ona.App.setup.
with_module(gfx).
with_state(Effects{}).
with_system(.load, ona.system_fn(load), .{.label = "load effects"}).
with_system(.render, ona.system_fn(render), .{.label = "render effects"}).build();
fn render(commands: gfx.Commands, effects: ona.Write(Effects), app: ona.Read(ona.App), display: ona.Write(gfx.Display)) !void {
try commands.set_target(.{
.texture = effects.state.render_texture,
.clear_color = coral.colors.black,
.clear_color = gfx.colors.black,
.clear_depth = 0,
.clear_stencil = 0,
});
@ -50,7 +50,7 @@ fn render(commands: coral.Commands, effects: ona.Write(Effects), app: ona.Read(o
const display_width: f32 = @floatFromInt(display.state.width);
const display_height: f32 = @floatFromInt(display.state.height);
const display_transform = coral.Transform2D{
const display_transform = gfx.Transform2D{
.origin = .{display_width / 2, display_height / 2},
.xbasis = .{display_width, 0},
.ybasis = .{0, display_height},
@ -84,9 +84,3 @@ fn render(commands: coral.Commands, effects: ona.Write(Effects), app: ona.Read(o
});
}
fn setup(world: *ona.World, events: ona.App.Events) !void {
try world.set_state(Effects{});
try world.on_event(events.load, ona.system_fn(load), .{.label = "load"});
try world.on_event(events.render, ona.system_fn(render), .{.label = "render actors"});
}

View File

@ -44,11 +44,11 @@ As the project evolves, dependencies on libraries external to the project codeba
After the repository has finished cloning, you will then want to also clone all Git submodule dependencies via the `git submodule update --init --recursive` command. If you are using a Git front-end, this may be a menu option or is handled *automagically*, it varies depending on the front-end.
Once all third-party tools and dependencies are satisfied, navigate to the root project folder and run `zig build demos` to build the demo executables. The resulting binaries will be placed in `/demos` will a name respective to the source file it was built from.
Once all third-party tools and dependencies are satisfied, navigate to the root project folder and run `zig build demos` to build the demo executables. The resulting binaries will be placed in `/demos` with a name respective to the source file it was built from.
To experiment without creating your own Zig project, you can create a new uniquely named source file in this directory and the `zig build demos` step will (re)build it along with everything else.
The unit testing suite for the engine modules (`src/ona`, `src/coral`, etc.) may be built and ran via `zig build tests`.
The unit testing suite for the engine modules (`src/ona`, `src/gfx`, etc.) may be built and ran via `zig build tests`.
Tests are ran by our continuous integration host so should these shouldn't fail locally without user intervention. If they do, feel free to report an issue via my email linked in my Sauce Control bio or through any other means of public communication I have.
@ -103,6 +103,6 @@ const app = b.addExecutable(.{
});
app.root_module.addImport("ona", ona_dependency.module("ona"));
app.root_module.addImport("coral", ona_dependency.module("coral"));
app.root_module.addImport("input", ona_dependency.module("input"));
app.root_module.addImport("gfx", ona_dependency.module("gfx"));
app.root_module.addImport("hid", ona_dependency.module("hid"));
```

View File

@ -1,13 +0,0 @@
const coral = @import("./coral.zig");
pub const black = greyscale(0);
pub const white = greyscale(1);
pub fn greyscale(v: f32) coral.Color {
return .{v, v, v, 1};
}
pub fn rgb(r: f32, g: f32, b: f32) coral.Color {
return .{r, g, b, 1};
}

View File

@ -1,4 +1,4 @@
const coral = @import("./coral.zig");
const gfx = @import("./gfx.zig");
const ona = @import("ona");
@ -24,7 +24,7 @@ pub const Effect = struct {
self.* = undefined;
}
pub fn init(desc: coral.Effect.Desc) spirv.Error!Effect {
pub fn init(desc: gfx.Effect.Desc) spirv.Error!Effect {
var spirv_arena = std.heap.ArenaAllocator.init(ona.heap.allocator);
defer {
@ -282,7 +282,7 @@ pub const Texture = struct {
self.* = undefined;
}
pub fn init(desc: coral.Texture.Desc) std.mem.Allocator.Error!Texture {
pub fn init(desc: gfx.Texture.Desc) std.mem.Allocator.Error!Texture {
const pixel_format = switch (desc.format) {
.rgba8 => sokol.gfx.PixelFormat.RGBA8,
.bgra8 => sokol.gfx.PixelFormat.BGRA8,
@ -386,7 +386,7 @@ pub const Texture = struct {
const TexturePool = ona.Pool(Texture);
pub fn create_effect(self: *Self, desc: coral.Effect.Desc) !coral.Effect {
pub fn create_effect(self: *Self, desc: gfx.Effect.Desc) !gfx.Effect {
var effect = try Effect.init(desc);
errdefer effect.deinit();
@ -394,7 +394,7 @@ pub fn create_effect(self: *Self, desc: coral.Effect.Desc) !coral.Effect {
return @enumFromInt(try self.effects.insert(effect));
}
pub fn create_texture(self: *Self, desc: coral.Texture.Desc) !coral.Texture {
pub fn create_texture(self: *Self, desc: gfx.Texture.Desc) !gfx.Texture {
var texture = try Texture.init(desc);
errdefer texture.deinit();
@ -422,7 +422,7 @@ pub fn deinit(self: *Self) void {
self.* = undefined;
}
pub fn destroy_effect(self: *Self, handle: coral.Effect) bool {
pub fn destroy_effect(self: *Self, handle: gfx.Effect) bool {
switch (handle) {
.default => {},
@ -438,7 +438,7 @@ pub fn destroy_effect(self: *Self, handle: coral.Effect) bool {
return true;
}
pub fn destroy_texture(self: *Self, handle: coral.Texture) bool {
pub fn destroy_texture(self: *Self, handle: gfx.Texture) bool {
switch (handle) {
.default => {},
@ -454,11 +454,11 @@ pub fn destroy_texture(self: *Self, handle: coral.Texture) bool {
return true;
}
pub fn get_effect(self: *Self, handle: coral.Effect) ?*Effect {
pub fn get_effect(self: *Self, handle: gfx.Effect) ?*Effect {
return self.effects.get(@intFromEnum(handle));
}
pub fn get_texture(self: *Self, handle: coral.Texture) ?*Texture {
pub fn get_texture(self: *Self, handle: gfx.Texture) ?*Texture {
return self.textures.get(@intFromEnum(handle));
}
@ -478,11 +478,11 @@ pub fn init() !Self {
}
};
assert.is_handle(coral.Effect.default, try pools.create_effect(.{
assert.is_handle(gfx.Effect.default, try pools.create_effect(.{
.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")),
}));
assert.is_handle(coral.Texture.default, try pools.create_texture(.{
assert.is_handle(gfx.Texture.default, try pools.create_texture(.{
.format = .rgba8,
.access = .{
@ -503,7 +503,7 @@ pub fn init() !Self {
},
}));
assert.is_handle(coral.Texture.backbuffer, try pools.create_texture(.{
assert.is_handle(gfx.Texture.backbuffer, try pools.create_texture(.{
.format = .rgba8,
.access = .{

13
src/gfx/colors.zig Normal file
View File

@ -0,0 +1,13 @@
const gfx = @import("./gfx.zig");
pub const black = greyscale(0);
pub const white = greyscale(1);
pub fn greyscale(v: f32) gfx.Color {
return .{v, v, v, 1};
}
pub fn rgb(r: f32, g: f32, b: f32) gfx.Color {
return .{r, g, b, 1};
}

View File

@ -477,9 +477,9 @@ pub fn setup(world: *ona.World, events: ona.App.Events) (error {Unsupported} ||
}
try world.set_state(Display{});
try world.on_event(events.pre_update, ona.system_fn(poll), .{.label = "poll coral"});
try world.on_event(events.exit, ona.system_fn(stop), .{.label = "stop coral"});
try world.on_event(events.finish, ona.system_fn(synchronize), .{.label = "synchronize coral"});
try world.on_event(events.pre_update, ona.system_fn(poll), .{.label = "poll gfx"});
try world.on_event(events.exit, ona.system_fn(stop), .{.label = "stop gfx"});
try world.on_event(events.finish, ona.system_fn(synchronize), .{.label = "synchronize gfx"});
}
pub fn stop(assets: ona.Write(Assets)) void {

View File

@ -1,6 +1,6 @@
const Resources = @import("./Resources.zig");
const coral = @import("./coral.zig");
const gfx = @import("./gfx.zig");
const ext = @cImport({
@cInclude("SDL2/SDL.h");
@ -20,12 +20,12 @@ const Frame = struct {
quad_vertex_buffer: sokol.gfx.Buffer,
drawn_count: usize = 0,
flushed_count: usize = 0,
current_source_texture: coral.Texture = .default,
current_target_texture: coral.Texture = .backbuffer,
current_effect: coral.Effect = .default,
current_source_texture: gfx.Texture = .default,
current_target_texture: gfx.Texture = .backbuffer,
current_effect: gfx.Effect = .default,
const DrawTexture = extern struct {
transform: coral.Transform2D,
transform: gfx.Transform2D,
tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)),
depth: f32 = 0,
texture_offset: @Vector(2, f32) = @splat(0),
@ -73,7 +73,7 @@ const Frame = struct {
};
}
pub fn draw_texture(self: *Frame, resources: *Resources, command: coral.Commands.DrawTextureCommand) !void {
pub fn draw_texture(self: *Frame, resources: *Resources, command: gfx.Commands.DrawTextureCommand) !void {
if (command.texture != self.current_source_texture) {
self.flush(resources);
}
@ -173,7 +173,7 @@ const Frame = struct {
}
}
pub fn set_effect(self: *Frame, resources: *Resources, command: coral.Commands.SetEffectCommand) void {
pub fn set_effect(self: *Frame, resources: *Resources, command: gfx.Commands.SetEffectCommand) void {
if (command.effect != self.current_effect) {
self.flush(resources);
}
@ -185,7 +185,7 @@ const Frame = struct {
}
}
pub fn set_target(self: *Frame, resources: *Resources, command: coral.Commands.SetTargetCommand) void {
pub fn set_target(self: *Frame, resources: *Resources, command: gfx.Commands.SetTargetCommand) void {
sokol.gfx.endPass();
var pass = sokol.gfx.Pass{
@ -237,7 +237,7 @@ const vertex_indices = .{
.instance = 1,
};
fn orthographic_projection(near: f32, far: f32, viewport: coral.Rect) Matrix(4, f32) {
fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) Matrix(4, f32) {
const width = viewport.right - viewport.left;
const height = viewport.bottom - viewport.top;
@ -249,7 +249,7 @@ fn orthographic_projection(near: f32, far: f32, viewport: coral.Rect) Matrix(4,
};
}
pub fn process_work(pending_work: *coral.Assets.WorkQueue, window: *ext.SDL_Window) !void {
pub fn process_work(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window) !void {
const context = configure_and_create: {
var result = @as(c_int, 0);

View File

@ -1,4 +1,4 @@
const coral = @import("./coral.zig");
const gfx = @import("./gfx.zig");
const ext = @cImport({
@cInclude("spirv-cross/spirv_cross_c.h");

View File

@ -128,11 +128,17 @@ const Schedule = struct {
graph: Graph,
arena: std.heap.ArenaAllocator,
system_id_nodes: ona.map.Hashed(usize, NodeBundle, ona.map.usize_traits),
inserted_systems: ona.stack.Sequential(InsertedSystem) = .{},
read_write_resource_id_nodes: ResourceNodeBundle,
read_only_resource_id_nodes: ResourceNodeBundle,
parallel_work_bundles: ParallelNodeBundles,
blocking_work: NodeBundle,
const InsertedSystem = struct {
info: *const ona.SystemInfo,
order: ona.SystemOrder,
};
pub fn deinit(self: *Schedule, world: *Self) void {
{
var nodes = self.system_id_nodes.entries();
@ -184,12 +190,91 @@ const Schedule = struct {
self.system_id_nodes.deinit();
self.read_write_resource_id_nodes.deinit();
self.read_only_resource_id_nodes.deinit();
self.inserted_systems.deinit();
self.arena.deinit();
}
pub fn run(self: *Schedule, world: *Self) anyerror!void {
if (self.is_invalidated()) {
const work = struct {
fn bind_systems(schedule: *Schedule, world_: *Self) !void {
for (schedule.inserted_systems.values) |system| {
const nodes = lazily_create: {
const system_id = @intFromPtr(system.info);
break: lazily_create schedule.system_id_nodes.get(system_id) orelse insert: {
std.debug.assert(try schedule.system_id_nodes.emplace(system_id, .{
.allocator = schedule.system_id_nodes.allocator,
}));
break: insert schedule.system_id_nodes.get(system_id).?;
};
};
const dependencies = init: {
const total_run_orders = system.order.run_after.len + system.order.run_before.len;
const dependencies = try ona.heap.allocator.alloc(Dependency, total_run_orders);
var dependencies_written = @as(usize, 0);
for (system.order.run_after) |after_system| {
dependencies[dependencies_written] = .{
.id = @intFromPtr(after_system),
.kind = .after,
};
dependencies_written += 1;
}
for (system.order.run_before) |before_system| {
dependencies[dependencies_written] = .{
.id = @intFromPtr(before_system),
.kind = .before,
};
dependencies_written += 1;
}
break: init dependencies;
};
errdefer ona.heap.allocator.free(dependencies);
const label = try ona.heap.allocator.dupeZ(u8, if (system.order.label.len == 0) "anonymous system" else system.order.label);
errdefer ona.heap.allocator.free(label);
const node = try schedule.graph.append(.{
.info = system.info,
.label = label,
.dependencies = dependencies,
.resource_accesses = .{.allocator = ona.heap.allocator},
});
const system_node = schedule.graph.get_ptr(node).?;
const system_parameter_states = system_node.parameter_states[0 .. system.info.parameter_count];
errdefer {
for (system.info.used_parameters(), system_parameter_states) |parameter, state| {
parameter.unbind(schedule.arena.allocator(), state, .{
.world = world_,
});
}
std.debug.assert(schedule.graph.remove_node(node) != null);
}
for (system_parameter_states, system.info.used_parameters()) |*state, parameter| {
state.* = try parameter.bind(schedule.arena.allocator(), .{
.world = world_,
.node = node,
.systems = schedule,
});
}
try nodes.push_grow(node);
}
}
fn regenerate_graph(schedule: *Schedule) !void {
schedule.graph.clear_edges();
@ -311,6 +396,7 @@ const Schedule = struct {
}
};
try work.bind_systems(self, world);
try work.regenerate_graph(self);
try work.sort(self);
}
@ -375,7 +461,12 @@ const Schedule = struct {
};
}
pub fn invalidate_work(self: *Schedule) void {
pub fn insert(self: *Schedule, info: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void {
try self.inserted_systems.push_grow(.{
.info = info,
.order = order,
});
self.blocking_work.clear();
for (self.parallel_work_bundles.values) |*bundle| {
@ -388,83 +479,6 @@ const Schedule = struct {
pub fn is_invalidated(self: Schedule) bool {
return self.parallel_work_bundles.is_empty() and self.blocking_work.is_empty();
}
pub fn then(self: *Schedule, world: *Self, info: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void {
const nodes = lazily_create: {
const system_id = @intFromPtr(info);
break: lazily_create self.system_id_nodes.get(system_id) orelse insert: {
std.debug.assert(try self.system_id_nodes.emplace(system_id, .{
.allocator = self.system_id_nodes.allocator,
}));
break: insert self.system_id_nodes.get(system_id).?;
};
};
const dependencies = init: {
const total_run_orders = order.run_after.len + order.run_before.len;
const dependencies = try ona.heap.allocator.alloc(Dependency, total_run_orders);
var dependencies_written = @as(usize, 0);
for (order.run_after) |after_system| {
dependencies[dependencies_written] = .{
.id = @intFromPtr(after_system),
.kind = .after,
};
dependencies_written += 1;
}
for (order.run_before) |before_system| {
dependencies[dependencies_written] = .{
.id = @intFromPtr(before_system),
.kind = .before,
};
dependencies_written += 1;
}
break: init dependencies;
};
errdefer ona.heap.allocator.free(dependencies);
const label = try ona.heap.allocator.dupeZ(u8, if (order.label.len == 0) "anonymous system" else order.label);
errdefer ona.heap.allocator.free(label);
const node = try self.graph.append(.{
.info = info,
.label = label,
.dependencies = dependencies,
.resource_accesses = .{.allocator = ona.heap.allocator},
});
const system = self.graph.get_ptr(node).?;
errdefer {
for (info.used_parameters(), system.parameter_states[0 .. info.parameter_count]) |parameter, state| {
parameter.unbind(self.arena.allocator(), state, .{
.world = world,
});
}
std.debug.assert(self.graph.remove_node(node) != null);
}
for (system.parameter_states[0 .. info.parameter_count], info.used_parameters()) |*state, parameter| {
state.* = try parameter.bind(self.arena.allocator(), .{
.world = world,
.node = node,
.systems = self,
});
}
try nodes.push_grow(node);
self.invalidate_work();
}
};
const Self = @This();
@ -598,7 +612,7 @@ pub fn init(thread_count: u32) std.Thread.SpawnError!Self {
}
pub fn on_event(self: *Self, event: Event, action: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void {
try self.event_systems.values[@intFromEnum(event)].then(self, action, order);
try self.event_systems.values[@intFromEnum(event)].insert(action, order);
}
pub fn run_event(self: *Self, event: Event) anyerror!void {

View File

@ -44,6 +44,169 @@ pub const App = struct {
exit: World.Event,
};
pub const Setup = struct {
pub const Event = enum {
load,
pre_update,
update,
post_update,
render,
finish,
exit,
};
step: fn (*World, Events) anyerror!void = noop,
fn noop(_: *World, _: Events) !void {}
pub fn build(self: Setup) fn () anyerror!void {
const Start = struct {
fn main() anyerror!void {
defer {
heap.trace_leaks();
}
parse_args: for (std.os.argv[1 ..]) |arg| {
const arg_span = std.mem.span(arg);
const arg_split_index = std.mem.indexOfScalar(u8, arg_span, '=') orelse arg_span.len;
const arg_name = arg_span[0 .. arg_split_index];
for (LaunchFlag.values) |value| {
const name = @tagName(value);
if (!std.mem.eql(u8, arg_name, name)) {
continue;
}
LaunchFlag.args[@intFromEnum(value)] =
if (arg_split_index == arg_span.len)
name
else
arg_span[arg_split_index ..];
continue: parse_args;
}
}
const cpu_count = @as(u32, @intCast(std.math.clamp(std.Thread.getCpuCount() catch |cpu_count_error| {
@panic(switch (cpu_count_error) {
error.PermissionDenied => "permission denied retrieving CPU count",
error.SystemResources => "system resources are preventing retrieval of the CPU count",
error.Unexpected => "unexpected failure retrieving CPU count",
});
}, 0, std.math.maxInt(u32))));
var world = try World.init(scalars.fractional(cpu_count, @as(f32, 0.75)) orelse 0);
defer world.deinit();
const events = App.Events{
.load = try world.create_event("load"),
.pre_update = try world.create_event("pre-update"),
.update = try world.create_event("update"),
.post_update = try world.create_event("post-update"),
.render = try world.create_event("render"),
.finish = try world.create_event("finish"),
.exit = try world.create_event("exit"),
};
const app = try world.set_get_state(App{
.events = &events,
.target_frame_time = 1.0 / 60.0,
.elapsed_time = 0,
.is_running = true,
});
try self.step(&world, events);
try world.run_event(events.load);
const ticks_initial = std.time.milliTimestamp();
var ticks_previous = ticks_initial;
var accumulated_time = @as(f64, 0);
while (app.is_running) {
const ticks_current = std.time.milliTimestamp();
const milliseconds_per_second = 1000.0;
const delta_time = @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second;
app.elapsed_time = @as(f64, @floatFromInt(ticks_current - ticks_initial)) / milliseconds_per_second;
ticks_previous = ticks_current;
accumulated_time += delta_time;
try world.run_event(events.pre_update);
while (accumulated_time >= app.target_frame_time) : (accumulated_time -= app.target_frame_time) {
try world.run_event(events.update);
}
try world.run_event(events.post_update);
try world.run_event(events.render);
try world.run_event(events.finish);
}
try world.run_event(events.exit);
}
};
return Start.main;
}
pub fn with_module(self: Setup, comptime Module: type) Setup {
if (!@hasDecl(Module, "setup")) {
@compileError("`Module` argument must have a setup fn declaration");
}
const WithState = struct {
fn step(world: *World, events: App.Events) !void {
try Module.setup(world, events);
try self.step(world, events);
}
};
return .{
.step = WithState.step,
};
}
pub fn with_state(self: Setup, comptime state: anytype) Setup {
const WithState = struct {
fn step(world: *World, events: App.Events) !void {
try world.set_state(state);
try self.step(world, events);
}
};
return .{
.step = WithState.step,
};
}
pub fn with_system(self: Setup, comptime event: Event, comptime info: *const SystemInfo, comptime order: SystemOrder) Setup {
const WithState = struct {
fn step(world: *World, events: App.Events) !void {
const app_event = switch (event) {
.load => events.load,
.pre_update => events.pre_update,
.update => events.update,
.post_update => events.post_update,
.render => events.render,
.finish => events.finish,
.exit => events.exit,
};
try world.on_event(app_event, info, order);
try self.step(world, events);
}
};
return .{
.step = WithState.step,
};
}
};
pub const setup = Setup{};
pub fn quit(self: *App) void {
self.is_running = false;
}
@ -161,7 +324,7 @@ pub fn Exclusive(comptime values: []const type) type {
.Optional => has_state,
else => has_state orelse {
@panic(std.fmt.comptimePrint("attempt to use exclusive {s} that has not yet been set", .{
@panic(std.fmt.comptimePrint("attempt to use exclusive {s} that has not been set yet", .{
@typeName(Value),
}));
},
@ -190,17 +353,6 @@ pub const LaunchFlag = enum {
const values = std.enums.values(LaunchFlag);
};
pub const Options = struct {
tick_rate: u64,
execution: Execution,
middleware: []const *const Setup,
pub const Execution = union (enum) {
single_threaded,
thread_share: f32,
};
};
pub fn Params(comptime Value: type) type {
if (!@hasDecl(Value, "Param")) {
@compileError("System parameters must have a Params type declaration");
@ -430,8 +582,6 @@ pub fn Send(comptime Message: type) type {
};
}
pub const Setup = fn (*World, App.Events) anyerror!void;
pub const ShareInfo = struct {
thread_restriction: ThreadRestriction,
read_only: bool,
@ -497,7 +647,7 @@ pub fn Shared(comptime Value: type, comptime info: ShareInfo) type {
.Optional => state,
else => state orelse {
@panic(std.fmt.comptimePrint("attempt to use {s}{s} {s} that has not yet been set", .{
@panic(std.fmt.comptimePrint("attempt to use {s}{s} {s} that has not been set yet", .{
thread_restriction_name,
if (info.read_only) "read-only" else "read-write",
@typeName(Value),
@ -688,108 +838,6 @@ fn parameter_type(comptime Value: type) *const SystemInfo.Parameter {
};
}
pub fn start(comptime setup: Setup, comptime options: Options) fn () anyerror!void {
const Start = struct {
fn main() anyerror!void {
defer {
heap.trace_leaks();
}
parse_args: for (std.os.argv[1 ..]) |arg| {
const arg_span = std.mem.span(arg);
const arg_split_index = std.mem.indexOfScalar(u8, arg_span, '=') orelse arg_span.len;
const arg_name = arg_span[0 .. arg_split_index];
for (LaunchFlag.values) |value| {
const name = @tagName(value);
if (!std.mem.eql(u8, arg_name, name)) {
continue;
}
LaunchFlag.args[@intFromEnum(value)] =
if (arg_split_index == arg_span.len)
name
else
arg_span[arg_split_index ..];
continue: parse_args;
}
}
var world = try switch (options.execution) {
.single_threaded => World.init(0),
.thread_share => |thread_share| init: {
const cpu_count = @as(u32, @intCast(std.math.clamp(std.Thread.getCpuCount() catch |cpu_count_error| {
@panic(switch (cpu_count_error) {
error.PermissionDenied => "permission denied retrieving CPU count",
error.SystemResources => "system resources are preventing retrieval of the CPU count",
error.Unexpected => "unexpected failure retrieving CPU count",
});
}, 0, std.math.maxInt(u32))));
break: init World.init(scalars.fractional(cpu_count, thread_share) orelse 0);
},
};
defer world.deinit();
const events = App.Events{
.load = try world.create_event("load"),
.pre_update = try world.create_event("pre-update"),
.update = try world.create_event("update"),
.post_update = try world.create_event("post-update"),
.render = try world.create_event("render"),
.finish = try world.create_event("finish"),
.exit = try world.create_event("exit"),
};
const app = try world.set_get_state(App{
.events = &events,
.target_frame_time = 1.0 / @as(f64, @floatFromInt(options.tick_rate)),
.elapsed_time = 0,
.is_running = true,
});
inline for (options.middleware) |setup_middleware| {
try setup_middleware(&world, events);
}
try setup(&world, events);
try world.run_event(events.load);
const ticks_initial = std.time.milliTimestamp();
var ticks_previous = ticks_initial;
var accumulated_time = @as(f64, 0);
while (app.is_running) {
const ticks_current = std.time.milliTimestamp();
const milliseconds_per_second = 1000.0;
const delta_time = @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second;
app.elapsed_time = @as(f64, @floatFromInt(ticks_current - ticks_initial)) / milliseconds_per_second;
ticks_previous = ticks_current;
accumulated_time += delta_time;
try world.run_event(events.pre_update);
while (accumulated_time >= app.target_frame_time) : (accumulated_time -= app.target_frame_time) {
try world.run_event(events.update);
}
try world.run_event(events.post_update);
try world.run_event(events.render);
try world.run_event(events.finish);
}
try world.run_event(events.exit);
}
};
return Start.main;
}
pub fn system_fn(comptime call: anytype) *const SystemInfo {
const Call = @TypeOf(call);