diff --git a/demos/canvas.zig b/demos/canvas.zig index 654b764..c31f7d8 100644 --- a/demos/canvas.zig +++ b/demos/canvas.zig @@ -12,20 +12,33 @@ pub const main = ona.App.game .with_module(gui) .with_system(.load, ona.system_fn(load), .{ .label = "Hello Ona" }).build(); -pub fn load(canvas: ona.Write(gui.Canvas)) !void { +fn load(canvas: ona.Write(gui.Canvas)) !void { const item = try canvas.state.append("", .{ - .left = 0, - .top = 0, + .x = 0, + .y = 0, .width = 256, .height = 256, }); - canvas.state.set_block(item, .{ - .has_background = .default, + try canvas.state.set_block(item, .{ .has_color = gfx.colors.purple, }); try canvas.state.set_label(item, .{ .has_text = "Hello, world", }); + + const item2 = try canvas.state.append("", .{ + .x = 128, + .y = 128, + .width = 256, + .height = 256, + }); + + try canvas.state.set_block(item2, .{ + .has_color = .{0, 1, 0, 1}, + }); + + canvas.state.set_parent(item2, item); } + diff --git a/demos/effects.zig b/demos/effects.zig index a1d458a..c159fc6 100644 --- a/demos/effects.zig +++ b/demos/effects.zig @@ -72,7 +72,7 @@ fn update(effects: ona.Write(Effects), loop: ona.Read(ona.Loop)) void { } } -fn render(commands: gfx.Commands(.background), effects: ona.Write(Effects), loop: ona.Read(ona.Loop), display: ona.Write(gfx.Display)) !void { +fn render(commands: gfx.Commands, 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, diff --git a/demos/inputs.zig b/demos/inputs.zig index af87f2c..e34c59b 100644 --- a/demos/inputs.zig +++ b/demos/inputs.zig @@ -6,43 +6,46 @@ const ona = @import("ona"); const std = @import("std"); -const Spawned = struct { - visual: struct { +const Visuals = struct { + spawned: SpawnMap = SpawnMap.init(ona.heap.allocator), + random: std.Random.Xoroshiro128, + mouse_position: @Vector(2, f32) = @splat(0), + + const SpawnMap = ona.SlotMap(struct { color: gfx.Color, position: gfx.Vector2, rotation: f32, - }, - - lifetime_seconds: f32, -}; - -const Visuals = struct { - spawned: ona.stack.Parallel(Spawned) = .{}, - random: std.Random.Xoroshiro128, - mouse_position: @Vector(2, f32) = @splat(0), + lifetime_seconds: f32, + }); }; fn cleanup(visuals: ona.Write(Visuals)) !void { visuals.state.spawned.deinit(); } -fn load(visuals: ona.Write(Visuals)) !void { - const initial_spawn_capacity = 1024; - - try visuals.state.spawned.grow(initial_spawn_capacity); -} - 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); + const float_speed = 6; + + for (0 .. visuals.state.spawned.next) |i| { + const spawned = visuals.state.spawned.get(i) orelse { + continue; + }; + + spawned.lifetime_seconds -= 1.0 / 60.0; + spawned.position -= .{0, float_speed}; + + if (spawned.lifetime_seconds <= 0) { + std.debug.assert(visuals.state.spawned.remove(i) != null); + } + } const random = visuals.state.random.random(); const width: f32 = @floatFromInt(display.state.width); @@ -51,26 +54,20 @@ fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display: for (events.messages()) |event| { switch (event) { .key_down => { - try visuals.state.spawned.push_grow(.{ + _ = try visuals.state.spawned.insert(.{ .lifetime_seconds = 2.5 + (5 * random.float(f32)), - - .visual = .{ - .color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)}, - .position = .{width * random.float(f32), height}, - .rotation = std.math.pi * random.float(f32), - }, + .color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)}, + .position = .{width * random.float(f32), height}, + .rotation = std.math.pi * random.float(f32), }); }, .mouse_down => { - try visuals.state.spawned.push_grow(.{ + _ = try visuals.state.spawned.insert(.{ .lifetime_seconds = 2.5 + (5 * random.float(f32)), - - .visual = .{ - .color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)}, - .position = visuals.state.mouse_position, - .rotation = std.math.pi * random.float(f32), - }, + .color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)}, + .position = visuals.state.mouse_position, + .rotation = std.math.pi * random.float(f32), }); }, @@ -83,35 +80,8 @@ fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display: } } -fn update_spawned(spawned: *ona.stack.Parallel(Spawned)) void { - const float_speed = 6; - - for (spawned.values.slice(.visual)) |*visual| { - visual.position -= .{0, float_speed}; - } - - { - var range = spawned.len(); - var index: usize = 0; - - while (index < range) : (index += 1) { - const lifetime_seconds = spawned.values.get(.lifetime_seconds, index).?; - - lifetime_seconds.* -= 1.0 / 60.0; - - if (lifetime_seconds.* <= 0) { - range -= 1; - - std.mem.swap(f32, lifetime_seconds, spawned.values.get(.lifetime_seconds, range).?); - } - } - - std.debug.assert(spawned.pop_many(spawned.len() - range)); - } -} - -fn render(visuals: ona.Write(Visuals), commands: gfx.Commands(.foreground)) !void { - for (visuals.state.spawned.values.slice(.visual)) |visual| { +fn render(visuals: ona.Write(Visuals), commands: gfx.Commands) !void { + for (visuals.state.spawned.values()) |visual| { try commands.draw_texture(.{ .anchor = @splat(0.5), .position = visual.position, diff --git a/src/gfx/gfx.zig b/src/gfx/gfx.zig index dd0d0d4..19ec459 100644 --- a/src/gfx/gfx.zig +++ b/src/gfx/gfx.zig @@ -119,6 +119,7 @@ pub const Command = union (enum) { draw_font: DrawFont, draw_texture: DrawTexture, set_effect: SetEffect, + set_scissor: SetScissor, set_target: SetTarget, pub const DrawFont = struct { @@ -146,6 +147,10 @@ pub const Command = union (enum) { properties: []const u8, }; + pub const SetScissor = struct { + has_rect: ?Rect, + }; + pub const SetTarget = struct { texture: ?Texture = null, clear_color: ?Color, @@ -154,120 +159,120 @@ pub const Command = union (enum) { }; }; -pub fn Commands(comptime layer: Layer) type { - return struct { - pending: *List, +pub const Commands = struct { + pending: *List, - pub const List = struct { - arena: std.heap.ArenaAllocator, - stack: ona.stack.Sequential(Command), + pub const List = struct { + arena: std.heap.ArenaAllocator, + stack: ona.stack.Sequential(Command), - fn clear(self: *List) void { - self.stack.clear(); + 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", .{}); - } + 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 { - 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 { + fn init(allocator: std.mem.Allocator) List { return .{ - .swap_lists = .{ - List.init(ona.heap.allocator), - List.init(ona.heap.allocator), - }, + .arena = std.heap.ArenaAllocator.init(allocator), + .stack = .{.allocator = allocator}, }; } - - pub fn init(param: *Param) Self { - return .{ - .pending = param.pending_list(), - }; - } - - 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, - }, - }); - } - - 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 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 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 .{ + .swap_lists = .{ + List.init(ona.heap.allocator), + List.init(ona.heap.allocator), + }, + }; + } + + pub fn init(param: *Param) Self { + return .{ + .pending = param.pending_list(), + }; + } + + 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, + }, + }); + } + + 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 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 fn set_scissor(self: Self, command: Command.SetScissor) std.mem.Allocator.Error!void { + try self.pending.stack.push_grow(.{.set_scissor = command}); + } + + 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 unbind(param: *Param, _: ona.World.UnbindContext) void { + param.deinit(); + } +}; pub const Descs = struct { arena: std.heap.ArenaAllocator, @@ -437,19 +442,28 @@ pub const Font = enum (u32) { }; }; -pub const Layer = enum { - background, - foreground, - overlay, - - const values = std.enums.values(Layer); -}; - pub const Rect = extern struct { left: f32, top: f32, right: f32, bottom: f32, + + pub fn area(self: Rect) f32 { + const width, const height = self.size(); + + return width * height; + } + + pub fn size(self: Rect) Vector2 { + return .{self.right - self.left, self.bottom - self.top}; + } + + pub const zero = Rect{ + .left = 0, + .top = 0, + .right = 0, + .bottom = 0, + }; }; pub const Texture = enum (u32) { @@ -513,13 +527,7 @@ const Work = union (enum) { width: u16, height: u16, finished: *std.Thread.ResetEvent, - commands_set: CommandsSet, - - const CommandsSet = struct { - ?*ona.Params(Commands(.background)).Node, - ?*ona.Params(Commands(.foreground)).Node, - ?*ona.Params(Commands(.overlay)).Node, - }; + has_commands: ?*ona.Params(Commands).Node, }; const UnloadEffect = struct { @@ -654,12 +662,10 @@ pub fn synchronize(exclusive: ona.Exclusive(&.{Assets, Display})) !void { assets.frame_rendered.wait(); assets.frame_rendered.reset(); - inline for (Layer.values) |layer| { - var has_command_param = exclusive.world.get_params(Commands(layer)).has_head; + var has_command_param = exclusive.world.get_params(Commands).has_head; - while (has_command_param) |command_param| : (has_command_param = command_param.has_next) { - command_param.param.rotate(); - } + while (has_command_param) |command_param| : (has_command_param = command_param.has_next) { + command_param.param.rotate(); } var display_width, var display_height = [_]c_int{0, 0}; @@ -670,27 +676,17 @@ pub fn synchronize(exclusive: ona.Exclusive(&.{Assets, Display})) !void { ext.SDL_SetWindowSize(assets.window, display.width, display.height); } - 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 { + if (exclusive.world.get_params(Commands).has_head) |has_commands| { assets.pending_work.enqueue(.{ .render_frame = .{ - .commands_set = commands_set, + .has_commands = has_commands, .width = display.width, .height = display.height, .clear_color = display.clear_color, .finished = &assets.frame_rendered, }, }); + } else { + assets.frame_rendered.set(); } } diff --git a/src/gfx/rendering.zig b/src/gfx/rendering.zig index 7b0f042..f964d2f 100644 --- a/src/gfx/rendering.zig +++ b/src/gfx/rendering.zig @@ -224,6 +224,24 @@ const Frame = struct { } } + pub fn set_scissor(self: *Frame, resources: *Resources, command: gfx.Command.SetScissor) void { + self.flush(resources); + + if (command.has_rect) |rect| { + const width, const height = rect.size(); + + sokol.gfx.applyScissorRect( + @intFromFloat(rect.left), + @intFromFloat(rect.top), + @intFromFloat(width), + @intFromFloat(height), + true, + ); + } else { + sokol.gfx.applyScissorRect(0, 0, self.width, self.height, true); + } + } + pub fn set_target(self: *Frame, resources: *Resources, command: gfx.Command.SetTarget) void { sokol.gfx.endPass(); @@ -402,28 +420,27 @@ pub fn process_work(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window .render_frame => |*render_frame| { frame.start(render_frame.width, render_frame.height); - inline for (&render_frame.commands_set) |has_commands| { - var has_commands_currently = has_commands; + var has_commands = render_frame.has_commands; - 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), - }; - } + while (has_commands) |commands| : (has_commands = 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_scissor => |set_scissor| frame.set_scissor(&resources, set_scissor), + .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/gui/gui.zig b/src/gui/gui.zig index 0bfceda..f877d78 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -5,45 +5,74 @@ const ona = @import("ona"); const std = @import("std"); pub const Box = struct { - left: f32, - top: f32, + x: f32, + y: f32, width: f32, height: f32, + + pub fn rect(self: Box) gfx.Rect { + return .{ + .left = self.x, + .top = self.y, + .right = self.x + self.width, + .bottom = self.y + self.height, + }; + } }; pub const Canvas = struct { - nodes: ona.stack.Parallel(Node) = .{}, - drawable_blocks: ona.stack.Sequential(DrawableBlock) = .{}, - drawable_labels: ona.stack.Sequential(DrawableLabel) = .{}, + nodes: NodeMap = NodeMap.init(ona.heap.allocator), + drawable_blocks: DrawableBlockMap = DrawableBlockMap.init(ona.heap.allocator), + drawable_labels: DrawableLabelMap = DrawableLabelMap.init(ona.heap.allocator), fn Drawable(comptime State: type) type { return struct { - state: State, + clip: gfx.Rect, + box: Box, depth: f32, + state: State, }; } - const DrawableBlock = Drawable(struct { + const DrawableBlockMap = ona.SlotMap(Drawable(struct { background: gfx.Texture = .default, color: gfx.Color = gfx.colors.white, - }); + })); - const DrawableLabel = Drawable(struct { + const DrawableLabelMap = ona.SlotMap(Drawable(struct { text: ona.stack.Sequential(u8) = .{}, color: gfx.Color = gfx.colors.black, font: gfx.Font = .default, - }); + })); - const Node = struct { + const NodeMap = ona.SlotMap(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); - }; + const Self = @This(); + + fn clip(self: Self, nodes: NodeMap) gfx.Rect { + if (self.parent_index == unused_index) { + return self.box.rect(); + } + + return nodes.get(self.parent_index).?.box.rect(); + } + + fn depth(self: Self, nodes: NodeMap) f32 { + var parent_index = self.parent_index; + var value: f32 = 0; + + while (parent_index != unused_index) : (parent_index = nodes.get(parent_index).?.parent_index) { + value += 1; + } + + return value; + } + }); 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); @@ -52,48 +81,17 @@ pub const Canvas = struct { ona.heap.allocator.free(duped_name); } - const index = self.nodes.len(); - - try self.nodes.push_grow(.{ + return @enumFromInt(try self.nodes.insert(.{ .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| { + for (self.drawable_labels.values()) |*drawable_label| { drawable_label.state.text.deinit(); } @@ -102,21 +100,19 @@ pub const Canvas = struct { self.* = undefined; } - const label_depth_offset = 0.5; + pub fn set_block(self: *Canvas, item: Item, set: SetBlock) std.mem.Allocator.Error!void { + const node = self.nodes.get(@intFromEnum(item)).?; - 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), + if (node.block_index == unused_index) { + node.block_index = @intCast(try self.drawable_blocks.insert(.{ + .clip = node.clip(self.nodes), + .depth = node.depth(self.nodes), + .box = node.box, .state = .{}, })); } - const block = &self.drawable_blocks.values[block_index.*]; + const block = self.drawable_blocks.get(node.block_index).?; if (set.has_background) |background| { block.state.background = background; @@ -128,18 +124,18 @@ pub const Canvas = struct { } 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)).?; + const node = self.nodes.get(@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, + if (node.label_index == unused_index) { + node.label_index = @intCast(try self.drawable_labels.insert(.{ + .clip = node.clip(self.nodes), + .depth = node.depth(self.nodes), + .box = node.box, .state = .{}, })); } - const label = &self.drawable_labels.values[label_index.*]; + const label = self.drawable_labels.get(node.label_index).?; if (set.has_text) |text| { label.state.text.pop_all(); @@ -159,28 +155,29 @@ pub const Canvas = struct { } 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).?; + const node = self.nodes.get(@intFromEnum(item)).?; - parent_index.* = if (has_parent) |parent| @intFromEnum(parent) else Node.unused_index; + node.parent_index = if (has_parent) |parent| @intFromEnum(parent) else 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; + const depth = node.depth(self.nodes); + const clip = node.clip(self.nodes); - if (label_index_exists or block_index_exists) { - const depth = self.calculate_depth(item); + if (node.block_index != unused_index) { + const block = self.drawable_blocks.get(node.block_index).?; - if (label_index_exists) { - self.drawable_labels[label_index].depth = depth + label_depth_offset; - } + block.depth = depth; + block.clip = clip; + } - if (block_index_exists) { - self.drawable_labels[label_index].depth = depth; - } + if (node.label_index != unused_index) { + const label = self.drawable_labels.get(node.label_index).?; + + label.depth = depth; + label.clip = clip; } } + + const unused_index = std.math.maxInt(u32); }; pub const Item = enum (u32) { @@ -202,29 +199,40 @@ 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); +pub fn render(commands: gfx.Commands, canvas: ona.Read(Canvas)) !void { + // TODO: Investigate if scissor rects are necessary for clipping content that overflows. + for (canvas.state.drawable_blocks.values()) |block| { + try commands.set_scissor(.{ + .has_rect = block.clip, + }); - for (canvas.state.drawable_blocks.values, boxes) |block, box| { try commands.draw_texture(.{ + .size = .{block.box.width, block.box.height}, + .position = .{block.box.x, block.box.y}, .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| { + for (canvas.state.drawable_labels.values()) |label| { + try commands.set_scissor(.{ + .has_rect = label.clip, + }); + try commands.draw_font(.{ + .size = .{label.box.width, label.box.height}, .depth = label.depth, .text = label.state.text.values, - .position = .{box.left, box.top}, - .size = .{box.width, box.height}, + .position = .{label.box.x, label.box.y}, .tint = label.state.color, .font = label.state.font, }); } + + try commands.set_scissor(.{ + .has_rect = null, + }); } pub fn setup(world: *ona.World) !void { diff --git a/src/ona/World.zig b/src/ona/World.zig index b248fec..56a47c0 100644 --- a/src/ona/World.zig +++ b/src/ona/World.zig @@ -365,7 +365,7 @@ const Schedule = struct { errdefer { bundle.deinit(); - std.debug.assert(schedule.parallel_work_bundles.pop()); + std.debug.assert(schedule.parallel_work_bundles.pop() != null); } try populate_bundle(bundle, &schedule.graph, node); diff --git a/src/ona/dag.zig b/src/ona/dag.zig index 50c8694..b67c2ed 100644 --- a/src/ona/dag.zig +++ b/src/ona/dag.zig @@ -30,13 +30,13 @@ pub fn Graph(comptime Payload: type) type { } pub fn clear_edges(self: *Self) void { - for (self.table.values.slice(.edges)) |*edges| { + for (self.table.values.get(.edges)) |*edges| { edges.clear(); } } pub fn deinit(self: *Self) void { - for (self.table.values.slice(.edges)) |*edges| { + for (self.table.values.get(.edges)) |*edges| { edges.deinit(); } @@ -50,15 +50,17 @@ pub fn Graph(comptime Payload: type) type { return null; } - return self.table.values.get(.edges, @intFromEnum(node)).?.values; + return self.table.values.get(.edges)[@intFromEnum(node)].values; } pub fn exists(self: Self, node: Node) bool { - if (self.table.values.get(.is_occupied, @intFromEnum(node))) |is_occupied| { - return is_occupied.*; + const node_index = @intFromEnum(node); + + if (node_index >= self.table.values.len) { + return false; } - return false; + return self.table.values.get(.is_occupied)[node_index]; } pub fn get(self: Self, node: Node) ?*Payload { @@ -66,7 +68,7 @@ pub fn Graph(comptime Payload: type) type { return null; } - return self.table.values.get(.payload, @intFromEnum(node)).?; + return &self.table.values.get(.payload)[@intFromEnum(node)]; } pub fn init(allocator: std.mem.Allocator) Self { @@ -76,13 +78,11 @@ pub fn Graph(comptime Payload: type) type { } pub fn insert_edge(self: *Self, dependant_node: Node, edge_node: Node) std.mem.Allocator.Error!bool { - if (!self.exists(edge_node)) { + if (!self.exists(edge_node) or !self.exists(dependant_node)) { return false; } - const edges = self.table.values.get(.edges, @intFromEnum(dependant_node)) orelse { - return false; - }; + const edges = &self.table.values.get(.edges)[@intFromEnum(dependant_node)]; if (std.mem.indexOfScalar(Node, edges.values, edge_node) == null) { try edges.push_grow(edge_node); @@ -97,7 +97,7 @@ pub fn Graph(comptime Payload: type) type { pub fn nodes(self: *const Self) Nodes { return .{ - .occupied_table = self.table.values.slice(.is_occupied), + .occupied_table = self.table.values.get(.is_occupied), }; } @@ -106,7 +106,7 @@ pub fn Graph(comptime Payload: type) type { return false; } - std.debug.assert(self.table.values.set(.is_visited, @intFromEnum(node), true)); + self.table.values.get(.is_visited)[@intFromEnum(node)] = true; return true; } @@ -118,14 +118,14 @@ pub fn Graph(comptime Payload: type) type { const node_index = @intFromEnum(node); - self.table.values.get(.is_occupied, node_index).?.* = false; + self.table.values.get(.is_occupied)[node_index] = false; self.node_count -= 1; - return self.table.values.get(.payload, node_index).?.*; + return self.table.values.get(.payload)[node_index]; } pub fn reset_visited(self: *Self) void { - @memset(self.table.values.slice(.is_visited), false); + @memset(self.table.values.get(.is_visited), false); } pub fn visited(self: Self, node: Node) ?bool { @@ -133,7 +133,7 @@ pub fn Graph(comptime Payload: type) type { return null; } - return self.table.values.get(.is_visited, @intFromEnum(node)).?.*; + return self.table.values.get(.is_visited)[@intFromEnum(node)]; } }; } diff --git a/src/ona/ona.zig b/src/ona/ona.zig index f430dd8..4aeb1ee 100644 --- a/src/ona/ona.zig +++ b/src/ona/ona.zig @@ -635,6 +635,127 @@ pub fn Shared(comptime Value: type, comptime info: ShareInfo) type { }; } +pub fn SlotMap(comptime Value: type) type { + const Index = @Type(.{.Int = .{ + .bits = @bitSizeOf(usize) - 1, + .signedness = .unsigned, + }}); + + comptime std.debug.assert(@sizeOf(Index) == @sizeOf(usize)); + + return struct { + slots: stack.Sequential(Slot), + entries: stack.Parallel(Entry), + next: Index = 0, + + const Entry = struct { + value: Value, + erased: usize, + }; + + const Self = @This(); + + const Slot = packed struct { + index: Index, + is_occupied: bool, + }; + + pub fn deinit(self: *Self) void { + self.slots.deinit(); + self.entries.deinit(); + } + + pub fn get(self: Self, index: usize) ?*Value { + if (index >= self.slots.len()) { + return null; + } + + const slot = self.slots.values[index]; + + if (!slot.is_occupied) { + return null; + } + + return &self.entries.values.get(.value)[slot.index]; + } + + pub fn init(allocator: std.mem.Allocator) Self { + return .{ + .slots = .{.allocator = allocator}, + .entries = .{.allocator = allocator}, + }; + } + + pub fn insert(self: *Self, value: Value) std.mem.Allocator.Error!usize { + const index = self.next; + const slots_len: Index = @intCast(self.slots.len()); + + if (self.next == slots_len) { + try self.slots.push_grow(.{ + .index = slots_len + 1, + .is_occupied = false, + }); + } + + const slot = &self.slots.values[index]; + + std.debug.assert(!slot.is_occupied); + + self.next = slot.index; + + slot.* = .{ + .index = index, + .is_occupied = true, + }; + + try self.entries.push_grow(.{ + .value = value, + .erased = index, + }); + + return index; + } + + pub fn remove(self: *Self, index: usize) ?Value { + if (index >= self.slots.len()) { + return null; + } + + const slot = self.slots.values[index]; + + if (!slot.is_occupied) { + return null; + } + + const value = self.entries.values.get(.value)[slot.index]; + + std.debug.assert(self.entries.values.write(slot.index, .{ + .value = self.entries.get(.value).?.*, + .erased = self.entries.get(.erased).?.*, + })); + + std.debug.assert(self.entries.pop()); + + if (!self.entries.is_empty()) { + self.slots.values[self.entries.values.get(.erased)[slot.index]] = slot; + } + + self.slots.values[index] = .{ + .index = self.next, + .is_occupied = false, + }; + + self.next = @intCast(index); + + return value; + } + + pub fn values(self: Self) []Value { + return self.entries.values.get(.value); + } + }; +} + pub const SystemInfo = struct { execute: *const fn ([]const *const Parameter, *const [max_parameters]*anyopaque) anyerror!void, parameters: [max_parameters]*const Parameter = undefined, diff --git a/src/ona/slices.zig b/src/ona/slices.zig index 42defe9..2542e3a 100644 --- a/src/ona/slices.zig +++ b/src/ona/slices.zig @@ -35,15 +35,15 @@ pub fn Parallel(comptime Type: type) type { const all_fields = std.enums.values(Field); + pub fn get(self: Self, comptime field: Field) []align (alignment) Element(field) { + return self.ptr(field)[0 .. self.len]; + } + pub fn ptr(self: Self, comptime field: Field) [*]align (alignment) Element(field) { return @as([*]align (alignment) Element(field), @ptrCast(self.ptrs[@intFromEnum(field)])); } - pub fn slice(self: Self, comptime field: Field) []align (alignment) Element(field) { - return self.ptr(field)[0 .. self.len]; - } - - pub fn slice_all(self: Self, off: usize, len: usize) ?Self { + pub fn slice(self: Self, off: usize, len: usize) ?Self { if (len > self.len or off > len) { return null; } @@ -57,31 +57,13 @@ pub fn Parallel(comptime Type: type) type { return sliced; } - pub fn get(self: Self, comptime field: Field, index: usize) ?*Element(field) { - if (index >= self.len) { - return null; - } - - return &self.ptr(field)[index]; - } - - pub fn set(self: Self, comptime field: Field, index: usize, value: Element(field)) bool { - if (index >= self.len) { - return false; - } - - self.slice(field)[index] = value; - - return true; - } - - pub fn set_all(self: Self, index: usize, value: Type) bool { + pub fn write(self: Self, index: usize, value: Type) bool { if (index >= self.len) { return false; } inline for (0 .. fields.len) |i| { - self.slice(all_fields[i])[index] = @field(value, fields[i].name); + self.get(all_fields[i])[index] = @field(value, fields[i].name); } return true; @@ -123,12 +105,12 @@ pub fn parallel_alloc(comptime Element: type, allocator: std.mem.Allocator, n: u pub fn parallel_copy(comptime Element: type, target: Parallel(Element), origin: Parallel(Element)) void { inline for (comptime std.enums.values(Parallel(Element).Field)) |field| { - @memcpy(target.slice(field), origin.slice(field)); + @memcpy(target.get(field), origin.get(field)); } } pub fn parallel_free(comptime Element: type, allocator: std.mem.Allocator, buffers: Parallel(Element)) void { inline for (comptime std.enums.values(Parallel(Element).Field)) |field| { - allocator.free(buffers.slice(field)); + allocator.free(buffers.get(field)); } } diff --git a/src/ona/stack.zig b/src/ona/stack.zig index fff9ff2..bb4ceb6 100644 --- a/src/ona/stack.zig +++ b/src/ona/stack.zig @@ -25,11 +25,12 @@ pub fn Sequential(comptime Value: type) type { } pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void { - if (additional == 0) { + const grown_capacity = self.values.len + additional; + + if (grown_capacity <= self.cap) { return; } - const grown_capacity = self.cap + additional; const buffer = try self.allocator.alloc(Value, grown_capacity); errdefer self.allocator.deallocate(buffer); @@ -59,14 +60,16 @@ pub fn Sequential(comptime Value: type) type { return self.values.len; } - pub fn pop(self: *Self) bool { + pub fn pop(self: *Self) ?Value { if (self.values.len == 0) { - return false; + return null; } - self.values = self.values[0 .. self.values.len - 1]; + const tail_index = self.values.len - 1; - return true; + defer self.values = self.values[0 .. tail_index]; + + return self.values[tail_index]; } pub fn pop_all(self: *Self) void { @@ -116,9 +119,7 @@ pub fn Sequential(comptime Value: type) type { } pub fn push_grow(self: *Self, value: Value) std.mem.Allocator.Error!void { - if (self.values.len == self.cap) { - try self.grow(@max(1, self.cap)); - } + try self.grow(@max(1, self.values.len)); const offset_index = self.values.len; @@ -231,20 +232,29 @@ pub fn Parallel(comptime Value: type) type { return null; } - return &self.slices.field_slice(field)[self.len() - 1]; + return @alignCast(&self.values.get(field)[self.len() - 1]); } pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void { - const grown_capacity = self.cap + additional; + const grown_capacity = self.values.len + additional; + + if (grown_capacity <= self.cap) { + return; + } + const buffer = try ona.slices.parallel_alloc(Value, self.allocator, grown_capacity); if (self.cap != 0) { - ona.slices.parallel_copy(Value, buffer.slice_all(0, self.values.len).?, self.values); - ona.slices.parallel_free(Value, self.allocator, self.values.slice_all(0, self.cap).?); + ona.slices.parallel_copy(Value, buffer.slice(0, self.values.len).?, self.values); + ona.slices.parallel_free(Value, self.allocator, self.values.slice(0, self.cap).?); } self.cap = grown_capacity; - self.values = buffer.slice_all(0, self.values.len).?; + self.values = buffer.slice(0, self.values.len).?; + } + + pub fn is_empty(self: Self) bool { + return self.values.len == 0; } pub fn len(self: Self) usize { @@ -256,7 +266,7 @@ pub fn Parallel(comptime Value: type) type { return false; } - self.values = self.values.slice_all(0, self.values.len - 1).?; + self.values = self.values.slice(0, self.values.len - 1).?; return true; } @@ -266,21 +276,19 @@ pub fn Parallel(comptime Value: type) type { return false; }; - self.values = self.values.slice_all(0, new_length).?; + self.values = self.values.slice(0, new_length).?; return true; } pub fn push_grow(self: *Self, value: Value) std.mem.Allocator.Error!void { - if (self.len() == self.cap) { - try self.grow(@max(1, self.cap)); - } + try self.grow(@max(1, self.values.len)); const tail_index = self.values.len; self.values.len += 1; - std.debug.assert(self.values.set_all(tail_index, value)); + std.debug.assert(self.values.write(tail_index, value)); } }; }