From 7e8691c05014a0f64246b94ff432af1e9bb4aa13 Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 29 Jul 2024 23:50:40 +0100 Subject: [PATCH] Add GUI module --- .vscode/launch.json | 2 +- build.zig | 14 + demos/canvas.zig | 31 ++ demos/effects.zig | 39 +- demos/inputs.zig | 49 +- src/gfx/Resources.zig | 17 +- src/gfx/gfx.zig | 418 +++++++++--------- src/gfx/rendering.zig | 148 ++++--- src/gfx/shaders/2d_default.vert | 29 -- .../{2d_default.frag => draw_texture.frag} | 0 src/gfx/shaders/draw_texture.vert | 42 ++ src/gfx/spirv.zig | 4 +- src/gui/gui.zig | 234 ++++++++++ src/hid/hid.zig | 4 +- src/ona/World.zig | 79 ++-- src/ona/map.zig | 2 +- src/ona/ona.zig | 332 ++++++-------- src/ona/stack.zig | 14 + 18 files changed, 859 insertions(+), 599 deletions(-) create mode 100644 demos/canvas.zig delete mode 100644 src/gfx/shaders/2d_default.vert rename src/gfx/shaders/{2d_default.frag => draw_texture.frag} (100%) create mode 100644 src/gfx/shaders/draw_texture.vert create mode 100644 src/gui/gui.zig diff --git a/.vscode/launch.json b/.vscode/launch.json index 62345d6..a9c3ffc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "name": "Runner", "type": "gdb", "request": "launch", - "target": "${workspaceRoot}/demos/effects.out", + "target": "${workspaceRoot}/demos/canvas.out", "cwd": "${workspaceRoot}/demos/", "valuesFormatting": "prettyPrinters", "preLaunchTask": "Build All" diff --git a/build.zig b/build.zig index 490af7b..afc70bb 100644 --- a/build.zig +++ b/build.zig @@ -254,6 +254,20 @@ pub fn build(b: *std.Build) !void { gfx_module.link_libc = true; + _ = try project.add_module(b, "gui", .{ + .imports = &.{ + .{ + .name = "ona", + .module = ona_module, + }, + + .{ + .name = "gfx", + .module = gfx_module, + }, + }, + }); + project.find_demos(b.step("demos", "Build demos")); project.find_tests(b.step("tests", "Build and run tests")); } diff --git a/demos/canvas.zig b/demos/canvas.zig new file mode 100644 index 0000000..654b764 --- /dev/null +++ b/demos/canvas.zig @@ -0,0 +1,31 @@ +const gfx = @import("gfx"); + +const gui = @import("gui"); + +const hid = @import("hid"); + +const ona = @import("ona"); + +pub const main = ona.App.game + .with_module(gfx) + .with_module(hid) + .with_module(gui) + .with_system(.load, ona.system_fn(load), .{ .label = "Hello Ona" }).build(); + +pub fn load(canvas: ona.Write(gui.Canvas)) !void { + const item = try canvas.state.append("", .{ + .left = 0, + .top = 0, + .width = 256, + .height = 256, + }); + + canvas.state.set_block(item, .{ + .has_background = .default, + .has_color = gfx.colors.purple, + }); + + try canvas.state.set_label(item, .{ + .has_text = "Hello, world", + }); +} diff --git a/demos/effects.zig b/demos/effects.zig index 8b22d54..a1d458a 100644 --- a/demos/effects.zig +++ b/demos/effects.zig @@ -56,23 +56,23 @@ fn load(display: ona.Write(gfx.Display), effects: ona.Write(Effects), assets: on }; } -pub const main = ona.App.setup. - with_module(gfx). - with_state(Effects{}). - with_system(.load, ona.system_fn(load), .{.label = "load effects"}). - with_system(.update, ona.system_fn(update), .{.label = "update effects"}). - with_system(.render, ona.system_fn(render), .{.label = "render effects"}).build(); +pub const main = ona.App.game + .with_module(gfx) + .with_state(Effects{}) + .with_system(.load, ona.system_fn(load), .{.label = "load effects"}) + .with_system(.update, ona.system_fn(update), .{.label = "update effects"}) + .with_system(.render, ona.system_fn(render), .{.label = "render effects"}).build(); -fn update(effects: ona.Write(Effects), app: ona.Read(ona.App)) void { +fn update(effects: ona.Write(Effects), loop: ona.Read(ona.Loop)) void { const update_seconds = 5; - if ((app.state.elapsed_time - effects.state.last_time) > update_seconds) { + if ((loop.state.elapsed_time - effects.state.last_time) > update_seconds) { effects.state.image_index = (effects.state.image_index + 1) % effects.state.image_textures.len; - effects.state.last_time = app.state.elapsed_time; + effects.state.last_time = loop.state.elapsed_time; } } -fn render(commands: gfx.Commands, effects: ona.Write(Effects), app: ona.Read(ona.App), display: ona.Write(gfx.Display)) !void { +fn render(commands: gfx.Commands(.background), effects: ona.Write(Effects), loop: ona.Read(ona.Loop), display: ona.Write(gfx.Display)) !void { try commands.set_target(.{ .texture = effects.state.render_texture, .clear_color = gfx.colors.black, @@ -80,26 +80,21 @@ fn render(commands: gfx.Commands, effects: ona.Write(Effects), app: ona.Read(ona .clear_stencil = 0, }); - const display_width: f32 = @floatFromInt(display.state.width); - const display_height: f32 = @floatFromInt(display.state.height); - - const display_transform = gfx.transform_2d(.{ - .translation = .{display_width / 2, display_height / 2}, - }); + const width: f32 = @floatFromInt(display.state.width); + const height: f32 = @floatFromInt(display.state.height); try commands.draw_texture(.{ .texture = effects.state.image_textures[effects.state.image_index], - .transform = display_transform, - .resolution = .{display.state.width, display.state.height}, + .size = .{width, height}, }); try commands.set_effect(.{ .effect = effects.state.crt_effect, .properties = std.mem.asBytes(&CRT{ - .width = display_width, - .height = display_height, - .time = @floatCast(app.state.elapsed_time), + .width = width, + .height = height, + .time = @floatCast(loop.state.elapsed_time), }), }); @@ -111,7 +106,7 @@ fn render(commands: gfx.Commands, effects: ona.Write(Effects), app: ona.Read(ona try commands.draw_texture(.{ .texture = effects.state.render_texture, - .transform = display_transform, + .size = .{width, height}, }); } diff --git a/demos/inputs.zig b/demos/inputs.zig index 16a34e0..af87f2c 100644 --- a/demos/inputs.zig +++ b/demos/inputs.zig @@ -9,7 +9,8 @@ const std = @import("std"); const Spawned = struct { visual: struct { color: gfx.Color, - transform: gfx.Transform2D, + position: gfx.Vector2, + rotation: f32, }, lifetime_seconds: f32, @@ -23,7 +24,6 @@ const Visuals = struct { fn cleanup(visuals: ona.Write(Visuals)) !void { visuals.state.spawned.deinit(); - visuals.state.spawned.deinit(); } fn load(visuals: ona.Write(Visuals)) !void { @@ -32,14 +32,14 @@ fn load(visuals: ona.Write(Visuals)) !void { try visuals.state.spawned.grow(initial_spawn_capacity); } -pub const main = ona.App.setup. - with_module(gfx). - with_module(hid). - with_state(Visuals{.random = std.Random.Xoroshiro128.init(47342563891212)}). - with_system(.load, ona.system_fn(load), .{.label = "load visuals"}). - with_system(.update, ona.system_fn(update), .{.label = "spawn visuals"}). - with_system(.render, ona.system_fn(render), .{.label = "render visuals"}). - with_system(.exit, ona.system_fn(cleanup), .{.label = "clean up visuals"}).build(); +pub const main = ona.App.game + .with_module(gfx) + .with_module(hid) + .with_state(Visuals{.random = std.Random.Xoroshiro128.init(47342563891212)}) + .with_system(.load, ona.system_fn(load), .{.label = "load visuals"}) + .with_system(.update, ona.system_fn(update), .{.label = "spawn visuals"}) + .with_system(.render, ona.system_fn(render), .{.label = "render visuals"}) + .with_system(.exit, ona.system_fn(cleanup), .{.label = "clean up visuals"}).build(); fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display: ona.Read(gfx.Display)) !void { update_spawned(&visuals.state.spawned); @@ -47,7 +47,6 @@ fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display: const random = visuals.state.random.random(); const width: f32 = @floatFromInt(display.state.width); const height: f32 = @floatFromInt(display.state.height); - const icon_scale = .{8, 8}; for (events.messages()) |event| { switch (event) { @@ -57,12 +56,8 @@ fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display: .visual = .{ .color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)}, - - .transform = gfx.transform_2d(.{ - .translation = .{width * random.float(f32), height}, - .rotation = std.math.pi * random.float(f32), - .scale = icon_scale, - }), + .position = .{width * random.float(f32), height}, + .rotation = std.math.pi * random.float(f32), }, }); }, @@ -73,12 +68,8 @@ fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display: .visual = .{ .color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)}, - - .transform = gfx.transform_2d(.{ - .translation = visuals.state.mouse_position, - .rotation = std.math.pi * random.float(f32), - .scale = icon_scale, - }), + .position = visuals.state.mouse_position, + .rotation = std.math.pi * random.float(f32), }, }); }, @@ -96,7 +87,7 @@ fn update_spawned(spawned: *ona.stack.Parallel(Spawned)) void { const float_speed = 6; for (spawned.values.slice(.visual)) |*visual| { - visual.transform = visual.transform.translated(.{0, -float_speed}); + visual.position -= .{0, float_speed}; } { @@ -119,11 +110,13 @@ fn update_spawned(spawned: *ona.stack.Parallel(Spawned)) void { } } -fn render(visuals: ona.Write(Visuals), commands: gfx.Commands) !void { +fn render(visuals: ona.Write(Visuals), commands: gfx.Commands(.foreground)) !void { for (visuals.state.spawned.values.slice(.visual)) |visual| { - try commands.draw_rect(.{ - .transform = visual.transform, - .color = visual.color, + try commands.draw_texture(.{ + .anchor = @splat(0.5), + .position = visual.position, + .tint = visual.color, + .size = @as(gfx.Vector2, @splat(64)), }); } } diff --git a/src/gfx/Resources.zig b/src/gfx/Resources.zig index 67caff3..8bd91cc 100644 --- a/src/gfx/Resources.zig +++ b/src/gfx/Resources.zig @@ -38,7 +38,7 @@ pub const Effect = struct { }, .vertex_source = .{ - .ops = &spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")), + .ops = &spirv.to_ops(@embedFile("./shaders/draw_texture.vert.spv")), }, .fragment_source = .{ @@ -197,12 +197,12 @@ pub const Effect = struct { }; attrs[2] = .{ - .format = .FLOAT2, + .format = .FLOAT, .buffer_index = 1, }; attrs[3] = .{ - .format = .FLOAT2, + .format = .FLOAT, .buffer_index = 1, }; @@ -212,12 +212,12 @@ pub const Effect = struct { }; attrs[5] = .{ - .format = .UBYTE4N, + .format = .FLOAT2, .buffer_index = 1, }; attrs[6] = .{ - .format = .FLOAT, + .format = .FLOAT2, .buffer_index = 1, }; @@ -226,6 +226,11 @@ pub const Effect = struct { .buffer_index = 1, }; + attrs[8] = .{ + .format = .FLOAT4, + .buffer_index = 1, + }; + break: get attrs; }, @@ -466,7 +471,7 @@ pub fn init() !Self { } assert.is_default_handle(try pools.create_effect(.{ - .fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")), + .fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/draw_texture.frag.spv")), })); assert.is_default_handle(try pools.create_texture(try descs.solid_texture(.{ diff --git a/src/gfx/gfx.zig b/src/gfx/gfx.zig index dd7bff9..dd0d0d4 100644 --- a/src/gfx/gfx.zig +++ b/src/gfx/gfx.zig @@ -27,40 +27,7 @@ pub const Assets = struct { load_file: *const fn (*std.heap.ArenaAllocator, ona.files.Storage, []const u8) LoadFileError!Texture.Desc, }; - pub const WorkQueue = ona.asyncio.BlockingQueue(1024, union (enum) { - load_effect: LoadEffectWork, - load_texture: LoadTextureWork, - render_frame: RenderFrameWork, - shutdown, - unload_effect: UnloadEffectWork, - unload_texture: UnloadTextureWork, - - const LoadEffectWork = struct { - desc: Effect.Desc, - loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Effect), - }; - - const LoadTextureWork = struct { - desc: Texture.Desc, - loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Texture), - }; - - const RenderFrameWork = struct { - clear_color: Color, - width: u16, - height: u16, - finished: *std.Thread.ResetEvent, - has_command_params: ?*ona.Params(Commands).Node, - }; - - const UnloadEffectWork = struct { - handle: Effect, - }; - - const UnloadTextureWork = struct { - handle: Texture, - }; - }); + pub const WorkQueue = ona.asyncio.BlockingQueue(1024, Work); fn deinit(self: *Assets) void { self.pending_work.enqueue(.shutdown); @@ -72,12 +39,10 @@ pub const Assets = struct { self.* = undefined; } - fn init() !Assets { + fn init(width: u16, height: u16) !Assets { const window = create: { const position = ext.SDL_WINDOWPOS_CENTERED; const flags = ext.SDL_WINDOW_OPENGL; - const width = 640; - const height = 480; break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse { return error.Unsupported; @@ -138,139 +103,171 @@ pub const Assets = struct { } pub const thread_restriction = .main; + + pub fn unload_effect(self: *Assets, handle: Effect) void { + self.pending_work.enqueue(.{.unload_effect = handle}); + } + + pub fn unload_texture(self: *Assets, handle: Texture) void { + self.pending_work.enqueue(.{.unload_texture = handle}); + } }; pub const Color = @Vector(4, f32); -pub const Commands = struct { - pending: *List, +pub const Command = union (enum) { + draw_font: DrawFont, + draw_texture: DrawTexture, + set_effect: SetEffect, + set_target: SetTarget, - pub const Command = union (enum) { - draw_rect: DrawRectCommand, - draw_texture: DrawTextureCommand, - set_effect: SetEffectCommand, - set_target: SetTargetCommand, + pub const DrawFont = struct { + position: Vector2 = @splat(0), + size: Vector2, + text: []const u8, + font: Font, + tint: Color, + depth: f32, }; - pub const DrawRectCommand = struct { - color: Color, - transform: Transform2D, + pub const DrawTexture = struct { + anchor: Vector2 = @splat(0), + size: ?Vector2 = null, + source: Rect = .{.left = 0, .top = 0, .right = 1, .bottom = 1}, + position: Vector2 = @splat(0), + tint: Color = colors.white, + texture: Texture = .default, + rotation: f32 = 0, + depth: f32 = 0, }; - pub const DrawTextureCommand = struct { - texture: Texture, - transform: Transform2D, - resolution: ?@Vector(2, u16) = null, - }; - - pub const SetEffectCommand = struct { + pub const SetEffect = struct { effect: Effect, properties: []const u8, }; - pub const SetTargetCommand = struct { + pub const SetTarget = struct { texture: ?Texture = null, clear_color: ?Color, clear_depth: ?f32, clear_stencil: ?u8, }; +}; - pub const List = struct { - arena: std.heap.ArenaAllocator, - stack: ona.stack.Sequential(Command), +pub fn Commands(comptime layer: Layer) type { + return struct { + pending: *List, - fn clear(self: *List) void { - self.stack.clear(); + pub const List = struct { + arena: std.heap.ArenaAllocator, + stack: ona.stack.Sequential(Command), - if (!self.arena.reset(.retain_capacity)) { - std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{}); + fn clear(self: *List) void { + self.stack.clear(); + + if (!self.arena.reset(.retain_capacity)) { + std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{}); + } } - } - fn deinit(self: *List) void { - self.arena.deinit(); - self.stack.deinit(); + fn deinit(self: *List) void { + self.arena.deinit(); + self.stack.deinit(); - self.* = undefined; - } + self.* = undefined; + } - fn init(allocator: std.mem.Allocator) List { + fn init(allocator: std.mem.Allocator) List { + return .{ + .arena = std.heap.ArenaAllocator.init(allocator), + .stack = .{.allocator = allocator}, + }; + } + }; + + pub const Param = struct { + swap_lists: [2]List, + swap_state: u1 = 0, + + fn deinit(self: *Param) void { + for (&self.swap_lists) |*list| { + list.deinit(); + } + + self.* = undefined; + } + + fn pending_list(self: *Param) *List { + return &self.swap_lists[self.swap_state]; + } + + fn rotate(self: *Param) void { + const swapped_state = self.swap_state ^ 1; + + self.swap_lists[swapped_state].clear(); + + self.swap_state = swapped_state; + } + + pub fn submitted_commands(self: Param) []const Command { + return self.swap_lists[self.swap_state ^ 1].stack.values; + } + }; + + const Self = @This(); + + pub fn bind(_: ona.World.BindContext) std.mem.Allocator.Error!Param { return .{ - .arena = std.heap.ArenaAllocator.init(allocator), - .stack = .{.allocator = allocator}, + .swap_lists = .{ + List.init(ona.heap.allocator), + List.init(ona.heap.allocator), + }, }; } - }; - pub const Param = struct { - swap_lists: [2]List, - swap_state: u1 = 0, - - fn deinit(self: *Param) void { - for (&self.swap_lists) |*list| { - list.deinit(); - } - - self.* = undefined; + pub fn init(param: *Param) Self { + return .{ + .pending = param.pending_list(), + }; } - fn pending_list(self: *Param) *List { - return &self.swap_lists[self.swap_state]; + pub fn draw_font(self: Self, command: Command.DrawFont) std.mem.Allocator.Error!void { + try self.pending.stack.push_grow(.{ + .draw_font = .{ + .text = try self.pending.arena.allocator().dupe(u8, command.text), + .position = command.position, + .size = command.size, + .font = command.font, + .tint = command.tint, + .depth = command.depth, + }, + }); } - fn rotate(self: *Param) void { - const swapped_state = self.swap_state ^ 1; - - self.swap_lists[swapped_state].clear(); - - self.swap_state = swapped_state; + pub fn draw_texture(self: Self, command: Command.DrawTexture) std.mem.Allocator.Error!void { + try self.pending.stack.push_grow(.{.draw_texture = command}); } - pub fn submitted_commands(self: Param) []const Command { - return self.swap_lists[self.swap_state ^ 1].stack.values; + pub fn set_effect(self: Self, command: Command.SetEffect) std.mem.Allocator.Error!void { + try self.pending.stack.push_grow(.{ + .set_effect = .{ + .properties = try self.pending.arena.allocator().dupe(u8, command.properties), + .effect = command.effect, + }, + }); + } + + pub const _ = layer; + + pub fn unbind(param: *Param, _: ona.World.UnbindContext) void { + param.deinit(); + } + + pub fn set_target(self: Self, command: Command.SetTarget) std.mem.Allocator.Error!void { + try self.pending.stack.push_grow(.{.set_target = command}); } }; - - pub fn bind(_: ona.World.BindContext) std.mem.Allocator.Error!Param { - return .{ - .swap_lists = .{ - List.init(ona.heap.allocator), - List.init(ona.heap.allocator), - }, - }; - } - - pub fn init(param: *Param) Commands { - return .{ - .pending = param.pending_list(), - }; - } - - pub fn draw_rect(self: Commands, command: DrawRectCommand) std.mem.Allocator.Error!void { - try self.pending.stack.push_grow(.{.draw_rect = command}); - } - - pub fn draw_texture(self: Commands, command: DrawTextureCommand) std.mem.Allocator.Error!void { - try self.pending.stack.push_grow(.{.draw_texture = command}); - } - - pub fn set_effect(self: Commands, command: SetEffectCommand) std.mem.Allocator.Error!void { - try self.pending.stack.push_grow(.{ - .set_effect = .{ - .properties = try self.pending.arena.allocator().dupe(u8, command.properties), - .effect = command.effect, - }, - }); - } - - pub fn unbind(param: *Param, _: ona.World.UnbindContext) void { - param.deinit(); - } - - pub fn set_target(self: Commands, command: SetTargetCommand) std.mem.Allocator.Error!void { - try self.pending.stack.push_grow(.{.set_target = command}); - } -}; +} pub const Descs = struct { arena: std.heap.ArenaAllocator, @@ -431,7 +428,24 @@ pub const Effect = enum (u32) { }; }; -pub const Rect = struct { +pub const Font = enum (u32) { + default, + _, + + pub const Desc = struct { + + }; +}; + +pub const Layer = enum { + background, + foreground, + overlay, + + const values = std.enums.values(Layer); +}; + +pub const Rect = extern struct { left: f32, top: f32, right: f32, @@ -474,58 +488,47 @@ pub const Texture = enum (u32) { }; }; -pub const Transform2D = extern struct { - xbasis: Vector = .{1, 0}, - ybasis: Vector = .{0, 1}, - origin: Vector = @splat(0), +pub const Vector2 = @Vector(2, f32); - pub const Simplified = struct { - translation: Vector = @splat(0), - rotation: f32 = 0, - scale: Vector = @splat(1), - skew: f32 = 0, +const Work = union (enum) { + load_effect: LoadEffect, + load_texture: LoadTexture, + render_frame: RenderFrame, + shutdown, + unload_effect: UnloadEffect, + unload_texture: UnloadTexture, + + const LoadEffect = struct { + desc: Effect.Desc, + loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Effect), }; - pub const Vector = @Vector(2, f32); + const LoadTexture = struct { + desc: Texture.Desc, + loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Texture), + }; - pub fn scaled(self: Transform2D, scale: Vector) Transform2D { - var transform = self; - const scale_x, const scale_y = scale; + const RenderFrame = struct { + clear_color: Color, + width: u16, + height: u16, + finished: *std.Thread.ResetEvent, + commands_set: CommandsSet, - transform.xbasis *= @splat(scale_x); - transform.ybasis *= @splat(scale_y); - - return transform; - } - - pub fn transformed(self: Transform2D, other: Transform2D) Transform2D { - const xbasis_x, const xbasis_y = other.xbasis; - const ybasis_x, const ybasis_y = other.ybasis; - const origin_x, const origin_y = other.origin; - - return .{ - .xbasis = - (self.xbasis * @as(Vector, @splat(xbasis_x))) + - (self.ybasis * @as(Vector, @splat(xbasis_y))), - - .ybasis = - (self.xbasis * @as(Vector, @splat(ybasis_x))) + - (self.ybasis * @as(Vector, @splat(ybasis_y))), - - .origin = - (self.xbasis * @as(Vector, @splat(origin_x))) + - (self.ybasis * @as(Vector, @splat(origin_y))) + - self.origin, + const CommandsSet = struct { + ?*ona.Params(Commands(.background)).Node, + ?*ona.Params(Commands(.foreground)).Node, + ?*ona.Params(Commands(.overlay)).Node, }; - } + }; - pub fn translated(self: Transform2D, translation: Vector) Transform2D { - var transform = self; + const UnloadEffect = struct { + handle: Effect, + }; - transform.origin += translation; - - return transform; - } + const UnloadTexture = struct { + handle: Texture, + }; }; pub const colors = struct { @@ -546,6 +549,8 @@ pub const colors = struct { pub const purple = rgb(0.5, 0, 0.5); + pub const red = rgb(1, 0, 0); + pub fn rgb(r: f32, g: f32, b: f32) Color { return .{r, g, b, 1}; } @@ -553,13 +558,13 @@ pub const colors = struct { pub const white = greyscale(1); }; -pub fn poll(app: ona.Write(ona.App), events: ona.Send(hid.Event)) !void { +pub fn poll(loop: ona.Write(ona.Loop), events: ona.Send(hid.Event)) !void { var event = @as(ext.SDL_Event, undefined); while (ext.SDL_PollEvent(&event) != 0) { switch (event.type) { ext.SDL_QUIT => { - app.state.quit(); + loop.state.quit(); }, ext.SDL_KEYUP => { @@ -606,13 +611,15 @@ pub fn poll(app: ona.Write(ona.App), events: ona.Send(hid.Event)) !void { } } -pub fn setup(world: *ona.World, events: ona.App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void { +pub fn setup(world: *ona.World) !void { if (ext.SDL_Init(ext.SDL_INIT_VIDEO | ext.SDL_INIT_EVENTS) != 0) { return error.Unsupported; } + const display = try world.set_get_state(Display{}); + const assets = create: { - var assets = try Assets.init(); + var assets = try Assets.init(display.width, display.height); errdefer { assets.deinit(); @@ -632,10 +639,9 @@ pub fn setup(world: *ona.World, events: ona.App.Events) (error {Unsupported} || assets.window, }); - try world.set_state(Display{}); - 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"}); + try world.on_event(.pre_update, ona.system_fn(poll), .{.label = "poll gfx"}); + try world.on_event(.exit, ona.system_fn(stop), .{.label = "stop gfx"}); + try world.on_event(.finish, ona.system_fn(synchronize), .{.label = "synchronize gfx"}); } pub fn stop(assets: ona.Write(Assets)) void { @@ -648,8 +654,8 @@ pub fn synchronize(exclusive: ona.Exclusive(&.{Assets, Display})) !void { assets.frame_rendered.wait(); assets.frame_rendered.reset(); - { - var has_command_param = exclusive.world.get_params(Commands).has_head; + inline for (Layer.values) |layer| { + var has_command_param = exclusive.world.get_params(Commands(layer)).has_head; while (has_command_param) |command_param| : (has_command_param = command_param.has_next) { command_param.param.rotate(); @@ -664,35 +670,27 @@ pub fn synchronize(exclusive: ona.Exclusive(&.{Assets, Display})) !void { ext.SDL_SetWindowSize(assets.window, display.width, display.height); } - if (exclusive.world.get_params(Commands).has_head) |command_param| { + var commands_filled: usize = 0; + var commands_set = Work.RenderFrame.CommandsSet{null, null, null}; + + inline for (0 .. Layer.values.len, Layer.values) |i, layer| { + if (exclusive.world.get_params(Commands(layer)).has_head) |command_param| { + commands_set[i] = command_param; + commands_filled += 1; + } + } + + if (commands_filled == 0) { + assets.frame_rendered.set(); + } else { assets.pending_work.enqueue(.{ .render_frame = .{ - .has_command_params = command_param, + .commands_set = commands_set, .width = display.width, .height = display.height, .clear_color = display.clear_color, .finished = &assets.frame_rendered, }, }); - } else { - assets.frame_rendered.set(); } } - -pub fn transform_2d(simplified: Transform2D.Simplified) Transform2D { - const rotation_skew = simplified.rotation + simplified.skew; - - return .{ - .xbasis = simplified.scale * Transform2D.Vector{ - std.math.cos(simplified.rotation), - std.math.sin(simplified.rotation), - }, - - .ybasis = simplified.scale * Transform2D.Vector{ - -std.math.sin(rotation_skew), - std.math.cos(rotation_skew), - }, - - .origin = simplified.translation, - }; -} diff --git a/src/gfx/rendering.zig b/src/gfx/rendering.zig index 26af3bf..7b0f042 100644 --- a/src/gfx/rendering.zig +++ b/src/gfx/rendering.zig @@ -23,15 +23,23 @@ const Frame = struct { current_source_texture: gfx.Texture = .default, current_target_texture: ?gfx.Texture = null, current_effect: gfx.Effect = .default, + transform_matrix: Matrix = identity_matrix, width: u16 = 0, height: u16 = 0, const DrawTexture = extern struct { - transform: gfx.Transform2D, - tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)), - depth: f32 = 0, - texture_offset: @Vector(2, f32) = @splat(0), - texture_size: @Vector(2, f32) = @splat(1), + rotation: f32, + depth: f32, + position: gfx.Vector2, + size: gfx.Vector2, + anchor: gfx.Vector2, + source_clip: gfx.Rect, + tint: gfx.Color, + }; + + const View = extern struct { + projection_matrix: Matrix, + transform_matrix: Matrix, }; const batches_per_buffer = 512; @@ -75,35 +83,13 @@ const Frame = struct { }; } - pub fn draw_rect(self: *Frame, resources: *Resources, command: gfx.Commands.DrawRectCommand) !void { - if (self.current_source_texture != .default) { - self.flush(resources); - } - - self.current_source_texture = .default; - - const has_filled_current_buffer = (self.drawn_count % batches_per_buffer) == 0; - const buffer_count = self.drawn_count / batches_per_buffer; - - if (has_filled_current_buffer and buffer_count == self.texture_batch_buffers.len()) { - const instance_buffer = sokol.gfx.makeBuffer(.{ - .size = @sizeOf(DrawTexture) * batches_per_buffer, - .usage = .STREAM, - }); - - errdefer sokol.gfx.destroyBuffer(instance_buffer); - - try self.texture_batch_buffers.push_grow(instance_buffer); - } - - _ = sokol.gfx.appendBuffer(self.texture_batch_buffers.get().?.*, sokol.gfx.asRange(&DrawTexture{ - .transform = command.transform, - })); - - self.drawn_count += 1; + pub fn draw_font(self: *Frame, resources: *Resources, command: gfx.Command.DrawFont) !void { + _ = self; + _ = resources; + _ = command; } - pub fn draw_texture(self: *Frame, resources: *Resources, command: gfx.Commands.DrawTextureCommand) !void { + pub fn draw_texture(self: *Frame, resources: *Resources, command: gfx.Command.DrawTexture) !void { if (self.current_source_texture != command.texture) { self.flush(resources); } @@ -127,10 +113,13 @@ const Frame = struct { const texture = resources.get_texture(command.texture).?; _ = sokol.gfx.appendBuffer(self.texture_batch_buffers.get().?.*, sokol.gfx.asRange(&DrawTexture{ - .transform = command.transform.scaled(@floatFromInt(command.resolution orelse .{ - texture.width, - texture.height, - })), + .size = command.size orelse .{@floatFromInt(texture.width), @floatFromInt(texture.height)}, + .anchor = command.anchor, + .rotation = command.rotation, + .depth = command.depth, + .source_clip = command.source, + .tint = command.tint, + .position = command.position, })); self.drawn_count += 1; @@ -178,19 +167,27 @@ const Frame = struct { if (self.current_target_texture) |target_texture| { const texture = resources.get_texture(target_texture).?; - sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&orthographic_projection(-1.0, 1.0, .{ - .left = 0, - .top = 0, - .right = @floatFromInt(texture.width), - .bottom = @floatFromInt(texture.height), - }))); + sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&View{ + .projection_matrix = orthographic_projection(-1.0, 1.0, .{ + .left = 0, + .top = 0, + .right = @floatFromInt(texture.width), + .bottom = @floatFromInt(texture.height), + }), + + .transform_matrix = self.transform_matrix, + })); } else { - sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&orthographic_projection(-1.0, 1.0, .{ - .left = 0, - .top = 0, - .right = @floatFromInt(self.width), - .bottom = @floatFromInt(self.height), - }))); + sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&View{ + .projection_matrix = orthographic_projection(-1.0, 1.0, .{ + .left = 0, + .top = 0, + .right = @floatFromInt(self.width), + .bottom = @floatFromInt(self.height), + }), + + .transform_matrix = self.transform_matrix, + })); } if (effect.properties.len != 0) { @@ -215,7 +212,7 @@ const Frame = struct { } } - pub fn set_effect(self: *Frame, resources: *Resources, command: gfx.Commands.SetEffectCommand) void { + pub fn set_effect(self: *Frame, resources: *Resources, command: gfx.Command.SetEffect) void { if (command.effect != self.current_effect) { self.flush(resources); } @@ -227,7 +224,7 @@ const Frame = struct { } } - pub fn set_target(self: *Frame, resources: *Resources, command: gfx.Commands.SetTargetCommand) void { + pub fn set_target(self: *Frame, resources: *Resources, command: gfx.Command.SetTarget) void { sokol.gfx.endPass(); var pass = sokol.gfx.Pass{ @@ -305,18 +302,23 @@ const Frame = struct { } }; -fn Matrix(comptime n: usize, comptime Element: type) type { - return [n]@Vector(n, Element); -} +const Matrix = [4]@Vector(4, f32); var default_sampler: sokol.gfx.Sampler = undefined; +const identity_matrix = Matrix{ + .{1, 0, 0, 0}, + .{0, 1, 0, 0}, + .{0, 0, 1, 0}, + .{0, 0, 0, 1}, +}; + const vertex_indices = .{ .mesh = 0, .instance = 1, }; -fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) Matrix(4, f32) { +fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) Matrix { const width = viewport.right - viewport.left; const height = viewport.bottom - viewport.top; @@ -397,29 +399,31 @@ pub fn process_work(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window } }, - .render_frame => |render_frame| { + .render_frame => |*render_frame| { frame.start(render_frame.width, render_frame.height); - var has_command_params = render_frame.has_command_params; + inline for (&render_frame.commands_set) |has_commands| { + var has_commands_currently = has_commands; - while (has_command_params) |command_params| : (has_command_params = command_params.has_next) { - for (command_params.param.submitted_commands()) |command| { - try switch (command) { - .draw_rect => |draw_rect| frame.draw_rect(&resources, draw_rect), - .draw_texture => |draw_texture| frame.draw_texture(&resources, draw_texture), - .set_effect => |set_effect| frame.set_effect(&resources, set_effect), - .set_target => |set_target| frame.set_target(&resources, set_target), - }; - } + while (has_commands_currently) |commands| : (has_commands_currently = commands.has_next) { + for (commands.param.submitted_commands()) |command| { + try switch (command) { + .draw_font => |draw_font| frame.draw_font(&resources, draw_font), + .draw_texture => |draw_texture| frame.draw_texture(&resources, draw_texture), + .set_effect => |set_effect| frame.set_effect(&resources, set_effect), + .set_target => |set_target| frame.set_target(&resources, set_target), + }; + } - frame.flush(&resources); + frame.flush(&resources); - if (frame.current_target_texture != null) { - frame.set_target(&resources, .{ - .clear_color = null, - .clear_depth = null, - .clear_stencil = null, - }); + if (frame.current_target_texture != null) { + frame.set_target(&resources, .{ + .clear_color = null, + .clear_depth = null, + .clear_stencil = null, + }); + } } } diff --git a/src/gfx/shaders/2d_default.vert b/src/gfx/shaders/2d_default.vert deleted file mode 100644 index 5d782fb..0000000 --- a/src/gfx/shaders/2d_default.vert +++ /dev/null @@ -1,29 +0,0 @@ -#version 430 - -layout (location = 0) in vec2 mesh_xy; -layout (location = 1) in vec2 mesh_uv; - -layout (location = 2) in vec2 instance_xbasis; -layout (location = 3) in vec2 instance_ybasis; -layout (location = 4) in vec2 instance_origin; -layout (location = 5) in vec4 instance_tint; -layout (location = 6) in float instance_depth; -layout (location = 7) in vec4 instance_clip; - -layout (location = 0) out vec4 color; -layout (location = 1) out vec2 uv; - -layout (binding = 0) uniform View { - mat4 projection_matrix; -}; - -void main() { - const vec2 world_position = instance_origin + mesh_xy.x * instance_xbasis + mesh_xy.y * instance_ybasis; - const vec2 projected_position = (projection_matrix * vec4(world_position, 0, 1)).xy; - const vec2 rect_size = instance_clip.zw - instance_clip.xy; - const vec4 screen_position = vec4(projected_position, instance_depth, 1.0); - - gl_Position = screen_position; - color = instance_tint; - uv = instance_clip.xy + (mesh_uv * rect_size); -} diff --git a/src/gfx/shaders/2d_default.frag b/src/gfx/shaders/draw_texture.frag similarity index 100% rename from src/gfx/shaders/2d_default.frag rename to src/gfx/shaders/draw_texture.frag diff --git a/src/gfx/shaders/draw_texture.vert b/src/gfx/shaders/draw_texture.vert new file mode 100644 index 0000000..8838617 --- /dev/null +++ b/src/gfx/shaders/draw_texture.vert @@ -0,0 +1,42 @@ +#version 430 + +layout (location = 0) in vec2 batch_xy; +layout (location = 1) in vec2 batch_uv; + +layout (location = 2) in float instance_rotation; +layout (location = 3) in float instance_depth; +layout (location = 4) in vec2 instance_position; +layout (location = 5) in vec2 instance_scale; +layout (location = 6) in vec2 instance_anchor; +layout (location = 7) in vec4 instance_clip; +layout (location = 8) in vec4 instance_tint; + +layout (location = 0) out vec4 color; +layout (location = 1) out vec2 uv; + +layout (binding = 0) uniform View { + mat4 projection_matrix; + mat4 transform_matrix; +}; + +void main() { + const vec2 normalized_position = (batch_xy + 0.5) - instance_anchor; + const float cos_theta = cos(instance_rotation); + const float sin_theta = sin(instance_rotation); + + const mat2 rotation_matrix = mat2( + cos_theta, -sin_theta, + sin_theta, cos_theta + ); + + const vec2 rotated_position = rotation_matrix * (normalized_position * instance_scale); + const vec2 world_position = instance_position + rotated_position; + const vec4 clip_space_position = projection_matrix * transform_matrix * vec4(world_position, 0.0, 1.0); + + gl_Position = vec4(clip_space_position.xy, instance_depth, 1.0); + + const vec2 clip_size = instance_clip.zw - instance_clip.xy; + + uv = instance_clip.xy + (batch_uv * clip_size); + color = instance_tint; +} diff --git a/src/gfx/spirv.zig b/src/gfx/spirv.zig index dfe55ab..96f0209 100644 --- a/src/gfx/spirv.zig +++ b/src/gfx/spirv.zig @@ -232,9 +232,7 @@ pub const Stage = struct { else => error.UnsupportedSPIRV, }, - .len = std.math.cast(u16, ext.spvc_type_get_array_dimension(member_type_handle, 0)) orelse { - return error.UnsupportedSPIRV; - }, + .len = std.math.cast(u16, ext.spvc_type_get_array_dimension(member_type_handle, 0)) orelse 0, }; } diff --git a/src/gui/gui.zig b/src/gui/gui.zig new file mode 100644 index 0000000..0bfceda --- /dev/null +++ b/src/gui/gui.zig @@ -0,0 +1,234 @@ +const gfx = @import("gfx"); + +const ona = @import("ona"); + +const std = @import("std"); + +pub const Box = struct { + left: f32, + top: f32, + width: f32, + height: f32, +}; + +pub const Canvas = struct { + nodes: ona.stack.Parallel(Node) = .{}, + drawable_blocks: ona.stack.Sequential(DrawableBlock) = .{}, + drawable_labels: ona.stack.Sequential(DrawableLabel) = .{}, + + fn Drawable(comptime State: type) type { + return struct { + state: State, + depth: f32, + }; + } + + const DrawableBlock = Drawable(struct { + background: gfx.Texture = .default, + color: gfx.Color = gfx.colors.white, + }); + + const DrawableLabel = Drawable(struct { + text: ona.stack.Sequential(u8) = .{}, + color: gfx.Color = gfx.colors.black, + font: gfx.Font = .default, + }); + + const Node = struct { + name: []u8, + box: Box, + parent_index: u32 = unused_index, + block_index: u32 = unused_index, + label_index: u32 = unused_index, + style_index: u32 = unused_index, + + const unused_index = std.math.maxInt(u32); + }; + + pub fn append(self: *Canvas, name: []const u8, box: Box) std.mem.Allocator.Error!Item { + const duped_name = try ona.heap.allocator.dupe(u8, name); + + errdefer { + ona.heap.allocator.free(duped_name); + } + + const index = self.nodes.len(); + + try self.nodes.push_grow(.{ + .name = duped_name, + .box = box, + }); + + errdefer { + std.debug.assert(self.nodes.pop()); + } + + const node_count = self.nodes.len(); + + try self.drawable_blocks.grow(ona.scalars.sub(node_count, self.drawable_blocks.cap) orelse 0); + try self.drawable_labels.grow(ona.scalars.sub(node_count, self.drawable_labels.cap) orelse 0); + + return @enumFromInt(index); + } + + fn calculate_depth(self: Canvas, item: Item) f32 { + const parent_indices = self.nodes.values.slice(.parent_index); + var depth: f32 = 0; + var index = @intFromEnum(item); + + while (true) : (depth += 1) { + const parent_index = parent_indices[index]; + + if (parent_index == Node.unused_index) { + break; + } + + index = parent_index; + } + + return depth; + } + + fn deinit(self: *Canvas) void { + self.nodes.deinit(); + self.drawable_blocks.deinit(); + + for (self.drawable_labels.values) |*drawable_label| { + drawable_label.state.text.deinit(); + } + + self.drawable_labels.deinit(); + + self.* = undefined; + } + + const label_depth_offset = 0.5; + + pub fn set_block(self: *Canvas, item: Item, set: SetBlock) void { + const block_index = self.nodes.values.get(.block_index, @intFromEnum(item)).?; + + if (block_index.* == Node.unused_index) { + block_index.* = @intCast(self.drawable_blocks.len()); + + std.debug.assert(self.drawable_blocks.push(.{ + .depth = self.calculate_depth(item), + .state = .{}, + })); + } + + const block = &self.drawable_blocks.values[block_index.*]; + + if (set.has_background) |background| { + block.state.background = background; + } + + if (set.has_color) |color| { + block.state.color = color; + } + } + + pub fn set_label(self: *Canvas, item: Item, set: SetLabel) std.mem.Allocator.Error!void { + const label_index = self.nodes.values.get(.label_index, @intFromEnum(item)).?; + + if (label_index.* == Node.unused_index) { + label_index.* = @intCast(self.drawable_labels.len()); + + std.debug.assert(self.drawable_labels.push(.{ + .depth = self.calculate_depth(item) + label_depth_offset, + .state = .{}, + })); + } + + const label = &self.drawable_labels.values[label_index.*]; + + if (set.has_text) |text| { + label.state.text.pop_all(); + + try label.state.text.grow(ona.scalars.sub(text.len, label.state.text.cap) orelse 0); + + std.debug.assert(label.state.text.push_all(text)); + } + + if (set.has_font) |font| { + label.state.font = font; + } + + if (set.has_color) |color| { + label.state.color = color; + } + } + + pub fn set_parent(self: *Canvas, item: Item, has_parent: ?Item) void { + const item_index = @intFromEnum(item); + const parent_index = self.nodes.values.get(.parent_index, item_index).?; + + parent_index.* = if (has_parent) |parent| @intFromEnum(parent) else Node.unused_index; + + const label_index = self.nodes.values.get(.parent_index, item_index).?.*; + const block_index = self.nodes.values.get(.parent_index, item_index).?.*; + const label_index_exists = label_index != Node.unused_index; + const block_index_exists = block_index != Node.unused_index; + + if (label_index_exists or block_index_exists) { + const depth = self.calculate_depth(item); + + if (label_index_exists) { + self.drawable_labels[label_index].depth = depth + label_depth_offset; + } + + if (block_index_exists) { + self.drawable_labels[label_index].depth = depth; + } + } + } +}; + +pub const Item = enum (u32) { + _, +}; + +pub const SetBlock = struct { + has_background: ?gfx.Texture = null, + has_color: ?gfx.Color = null, +}; + +pub const SetLabel = struct { + has_text: ?[]const u8 = null, + has_color: ?gfx.Color = null, + has_font: ?gfx.Font = null, +}; + +pub fn exit(canvas: ona.Write(Canvas)) !void { + canvas.state.deinit(); +} + +pub fn render(commands: gfx.Commands(.overlay), canvas: ona.Read(Canvas)) !void { + const boxes = canvas.state.nodes.values.slice(.box); + + for (canvas.state.drawable_blocks.values, boxes) |block, box| { + try commands.draw_texture(.{ + .depth = block.depth, + .position = .{box.left, box.top}, + .size = .{box.width, box.height}, + .texture = block.state.background, + .tint = block.state.color, + }); + } + + for (canvas.state.drawable_labels.values, boxes) |label, box| { + try commands.draw_font(.{ + .depth = label.depth, + .text = label.state.text.values, + .position = .{box.left, box.top}, + .size = .{box.width, box.height}, + .tint = label.state.color, + .font = label.state.font, + }); + } +} + +pub fn setup(world: *ona.World) !void { + try world.set_state(Canvas{}); + try world.on_event(.render, ona.system_fn(render), .{.label = "render gui"}); + try world.on_event(.exit, ona.system_fn(exit), .{.label = "cleanup gui"}); +} diff --git a/src/hid/hid.zig b/src/hid/hid.zig index 92e6775..33a25d7 100644 --- a/src/hid/hid.zig +++ b/src/hid/hid.zig @@ -293,10 +293,10 @@ test "mapping values" { } } -pub fn setup(world: *ona.World, events: ona.App.Events) std.mem.Allocator.Error!void { +pub fn setup(world: *ona.World) std.mem.Allocator.Error!void { try world.set_state(Mapping{}); - try world.on_event(events.pre_update, ona.system_fn(update), .{ + try world.on_event(.pre_update, ona.system_fn(update), .{ .label = "update actions", }); } diff --git a/src/ona/World.zig b/src/ona/World.zig index a68b3ff..b248fec 100644 --- a/src/ona/World.zig +++ b/src/ona/World.zig @@ -6,7 +6,7 @@ const std = @import("std"); thread_pool: ?*std.Thread.Pool = null, thread_restricted_resources: [std.enums.values(ona.ThreadRestriction).len]StateTable, -event_systems: ona.stack.Sequential(Schedule), +event_schedules: ona.map.Hashed([]const u8, *Schedule, ona.map.string_traits), pub const BindContext = struct { node: ona.dag.Node, @@ -110,8 +110,6 @@ const Graph = ona.dag.Graph(struct { resource_accesses: ona.stack.Sequential(StateAccess), }); -pub const Event = enum (usize) { _ }; - const ParallelNodeBundles = ona.stack.Sequential(NodeBundle); const NodeBundle = ona.stack.Sequential(ona.dag.Node); @@ -442,22 +440,16 @@ const Schedule = struct { } } - pub fn init(label: []const u8) std.mem.Allocator.Error!Schedule { - var arena = std.heap.ArenaAllocator.init(ona.heap.allocator); - - errdefer arena.deinit(); - - const duped_label = try arena.allocator().dupeZ(u8, label); - + pub fn init(comptime label: [:0]const u8) std.mem.Allocator.Error!Schedule { return .{ .graph = Graph.init(ona.heap.allocator), - .label = duped_label, - .arena = arena, - .system_id_nodes = .{.allocator = ona.heap.allocator}, - .read_write_resource_id_nodes = .{.allocator = ona.heap.allocator}, - .read_only_resource_id_nodes = .{.allocator = ona.heap.allocator}, - .parallel_work_bundles = .{.allocator = ona.heap.allocator}, - .blocking_work = .{.allocator = ona.heap.allocator}, + .arena = std.heap.ArenaAllocator.init(ona.heap.allocator), + .label = label, + .system_id_nodes = .{}, + .read_write_resource_id_nodes = .{}, + .read_only_resource_id_nodes = .{}, + .parallel_work_bundles = .{}, + .blocking_work = .{}, }; } @@ -544,21 +536,12 @@ pub const UnbindContext = struct { world: *Self, }; -pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Event { - var systems = try Schedule.init(label); - - errdefer systems.deinit(self); - - const index = self.event_systems.len(); - - try self.event_systems.push_grow(systems); - - return @enumFromInt(index); -} - pub fn deinit(self: *Self) void { - for (self.event_systems.values) |*schedule| { - schedule.deinit(self); + var event_schedules = self.event_schedules.entries(); + + while (event_schedules.next()) |schedule| { + schedule.value.deinit(self); + ona.heap.allocator.destroy(schedule.value); } for (&self.thread_restricted_resources) |*resources| { @@ -570,7 +553,7 @@ pub fn deinit(self: *Self) void { ona.heap.allocator.destroy(thread_pool); } - self.event_systems.deinit(); + self.event_schedules.deinit(); self.* = undefined; } @@ -594,7 +577,7 @@ pub fn set_get_state(self: *Self, value: anytype) std.mem.Allocator.Error!*@Type pub fn init(thread_count: u32) std.Thread.SpawnError!Self { var world = Self{ .thread_restricted_resources = .{StateTable.init(), StateTable.init()}, - .event_systems = .{.allocator = ona.heap.allocator}, + .event_schedules = .{.allocator = ona.heap.allocator}, }; if (thread_count != 0 and !builtin.single_threaded) { @@ -611,12 +594,34 @@ pub fn init(thread_count: u32) std.Thread.SpawnError!Self { return world; } -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)].insert(action, order); +pub fn on_event(self: *Self, comptime event: anytype, info: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void { + const event_name = @tagName(event); + + if (self.event_schedules.get(event_name)) |schedule| { + try schedule.*.insert(info, order); + } else { + const schedule = try ona.heap.allocator.create(Schedule); + + errdefer { + ona.heap.allocator.destroy(schedule); + } + + schedule.* = try Schedule.init(event_name); + + errdefer { + schedule.deinit(self); + } + + try schedule.insert(info, order); + + std.debug.assert(try self.event_schedules.emplace(event_name, schedule)); + } } -pub fn run_event(self: *Self, event: Event) anyerror!void { - try self.event_systems.values[@intFromEnum(event)].run(self); +pub fn run_event(self: *Self, comptime event: anytype) anyerror!void { + if (self.event_schedules.get(@tagName(event))) |schedule| { + try schedule.*.run(self); + } } pub fn set_state(self: *Self, value: anytype) std.mem.Allocator.Error!void { diff --git a/src/ona/map.zig b/src/ona/map.zig index 9193d4f..8aaa3de 100644 --- a/src/ona/map.zig +++ b/src/ona/map.zig @@ -7,7 +7,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits( const max_int = std.math.maxInt(usize); return struct { - allocator: std.mem.Allocator, + allocator: std.mem.Allocator = ona.heap.allocator, entry_map: []?Entry = &.{}, len: usize = 0, diff --git a/src/ona/ona.zig b/src/ona/ona.zig index c6fdd0f..f430dd8 100644 --- a/src/ona/ona.zig +++ b/src/ona/ona.zig @@ -29,185 +29,146 @@ pub const World = @import("./World.zig"); pub const lina = @import("./lina.zig"); pub const App = struct { - events: *const Events, + step: fn (*World) anyerror!void = noop, + + fn noop(_: *World) !void {} + + pub fn build(self: App) 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(); + + try self.step(&world); + } + }; + + return Start.main; + } + + pub const game = App{.step = run_game}; + + fn run_game(world: *World) !void { + const loop = try world.set_get_state(Loop{ + .target_frame_time = 1.0 / 60.0, + .elapsed_time = 0, + .is_running = true, + }); + + try world.run_event(.load); + + const ticks_initial = std.time.milliTimestamp(); + var ticks_previous = ticks_initial; + var accumulated_time = @as(f64, 0); + + while (loop.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; + + loop.elapsed_time = @as(f64, @floatFromInt(ticks_current - ticks_initial)) / milliseconds_per_second; + ticks_previous = ticks_current; + accumulated_time += delta_time; + + try world.run_event(.pre_update); + + while (accumulated_time >= loop.target_frame_time) : (accumulated_time -= loop.target_frame_time) { + try world.run_event(.update); + } + + try world.run_event(.post_update); + try world.run_event(.render); + try world.run_event(.finish); + } + + try world.run_event(.exit); + } + + pub fn with_module(self: App, comptime Module: type) App { + if (!@hasDecl(Module, "setup")) { + @compileError("`Module` argument must have a setup fn declaration"); + } + + const WithState = struct { + fn step(world: *World) !void { + try Module.setup(world); + try self.step(world); + } + }; + + return .{ + .step = WithState.step, + }; + } + + pub fn with_state(self: App, comptime state: anytype) App { + const WithState = struct { + fn step(world: *World) !void { + try world.set_state(state); + try self.step(world); + } + }; + + return .{ + .step = WithState.step, + }; + } + + pub fn with_system(self: App, comptime event: anytype, comptime info: *const SystemInfo, comptime order: SystemOrder) App { + const WithState = struct { + fn step(world: *World) !void { + try world.on_event(event, info, order); + try self.step(world); + } + }; + + return .{ + .step = WithState.step, + }; + } +}; + +pub const Loop = struct { target_frame_time: f64, elapsed_time: f64, is_running: bool, - pub const Events = struct { - load: World.Event, - pre_update: World.Event, - update: World.Event, - post_update: World.Event, - render: World.Event, - finish: World.Event, - 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 { + pub fn quit(self: *Loop) void { self.is_running = false; } }; @@ -355,7 +316,10 @@ pub const LaunchFlag = enum { pub fn Params(comptime Value: type) type { if (!@hasDecl(Value, "Param")) { - @compileError("System parameters must have a Params type declaration"); + @compileError(@typeName(Value) ++ + \\ is missing a public `Params` type declaration. + \\This is required for a type to be used as a system parameter + ); } return struct { @@ -519,15 +483,11 @@ pub fn Receive(comptime Message: type) type { .channel = (try context.register_readable_state_access(TypedChannel)) orelse set: { try context.world.set_state(TypedChannel.init(heap.allocator)); - const app = context.world.get_state(App) orelse { - @panic("Send system parameters depend on a " ++ @typeName(App) ++ " state to work"); - }; - - try context.world.on_event(app.events.post_update, system_fn(TypedChannel.swap), .{ + try context.world.on_event(.post_update, system_fn(TypedChannel.swap), .{ .label = "swap channel of " ++ @typeName(Message), }); - try context.world.on_event(app.events.exit, system_fn(TypedChannel.cleanup), .{ + try context.world.on_event(.exit, system_fn(TypedChannel.cleanup), .{ .label = "clean up channel of " ++ @typeName(Message), }); @@ -565,15 +525,11 @@ pub fn Send(comptime Message: type) type { .channel = (try context.register_writable_state_access(TypedChannel)) orelse set: { try context.world.set_state(TypedChannel.init(heap.allocator)); - const app = context.world.get_state(App) orelse { - @panic("Send system parameters depend on a " ++ @typeName(App) ++ " state to work"); - }; - - try context.world.on_event(app.events.post_update, system_fn(TypedChannel.swap), .{ + try context.world.on_event(.post_update, system_fn(TypedChannel.swap), .{ .label = "swap channel of " ++ @typeName(Message), }); - try context.world.on_event(app.events.exit, system_fn(TypedChannel.cleanup), .{ + try context.world.on_event(.exit, system_fn(TypedChannel.cleanup), .{ .label = "clean up channel of " ++ @typeName(Message), }); diff --git a/src/ona/stack.zig b/src/ona/stack.zig index e9657e1..fff9ff2 100644 --- a/src/ona/stack.zig +++ b/src/ona/stack.zig @@ -69,6 +69,10 @@ pub fn Sequential(comptime Value: type) type { return true; } + pub fn pop_all(self: *Self) void { + self.values = self.values[0 .. 0]; + } + pub fn pop_many(self: *Self, n: usize) bool { const new_length = ona.scalars.sub(self.values.len, n) orelse { return false; @@ -247,6 +251,16 @@ pub fn Parallel(comptime Value: type) type { return self.values.len; } + pub fn pop(self: *Self) bool { + if (self.values.len == 0) { + return false; + } + + self.values = self.values.slice_all(0, self.values.len - 1).?; + + return true; + } + pub fn pop_many(self: *Self, n: usize) bool { const new_length = ona.scalars.sub(self.values.len, n) orelse { return false;