diff --git a/build.zig b/build.zig index 07f8ac9..56e3720 100644 --- a/build.zig +++ b/build.zig @@ -109,26 +109,18 @@ pub fn build(b: *std.Build) void { const shaderc_dependency = b.dependency("shaderc_zig", .{}); + const sdl_dependency = b.dependency("sdl", .{ + .target = config.module_target, + .optimize = config.optimize, + .preferred_linkage = .static, + }); + const coral_module = b.addModule("coral", .{ .root_source_file = b.path("src/coral/coral.zig"), .target = config.module_target, .optimize = config.optimize, }); - const ext_module = b.createModule(.{ - .root_source_file = b.path("src/ext/ext.zig"), - .target = config.module_target, - .optimize = config.optimize, - .link_libc = true, - }); - - ext_module.linkSystemLibrary("SDL3", .{ - .needed = true, - .preferred_link_mode = .dynamic, - }); - - ext_module.linkLibrary(shaderc_dependency.artifact("shaderc")); - const ona_module = b.addModule("ona", .{ .root_source_file = b.path("src/ona/ona.zig"), .target = config.module_target, @@ -136,10 +128,6 @@ pub fn build(b: *std.Build) void { .link_libc = true, .imports = &.{ - .{ - .name = "ext", - .module = ext_module, - }, .{ .name = "coral", .module = coral_module, @@ -147,6 +135,9 @@ pub fn build(b: *std.Build) void { }, }); + ona_module.linkLibrary(shaderc_dependency.artifact("shaderc")); + ona_module.linkLibrary(sdl_dependency.artifact("SDL3")); + // config.addShaders(ona_module, &.{ // "./src/ona/gfx/effect_shader.zig", // "./src/ona/gfx/effect_fragment.zig", diff --git a/build.zig.zon b/build.zig.zon index 3867e7f..5c8fca4 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -7,6 +7,10 @@ .url = "git+https://github.com/tiawl/shaderc.zig#06565d2af3beec9780b11524984211ebd104fd21", .hash = "shaderc_zig-1.0.0-mOl840tjAwBiAnMSfRskq0Iq3JJ9jPRHy2JoEgnUvSpV", }, + .sdl = .{ + .url = "git+https://github.com/castholm/SDL.git#0f81c0affb2584b242b2fb5744e7dfebcfd904a5", + .hash = "sdl-0.2.6+3.2.20-7uIn9JkjfwGIQ6j3-etow2rCe-Zt16Yj-2gdp9jW7WZ9", + }, }, .fingerprint = 0x7d0142e88b22421d, diff --git a/src/coral/Box.zig b/src/coral/Box.zig index 85eb441..56d3bf8 100644 --- a/src/coral/Box.zig +++ b/src/coral/Box.zig @@ -2,40 +2,61 @@ const coral = @import("./coral.zig"); const std = @import("std"); -delete: *const fn (*anyopaque) void, +destroy: *const fn (*anyopaque) void, erased: *anyopaque, fn Layout(comptime Type: type) type { - return struct { - allocator: std.mem.Allocator, - value: Type, + return switch (Type) { + void => struct { + fn destroy(_: *anyopaque) void {} + }, - const TypeLayout = @This(); + else => struct { + allocator: std.mem.Allocator, + value: Type, - fn delete(erased: *anyopaque) void { - const layout: *TypeLayout = @ptrCast(@alignCast(erased)); + const TypeLayout = @This(); - if (@hasDecl(Type, "deinit")) { - layout.value.deinit(); + fn destroy(erased: *anyopaque) void { + const layout: *TypeLayout = @ptrCast(@alignCast(erased)); + + if (@hasDecl(Type, "deinit")) { + const deinit_fn = switch (@typeInfo(@TypeOf(Type.deinit))) { + .@"fn" => |@"fn"| @"fn", + + else => @compileError(std.fmt.comptimePrint("Declaration `{s}.deinit` must be an fn type to be boxable", .{ + @typeName(Type), + })), + }; + + if (deinit_fn.params.len != 1 or deinit_fn.params[0].type != *Type) { + @compileError(std.fmt.comptimePrint("Fn `{s}.deinit` is only permitted 1 parameter to be boxable and it must be of type {s}", .{ + @typeName(Type), + @typeName(*Type), + })); + } + + layout.value.deinit(); + } + + layout.allocator.destroy(layout); } - - layout.allocator.destroy(layout); - } + }, }; } const Self = @This(); pub fn deinit(self: *Self) void { - self.delete(self.erased); + self.destroy(self.erased); self.* = undefined; } -pub fn has(self: Self, comptime Type: type) ?*Type { - const ValueLayout = Layout(Type); +pub fn has(self: Self, comptime Value: type) ?*Value { + const ValueLayout = Layout(Value); - if (self.delete == ValueLayout.delete) { + if (self.destroy == ValueLayout.destroy) { const layout: *ValueLayout = @ptrCast(@alignCast(self.erased)); return &layout.value; @@ -44,25 +65,25 @@ pub fn has(self: Self, comptime Type: type) ?*Type { return null; } -pub fn init(value: anytype) std.mem.Allocator.Error!Self { - return initWithAllocator(coral.heap.allocator, value); -} - -pub fn initWithAllocator(allocator: std.mem.Allocator, value: anytype) std.mem.Allocator.Error!Self { - const ValueLayout = Layout(@TypeOf(value)); +pub fn init(allocator: std.mem.Allocator, value: anytype) std.mem.Allocator.Error!Self { + const Value = @TypeOf(value); + const ValueLayout = Layout(Value); const value_layout = try allocator.create(ValueLayout); errdefer { allocator.destroy(value_layout); } - value_layout.* = .{ - .allocator = allocator, - .value = value, - }; + if (@hasField(ValueLayout, "allocator")) { + value_layout.allocator = allocator; + } + + if (@hasField(ValueLayout, "value")) { + value_layout.value = value; + } return .{ .erased = @ptrCast(value_layout), - .delete = ValueLayout.delete, + .destroy = ValueLayout.destroy, }; } diff --git a/src/coral/asio.zig b/src/coral/asio.zig index 6af2a20..a39d899 100644 --- a/src/coral/asio.zig +++ b/src/coral/asio.zig @@ -251,10 +251,8 @@ pub const TaskQueue = struct { std.debug.assert(threads_spawned == thread_count); - const name = comptime try coral.ShortString.init("ona worker"); - for (threads) |thread| { - thread.setName(name.slice()) catch |set_name_error| { + thread.setName("ona worker") catch |set_name_error| { switch (set_name_error) { error.Unsupported, error.NameTooLong => break, else => continue, diff --git a/src/coral/coral.zig b/src/coral/coral.zig index fa09b83..b2b9ee2 100644 --- a/src/coral/coral.zig +++ b/src/coral/coral.zig @@ -10,12 +10,12 @@ pub const hashes = @import("./hashes.zig"); pub const heap = @import("./heap.zig"); -pub const list = @import("./list.zig"); - pub const map = @import("./map.zig"); pub const scalars = @import("./scalars.zig"); +pub const stack = @import("./stack.zig"); + const std = @import("std"); pub const tree = @import("./tree.zig"); @@ -113,243 +113,6 @@ pub fn KeyValuePair(comptime Key: type, comptime Value: type) type { }; } -pub const ShortString = extern struct { - buffer: [max]u8, - remaining: u8, - - pub const Error = error{ - StringTooLong, - }; - - pub fn append(self: *ShortString, c: u8) Error!void { - const post_remaining = scalars.sub(self.remaining, 1) orelse { - return error.StringTooLong; - }; - - self.buffer[self.len()] = c; - self.remaining = post_remaining; - } - - pub fn assign(self: *ShortString, data: []const u8) Error!void { - self.clear(); - - try self.join(data); - } - - pub fn clear(self: *ShortString) void { - self.* = empty; - } - - pub fn init(data: []const u8) Error!ShortString { - var string = empty; - - try string.join(data); - - return string; - } - - pub fn isEmpty(self: ShortString) bool { - return self.remaining == max; - } - - pub fn join(self: *ShortString, data: []const u8) Error!void { - const remaining = scalars.sub(self.remaining, data.len) orelse { - return error.StringTooLong; - }; - - @memcpy(self.buffer[self.len()..(max - remaining)], data); - - self.remaining = @intCast(remaining); - } - - pub const empty = ShortString{ - .buffer = [_]u8{0} ** max, - .remaining = max, - }; - - pub fn len(self: ShortString) usize { - return max - self.remaining; - } - - pub const max = 255; - - pub fn ptr(self: *const ShortString) [*:0]const u8 { - return @ptrCast(&self.buffer); - } - - pub fn slice(self: *const ShortString) [:0]const u8 { - return @ptrCast(self.ptr()[0..self.len()]); - } - - pub fn writeFormat(self: ShortString, output: bytes.Writable) bytes.ReadWriteError!void { - return bytes.writeAll(output, self.slice()); - } - - pub fn writer(self: *ShortString) bytes.Writable { - const writing = struct { - fn write(string: *ShortString, buffer: []const u8) usize { - string.append(buffer) catch { - return 0; - }; - - return buffer.len; - } - }; - - return .init(ShortString, self, writing.write); - } -}; - -pub fn Stack(comptime Value: type) type { - return struct { - values: []Value, - cap: usize, - - const Self = @This(); - - pub fn clear(self: *Self) void { - self.values = self.values[0..0]; - } - - pub fn deinit(self: *Self) void { - if (self.cap == 0) { - return; - } - - heap.allocator.free(self.values.ptr[0..self.cap]); - - self.* = undefined; - } - - pub const empty = Self{ - .values = &.{}, - .cap = 0, - }; - - pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void { - const grown_capacity = self.values.len + additional; - - if (grown_capacity <= self.cap) { - return; - } - - const buffer = try heap.allocator.alloc(Value, grown_capacity); - - errdefer { - heap.allocator.deallocate(buffer); - } - - if (self.cap != 0) { - @memcpy(buffer[0..self.values.len], self.values); - heap.allocator.free(self.values.ptr[0..self.cap]); - } - - self.values = buffer[0..self.values.len]; - self.cap = grown_capacity; - } - - pub fn isEmpty(self: Self) bool { - return self.values.len == 0; - } - - pub fn isFull(self: Self) bool { - return self.values.len == self.cap; - } - - pub fn get(self: Self) ?*Value { - return if (self.isEmpty()) null else &self.values[self.values.len - 1]; - } - - pub fn pop(self: *Self) ?Value { - if (self.isEmpty()) { - return null; - } - - const tail_index = self.values.len - 1; - - defer self.values = self.values[0..tail_index]; - - return self.values[tail_index]; - } - - pub fn push(self: *Self, value: Value) bool { - if (self.isFull()) { - return false; - } - - const offset_index = self.values.len; - - self.values = self.values.ptr[0 .. self.values.len + 1]; - self.values[offset_index] = value; - - return true; - } - - pub fn pushAll(self: *Self, values: []const Value) bool { - const new_length = self.values.len + values.len; - - if (new_length > self.cap) { - return false; - } - - const offset_index = self.values.len; - - self.values = self.values.ptr[0..new_length]; - - for (0..values.len) |index| { - self.values[offset_index + index] = values[index]; - } - - return true; - } - - pub fn pushGrow(self: *Self, value: Value) std.mem.Allocator.Error!void { - try self.grow(@max(1, self.values.len)); - - std.debug.assert(self.push(value)); - } - - pub fn pushMany(self: *Self, n: usize, value: Value) bool { - const new_length = self.values.len + n; - - if (new_length > self.cap) { - return false; - } - - const offset_index = self.values.len; - - self.values = self.values.ptr[0..new_length]; - - for (0..n) |index| { - self.values[offset_index + index] = value; - } - - return true; - } - - pub fn resize(self: *Self, size: usize, default_value: Value) std.mem.Allocator.Error!void { - if (self.cap == size) { - return; - } - - const values = try heap.allocator.alloc(Value, size); - - for (0..@min(values.len, self.values.len)) |i| { - values[i] = self.values[i]; - } - - if (values.len > self.values.len) { - for (self.values.len..values.len) |i| { - values[i] = default_value; - } - } - - self.values = values[0..values.len]; - self.cap = values.len; - } - }; -} - pub fn require(function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) switch (@typeInfo(@typeInfo(@TypeOf(function)).@"fn".return_type.?)) { .error_union => |error_union| error_union.payload, else => @compileError("fn parameter `function` must return an error union"), diff --git a/src/coral/list.zig b/src/coral/list.zig deleted file mode 100644 index a7ed735..0000000 --- a/src/coral/list.zig +++ /dev/null @@ -1,125 +0,0 @@ -const std = @import("std"); - -pub fn Linked(comptime Value: type, comptime block_size: usize) type { - return struct { - has_head_block: ?*Block, - has_tail_block: ?*Block, - - const Block = struct { - values: std.BoundedArray(Value, block_size) = .{}, - has_next: ?*Block = null, - }; - - const Self = @This(); - - pub const Values = struct { - has_block: ?*Block, - block_index: std.math.IntFittingRange(0, block_size), - - pub fn next(self: *Values) ?*Value { - var block = self.has_block orelse { - return null; - }; - - if (self.block_index >= block.values.len) { - self.has_block = block.has_next; - self.block_index = 0; - - block = self.has_block orelse { - return null; - }; - } - - defer { - self.block_index += 1; - } - - return &block.values.slice()[self.block_index]; - } - }; - - pub fn append(self: *Self, allocator: std.mem.Allocator, value: Value) std.mem.Allocator.Error!*Value { - const tail_block = self.has_tail_block orelse create: { - const block = try allocator.create(Block); - - block.* = .{}; - self.has_head_block = block; - self.has_tail_block = block; - - break :create block; - }; - - tail_block.values.append(value) catch { - const block = try allocator.create(Block); - - block.* = .{}; - tail_block.has_next = block; - self.has_tail_block = block; - - block.values.append(value) catch { - unreachable; - }; - }; - - return &tail_block.values.slice()[tail_block.values.len - 1]; - } - - pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { - var blocks = self.has_head_block; - - while (blocks) |block| { - const has_next = block.has_next; - - allocator.destroy(block); - - blocks = has_next; - } - - self.has_head_block = undefined; - self.has_tail_block = undefined; - } - - pub const empty = Self{ - .has_head_block = null, - .has_tail_block = null, - }; - - pub fn get(self: Self, index: usize) ?*Value { - if (self.has_tail_block) |tail_block| { - if (tail_block.values.len == 0) { - std.debug.assert(self.has_head_block == self.has_tail_block); - - return if (tail_block.values.len == 0) null else tail_block.values.slice()[index]; - } - } - - return null; - } - - pub fn isEmpty(self: Self) bool { - return self.has_head_block == null; - } - - pub fn len(self: Self) usize { - if (self.has_tail_block) |tail_block| { - var accounted = tail_block.values.len; - var blocks = self.has_head_block; - - while (blocks != self.has_tail_block) : (blocks = blocks.?.has_next) { - accounted += block_size; - } - - return accounted; - } - - return 0; - } - - pub fn values(self: *const Self) Values { - return Values{ - .has_block = self.has_head_block, - .block_index = 0, - }; - } - }; -} diff --git a/src/coral/stack.zig b/src/coral/stack.zig new file mode 100644 index 0000000..ad41509 --- /dev/null +++ b/src/coral/stack.zig @@ -0,0 +1,238 @@ +const std = @import("std"); + +fn Generic(comptime Item: type, comptime Buffer: type) type { + return struct { + items: Buffer, + + const Self = @This(); + + pub fn clear(self: *Self) void { + self.items.len = 0; + } + + pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { + self.items.deinit(allocator); + } + + pub const empty = Self{ + .items = .{}, + }; + + pub fn isEmpty(self: Self) bool { + return self.items.len == 0; + } + + pub fn isFull(self: Self) bool { + return self.items.len == self.items.cap; + } + + pub fn get(self: Self) ?Item { + return if (self.isEmpty()) null else self.items.get(self.items.len - 1).?; + } + + pub fn pop(self: *Self) ?Item { + if (self.isEmpty()) { + return null; + } + + const tail_index = self.items.len - 1; + + defer { + self.items.len = tail_index; + } + + return self.items.get(tail_index).?; + } + + pub fn push(self: *Self, item: Item) bool { + if (self.isFull()) { + return false; + } + + const index = self.items.len; + + self.items.len += 1; + + std.debug.assert(self.items.set(index, item)); + + return true; + } + + pub fn pushAll(self: *Self, items: []const Item) bool { + const new_len = self.items.len + items.len; + + if (new_len < self.cap) { + const index = self.items.len; + + self.items.len = new_len; + + std.debug.assert(self.items.sliced(index, self.items.len).copy(items)); + + return true; + } + + return false; + } + + pub fn pushGrow(self: *Self, allocator: std.mem.Allocator, item: Item) std.mem.Allocator.Error!void { + if (self.isFull()) { + try self.items.grow(allocator, @max(1, self.items.cap)); + } + + std.debug.assert(self.push(item)); + } + + pub fn pushMany(self: *Self, n: usize, item: Item) bool { + const new_len = self.items.len + n; + + if (new_len > self.cap) { + return false; + } + + const offset = self.items.len; + + self.items.len = new_len; + + for (offset..(offset + n)) |i| { + std.debug.assert(self.items.set(i, item)); + } + + return true; + } + }; +} + +pub fn Parallel(comptime Item: type) type { + const item_fields = switch (@typeInfo(Item)) { + .@"struct" => |@"struct"| @"struct".fields, + else => @compileError("`Item` must be a struct type"), + }; + + const item_align = @alignOf(Item); + const item_size = @sizeOf(usize); + const byte_size = @sizeOf(u8); + + return Generic(Item, struct { + ptr: [*]align(item_align) u8 = undefined, + len: u32 = 0, + cap: u32 = 0, + + pub const Field = std.meta.FieldEnum(Item); + + const Self = @This(); + + pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { + if (self.cap != 0) { + allocator.free(self.ptr[0 .. item_size * self.cap]); + } + + self.* = undefined; + } + + pub fn get(self: Self, index: usize) ?Item { + if (index >= self.cap) { + return null; + } + + var ptr: [*]u8 = self.ptr; + var item: Item = undefined; + + inline for (item_fields) |item_field| { + @field(item, item_field.name) = @as([*]item_field.type, @ptrCast(@alignCast(ptr)))[index]; + ptr += @sizeOf(item_field.type) * self.len; + } + + return item; + } + + pub fn grow(self: *Self, allocator: std.mem.Allocator, amount: usize) std.mem.Allocator.Error!void { + const new_cap = std.math.cast(u32, @as(usize, self.cap + amount)) orelse { + return error.OutOfMemory; + }; + + const reallocation = try allocator.alignedAlloc(u8, item_align, byte_size * new_cap); + const len_in_bytes = byte_size * self.len; + + @memcpy(reallocation[0..len_in_bytes], self.ptr[0..len_in_bytes]); + allocator.free(self.ptr[0 .. byte_size * self.cap]); + + self.ptr = reallocation.ptr; + self.cap = @intCast(new_cap); + } + + pub fn set(self: Self, index: usize, item: Item) bool { + if (index >= self.cap) { + return false; + } + + var ptr: [*]u8 = self.ptr; + + inline for (item_fields) |item_field| { + @as([*]item_field.type, @ptrCast(@alignCast(ptr)))[index] = @field(item, item_field.name); + ptr += @sizeOf(item_field.type) * self.len; + } + + return true; + } + + pub fn slice(self: Self, comptime field: Field) []item_fields[@intFromEnum(field)].type { + const field_index = @intFromEnum(field); + var ptr: [*]u8 = self.ptr; + + inline for (item_fields[0..field_index]) |item_field| { + ptr += @sizeOf(item_field.type) * self.len; + } + + return @as([*]item_fields[field_index].type, @ptrCast(@alignCast(ptr)))[0..self.len]; + } + }); +} + +pub fn Sequential(comptime Item: type) type { + return Generic(Item, struct { + ptr: [*]Item = undefined, + len: u32 = 0, + cap: u32 = 0, + + const Self = @This(); + + pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { + if (self.cap != 0) { + allocator.free(self.ptr[0..self.cap]); + } + + self.* = undefined; + } + + pub fn get(self: *Self, index: usize) ?Item { + if (index >= self.cap) { + return null; + } + + return self.ptr[index]; + } + + pub fn grow(self: *Self, allocator: std.mem.Allocator, amount: usize) std.mem.Allocator.Error!void { + const new_cap = std.math.cast(u32, @as(usize, self.cap + amount)) orelse { + return error.OutOfMemory; + }; + + self.ptr = (try allocator.realloc(self.ptr[0..self.cap], new_cap)).ptr; + self.cap = @intCast(new_cap); + } + + pub fn set(self: *Self, index: usize, value: Item) bool { + if (index >= self.cap) { + return false; + } + + self.ptr[index] = value; + + return true; + } + + pub fn slice(self: Self) []Item { + return self.ptr[0..self.len]; + } + }); +} diff --git a/src/demos/graphics.zig b/src/demos/graphics.zig index f8373d7..67f5f4c 100644 --- a/src/demos/graphics.zig +++ b/src/demos/graphics.zig @@ -4,6 +4,7 @@ const std = @import("std"); pub fn main() void { ona.realtime_app + .with(.initModule(ona.hid)) .with(.initModule(ona.gfx)) .run(); } diff --git a/src/ext/ext.zig b/src/ext/ext.zig deleted file mode 100644 index 812aa47..0000000 --- a/src/ext/ext.zig +++ /dev/null @@ -1,4 +0,0 @@ -pub usingnamespace @cImport({ - @cInclude("SDL3/SDL.h"); - @cInclude("shaderc/shaderc.h"); -}); diff --git a/src/ona/App.zig b/src/ona/App.zig index 9cf1642..7af8b87 100644 --- a/src/ona/App.zig +++ b/src/ona/App.zig @@ -75,6 +75,8 @@ pub fn init() std.mem.Allocator.Error!Self { .elapsed = 0, }); + try ona.registerChannel(&self, Exit); + return self; } @@ -99,7 +101,7 @@ pub fn run(self: *Self, tasks: *coral.asio.TaskQueue, comptime schedule: anytype } pub fn setState(self: *Self, state: anytype) std.mem.Allocator.Error!void { - var boxed_state = try coral.Box.init(state); + var boxed_state = try coral.Box.init(coral.heap.allocator, state); errdefer { boxed_state.deinit(); diff --git a/src/ona/App/Behavior.zig b/src/ona/App/Behavior.zig index c83988b..976712c 100644 --- a/src/ona/App/Behavior.zig +++ b/src/ona/App/Behavior.zig @@ -7,73 +7,41 @@ const ona = @import("../ona.zig"); const std = @import("std"); label: [*:0]const u8, -on_insertion: *const fn (*const Self, *ona.App, *SystemGraph) std.mem.Allocator.Error!void, -on_run: *const fn (*ona.App) void, -traits: ona.Traits, +onInsertion: *const fn (*const Self, std.mem.Allocator, *ona.App, *SystemGraph) std.mem.Allocator.Error![]coral.Box, +onRun: *const fn (*ona.App, []const coral.Box) void, +is_blocking: bool, const Param = struct { + uses_bind: bool, init_params: []const InitParam, - has_bind: ?fn (*ona.App) void = null, - traits: ona.Traits = .{}, + is_blocking: bool, const InitParam = struct { - type: type, + type_id: *const coral.TypeId, is_required: bool, is_read_only: bool, - traits: ona.Traits, }; - fn has_fn(comptime SystemParam: type, comptime name: []const u8) ?std.builtin.Type.Fn { - switch (@typeInfo(SystemParam)) { - .@"struct", .@"opaque" => {}, - else => @compileError("system fn params must be either struct or opaque types"), - } - - if (@hasDecl(SystemParam, name)) { - return switch (@typeInfo(@TypeOf(@field(SystemParam, name)))) { - .@"fn" => |@"fn"| @"fn", - else => @compileError(std.fmt.comptimePrint("system params with a .{s} declaration must be made an fn type", .{name})), - }; - } - - return null; - } - - pub fn init(comptime fn_param: std.builtin.Type.Fn.Param) Param { - if (fn_param.is_generic) { - @compileError("generic params on behavior fns are disallowed"); - } - - const init_fn = has_fn(fn_param.type.?, "init") orelse { - @compileError("behavior params must declare a .init fn"); - }; - - if (init_fn.return_type != fn_param.type.?) { - const param_type_name = @typeName(fn_param.type.?); - - @compileError(std.fmt.comptimePrint("{s}.init must return {s}", .{ param_type_name, param_type_name })); - } - + fn getInitParams(params: []const std.builtin.Type.Fn.Param) []const InitParam { const init_params = struct { const instance = get: { - var buffer: [init_fn.params.len]InitParam = undefined; + var buffer: [params.len]InitParam = undefined; - for (&buffer, init_fn.params) |*init_param, init_fn_param| { - if (init_fn_param.is_generic) { + for (&buffer, params) |*init_param, fn_param| { + const FnParam = fn_param.type orelse { @compileError("generic params on Param.init fns are disallowed"); - } + }; - init_param.* = switch (@typeInfo(init_fn_param.type.?)) { + init_param.* = switch (@typeInfo(FnParam)) { .pointer => |pointer| .{ - .type = pointer.child, + .type_id = .of(pointer.child), .is_required = true, .is_read_only = pointer.is_const, - .traits = if (@hasDecl(pointer.child, "traits") and @TypeOf(pointer.child.traits) == ona.Traits) pointer.child.traits else .{}, }, .optional => |optional| switch (@typeInfo(optional.child)) { .pointer => |pointer| .{ - .type = pointer.child, + .type_id = .of(pointer.child), .is_required = false, .is_read_only = pointer.is_const, }, @@ -89,47 +57,50 @@ const Param = struct { }; }; - var param = Param{ - .init_params = &init_params.instance, + return &init_params.instance; + } + + pub fn init(comptime fn_param: std.builtin.Type.Fn.Param) Param { + const FnParam = fn_param.type orelse { + @compileError("generic behavior params are not permitted"); }; - if (has_fn(fn_param.type.?, "bind")) |bind_fn| { - const type_error_message = std.fmt.comptimePrint("{s}.bind must match the signature fn ({s}) !{s}", .{ - @typeName(@TypeOf(fn_param.type.?.bind)), - @typeName(*ona.App), - @typeName(void), - }); + const init_fn = hasFn(FnParam, "init", null) orelse { + @compileError(std.fmt.comptimePrint("{s} must contain a .init declaration", .{@typeName(FnParam)})); + }; - if (bind_fn.params.len != 1) { - @compileError(type_error_message); - } + comptime var uses_bind = false; - if (bind_fn.params[0].type.? != *ona.App) { - @compileError(type_error_message); - } - - const binding = struct { - fn bind(app: *ona.App) void { - return switch (@typeInfo(bind_fn.return_type.?)) { - .error_union => coral.require(fn_param.type.?.bind, .{app}), - .void => fn_param.type.?.bind(app), - else => @compileError(type_error_message), - }; - } + if (hasFn(FnParam, "bind", &.{})) |bind_fn| { + const State = switch (@typeInfo(bind_fn.return_type.?)) { + .error_union => |error_union| error_union.payload, + else => bind_fn.return_type.?, }; - param.has_bind = binding.bind; + if (init_fn.params.len == 0 or init_fn.params[0].type != *State) { + const fn_param_name = @typeName(FnParam); + + @compileError(std.fmt.comptimePrint("The first parameter of fn {s}.init must be of type {s} while fn {s}.bind is present", .{ + fn_param_name, + @typeName(*State), + fn_param_name, + })); + } + + uses_bind = true; } - if (@hasDecl(fn_param.type.?, "traits") and @TypeOf(fn_param.type.?.traits) == ona.Traits) { - param.traits = fn_param.type.?.traits; + if (init_fn.return_type != FnParam) { + const param_type_name = @typeName(FnParam); + + @compileError(std.fmt.comptimePrint("Fn {s}.init must return {s}", .{ param_type_name, param_type_name })); } - inline for (param.init_params) |init_param| { - param.traits = param.traits.derived(init_param.traits); - } - - return param; + return .{ + .init_params = getInitParams(init_fn.params[@intFromBool(uses_bind)..]), + .is_blocking = usesTrait(FnParam, "blocking"), + .uses_bind = uses_bind, + }; } }; @@ -138,18 +109,18 @@ const Self = @This(); pub fn after(comptime self: *const Self, comptime dependency: *const Self) *const Self { const afters = struct { fn on_insertion(behavior: *const Self, app: *ona.App, systems: *SystemGraph) std.mem.Allocator.Error!void { - try self.on_insertion(behavior, app, systems); + try self.onInsertion(behavior, app, systems); try systems.depend_on_behavior(app, behavior, dependency); } fn on_run(app: *ona.App) void { - self.on_run(app); + self.onRun(app); } const instance = Self{ .label = std.fmt.comptimePrint("({s} after {s})", .{ self.label, dependency.label }), - .on_insertion = on_insertion, - .on_run = on_run, + .onInsertion = on_insertion, + .onRun = on_run, }; }; @@ -165,13 +136,61 @@ pub fn before(comptime self: *const Self, comptime dependant: *const Self) *cons const instance = Self{ .label = std.fmt.comptimePrint("({s} before {s})", .{ self.label, dependant.label }), - .on_insertion = on_insertion, + .onInsertion = on_insertion, }; }; return &afters.instance; } +fn hasFn(comptime SystemParam: type, comptime name: []const u8, comptime has_expected_param_types: ?[]const type) ?std.builtin.Type.Fn { + switch (@typeInfo(SystemParam)) { + .@"struct", .@"opaque" => {}, + + else => @compileError(std.fmt.comptimePrint("{s} must be a struct or opaque types", .{ + @typeName(SystemParam), + })), + } + + if (@hasDecl(SystemParam, name)) { + const named_fn = switch (@typeInfo(@TypeOf(@field(SystemParam, name)))) { + .@"fn" => |@"fn"| @"fn", + + else => @compileError(std.fmt.comptimePrint("{s}.{s} declaration must be made an fn type", .{ + @typeName(SystemParam), + name, + })), + }; + + if (has_expected_param_types) |expected_param_types| { + if (named_fn.params.len != expected_param_types.len) { + @compileError(std.fmt.comptimePrint("{s}.{s} fn must accept {d} parameters, not {d}", .{ + @typeName(SystemParam), + name, + expected_param_types.len, + named_fn.params.len, + })); + } + + inline for (named_fn.params, expected_param_types, 1..expected_param_types.len + 1) |fn_param, ExpectedParam, i| { + if (fn_param.type != ExpectedParam) { + @compileError(std.fmt.comptimePrint("Parameter {d} of {s}.{s} fn must be {s}, not {s}", .{ + i, + @typeName(SystemParam), + name, + @typeName(ExpectedParam), + @typeName(fn_param.type.?), + })); + } + } + } + + return named_fn; + } + + return null; +} + pub fn of(comptime call: anytype) *const Self { const Call = @TypeOf(call); @@ -181,41 +200,62 @@ pub fn of(comptime call: anytype) *const Self { }; const behaviors = struct { - fn on_insertion(behavior: *const Self, app: *ona.App, systems: *SystemGraph) std.mem.Allocator.Error!void { + fn onInsertion(behavior: *const Self, allocator: std.mem.Allocator, app: *ona.App, systems: *SystemGraph) std.mem.Allocator.Error![]coral.Box { + var states: [call_fn.params.len]coral.Box = undefined; + comptime var states_written = 0; + + errdefer { + inline for (states[states_written..]) |*state| { + state.deinit(); + } + } + inline for (call_fn.params) |call_param| { - const behavior_param = Param.init(call_param); + const behavior_param = comptime Param.init(call_param); inline for (behavior_param.init_params) |init_param| { try systems.dependOnType(app, behavior, .{ - .id = .of(init_param.type), + .id = init_param.type_id, .is_read_only = init_param.is_read_only, }); } - if (behavior_param.has_bind) |bind| { - bind(app); - } + states[states_written] = try .init(allocator, if (behavior_param.uses_bind) call_param.type.?.bind() else {}); + states_written += 1; } + + return allocator.dupe(coral.Box, &states); } - fn on_run(app: *ona.App) void { + fn onRun(app: *ona.App, states: []const coral.Box) void { var call_args: std.meta.ArgsTuple(Call) = undefined; - inline for (&call_args, call_fn.params) |*call_arg, call_fn_param| { - const call_param = Param.init(call_fn_param); - var init_args: std.meta.ArgsTuple(@TypeOf(call_fn_param.type.?.init)) = undefined; + std.debug.assert(states.len == call_args.len); - inline for (&init_args, call_param.init_params) |*init_arg, init_param| { + inline for (0..call_args.len) |i| { + const fn_param = call_fn.params[i]; + const param = comptime Param.init(fn_param); + var init_args: std.meta.ArgsTuple(@TypeOf(fn_param.type.?.init)) = undefined; + + if (param.uses_bind) { + init_args[0] = states[i].has(@TypeOf(init_args[0].*)).?; + } + + inline for (@intFromBool(param.uses_bind)..init_args.len, param.init_params) |arg_index, init_param| { if (init_param.is_required) { - init_arg.* = app.hasState(init_param.type) orelse { - @panic(std.fmt.comptimePrint("{s} is a required state but not present in the App", .{@typeName(init_param.type)})); + const InitArg = @TypeOf(init_args[arg_index].*); + + init_args[arg_index] = app.hasState(InitArg) orelse { + @panic(std.fmt.comptimePrint("{s} is a required state but not present in the App", .{@typeName(InitArg)})); }; } else { - init_arg.* = app.hasState(init_param.type); + const InitArg = @TypeOf(init_args[arg_index].*); + + init_args[arg_index] = app.hasState(InitArg); } } - call_arg.* = @call(.auto, call_fn_param.type.?.init, init_args); + call_args[i] = @call(.auto, fn_param.type.?.init, init_args); } switch (@typeInfo(call_fn.return_type.?)) { @@ -230,20 +270,52 @@ pub fn of(comptime call: anytype) *const Self { const instance = Self{ .label = @typeName(Call), - .on_insertion = on_insertion, - .on_run = on_run, + .onInsertion = onInsertion, + .onRun = onRun, - .traits = derive: { - var derived_traits = ona.Traits{}; + .is_blocking = check: { + for (call_fn.params) |fn_param| { + const param = Param.init(fn_param); - for (call_fn.params) |call_param| { - derived_traits = derived_traits.derived(Param.init(call_param).traits); + if (param.is_blocking) { + break :check true; + } } - break :derive derived_traits; + break :check false; }, }; }; return &behaviors.instance; } + +fn usesTrait(comptime SystemParam: type, name: []const u8) bool { + if (@hasDecl(SystemParam, "traits")) { + const Traits = @TypeOf(SystemParam.traits); + + const traits_struct = switch (@typeInfo(Traits)) { + .@"struct" => |@"struct"| @"struct", + + else => @compileError(std.fmt.comptimePrint("{s}.traits must be a struct type", .{ + @typeName(SystemParam), + })), + }; + + if (!traits_struct.is_tuple) { + @compileError(std.fmt.comptimePrint("{s}.traits must be a tuple", .{@typeName(SystemParam)})); + } + + for (traits_struct.fields) |field| { + if (@typeInfo(field.type) != .enum_literal) { + @compileError("All members of tuple {s}.traits must be enum literals"); + } + + if (std.mem.eql(u8, @tagName(@field(SystemParam.traits, field.name)), name)) { + return true; + } + } + } + + return false; +} diff --git a/src/ona/App/SystemGraph.zig b/src/ona/App/SystemGraph.zig index e41a877..6aa4af6 100644 --- a/src/ona/App/SystemGraph.zig +++ b/src/ona/App/SystemGraph.zig @@ -4,22 +4,49 @@ const ona = @import("../ona.zig"); const std = @import("std"); -dependants_edges: Map(BehaviorSet), +edges: Map(Edge), processed: Map(void), state_readers: AccessMap, state_writers: AccessMap, -blocking_work: BehaviorSet, -parallel_work: BehaviorSet, -parallel_work_ranges: coral.Stack(usize), +blocking_work: WorkSet, +parallel_work: WorkSet, +parallel_work_ranges: coral.stack.Sequential(usize), const AccessMap = coral.tree.Binary(*const coral.TypeId, BehaviorSet, coral.tree.scalarTraits(*const coral.TypeId)); -const BehaviorSet = coral.Stack(*const ona.App.Behavior); +const BehaviorSet = coral.stack.Sequential(*const ona.App.Behavior); + +const Edge = struct { + dependencies: BehaviorSet = .empty, + param_states: []coral.Box = &.{}, + + pub fn deinit(self: *Edge, allocator: std.mem.Allocator) void { + for (self.param_states) |*param_state| { + param_state.deinit(); + } + + allocator.free(self.param_states); + self.dependencies.deinit(allocator); + + self.param_states = undefined; + } +}; fn Map(comptime Payload: type) type { return coral.tree.Binary(*const ona.App.Behavior, Payload, coral.tree.scalarTraits(*const ona.App.Behavior)); } +const Processed = struct { + is_blocking: bool, +}; + +const Work = struct { + behavior: *const ona.App.Behavior, + param_states: []const coral.Box = &.{}, +}; + +const WorkSet = coral.stack.Sequential(Work); + const Self = @This(); pub const RunError = error{ @@ -33,15 +60,15 @@ pub const TypeDependency = struct { pub fn deinit(self: *Self) void { self.processed.deinit(coral.heap.allocator); - self.parallel_work.deinit(); - self.parallel_work_ranges.deinit(); - self.blocking_work.deinit(); + self.parallel_work.deinit(coral.heap.allocator); + self.parallel_work_ranges.deinit(coral.heap.allocator); + self.blocking_work.deinit(coral.heap.allocator); - inline for (.{ &self.dependants_edges, &self.state_readers, &self.state_writers }) |map| { + inline for (.{ &self.edges, &self.state_readers, &self.state_writers }) |map| { var key_values = map.keyValues(); while (key_values.nextValue()) |value| { - value.deinit(); + value.deinit(coral.heap.allocator); } map.deinit(coral.heap.allocator); @@ -51,56 +78,56 @@ pub fn deinit(self: *Self) void { pub fn dependOnBehavior(self: *Self, app: *ona.App, dependant: *const ona.App.Behavior, dependency: *const ona.App.Behavior) std.mem.Allocator.Error!void { try self.insert(app, dependant); - const edges = self.dependants_edges.get(dependant) orelse (try self.dependants_edges.insert(coral.heap.allocator, dependant, .empty)).?; + const edges = self.edges.get(dependant) orelse (try self.edges.insert(coral.heap.allocator, dependant, .{})).?; - if (std.mem.indexOfScalar(*const ona.App.Behavior, edges.values, dependency) == null) { - try edges.pushGrow(dependency); + if (std.mem.indexOfScalar(*const ona.App.Behavior, edges.dependencies.items.slice(), dependency) == null) { + try edges.dependencies.pushGrow(coral.heap.allocator, dependency); } } pub fn dependOnType(self: *Self, app: *ona.App, dependant: *const ona.App.Behavior, dependency: TypeDependency) std.mem.Allocator.Error!void { const readers = self.state_readers.get(dependency.id) orelse (try self.state_readers.insert(coral.heap.allocator, dependency.id, .empty)).?; - if (std.mem.indexOfScalar(*const ona.App.Behavior, readers.values, dependant)) |index| { - for (readers.values[0..index]) |reader| { + if (std.mem.indexOfScalar(*const ona.App.Behavior, readers.items.slice(), dependant)) |index| { + for (readers.items.slice()[0..index]) |reader| { try self.dependOnBehavior(app, dependant, reader); } - for (readers.values[index + 1 ..]) |reader| { + for (readers.items.slice()[index + 1 ..]) |reader| { try self.dependOnBehavior(app, dependant, reader); } } else { - for (readers.values) |reader| { + for (readers.items.slice()) |reader| { try self.dependOnBehavior(app, dependant, reader); } - try readers.pushGrow(dependant); + try readers.pushGrow(coral.heap.allocator, dependant); } if (!dependency.is_read_only) { const writers = self.state_writers.get(dependency.id) orelse (try self.state_writers.insert(coral.heap.allocator, dependency.id, .empty)).?; - if (std.mem.indexOfScalar(*const ona.App.Behavior, writers.values, dependant)) |index| { - for (writers.values[0..index]) |reader| { + if (std.mem.indexOfScalar(*const ona.App.Behavior, writers.items.slice(), dependant)) |index| { + for (writers.items.slice()[0..index]) |reader| { try self.dependOnBehavior(app, dependant, reader); } - for (writers.values[index..]) |reader| { + for (writers.items.slice()[index..]) |reader| { try self.dependOnBehavior(app, dependant, reader); } } else { - for (writers.values) |reader| { + for (writers.items.slice()) |reader| { try self.dependOnBehavior(app, dependant, reader); } - try writers.pushGrow(dependant); + try writers.pushGrow(coral.heap.allocator, dependant); } } } pub const empty = Self{ + .edges = .empty, .processed = .empty, - .dependants_edges = .empty, .state_readers = .empty, .state_writers = .empty, .blocking_work = .empty, @@ -111,36 +138,43 @@ pub const empty = Self{ pub fn insert(self: *Self, app: *ona.App, behavior: *const ona.App.Behavior) std.mem.Allocator.Error!void { self.processed.clear(); - if (try self.dependants_edges.insert(coral.heap.allocator, behavior, .empty) != null) { - try behavior.on_insertion(behavior, app, self); + if (try self.edges.insert(coral.heap.allocator, behavior, .{})) |edge| { + edge.param_states = try behavior.onInsertion(behavior, coral.heap.allocator, app, self); } } -fn parallelRun(app: *ona.App, behaviors: []const *const ona.App.Behavior) void { - for (behaviors) |behavior| { - behavior.on_run(app); +fn parallelRun(app: *ona.App, works: []const Work) void { + for (works) |work| { + work.behavior.onRun(app, work.param_states); } } -fn process(self: *Self, behavior: *const ona.App.Behavior, dependencies: BehaviorSet) !ona.Traits { - var inherited_traits = behavior.traits; +fn process(self: *Self, behavior: *const ona.App.Behavior, edge: Edge) !Processed { + var processed = Processed{ .is_blocking = behavior.is_blocking }; - for (dependencies.values) |dependency| { - const traits = try self.process(dependency, (self.dependants_edges.get(dependency) orelse { + for (edge.dependencies.items.slice()) |dependency| { + const processed_dependency = try self.process(dependency, (self.edges.get(dependency) orelse { return error.MissingDependency; }).*); - inherited_traits = inherited_traits.derived(traits); + processed.is_blocking = processed.is_blocking or processed_dependency.is_blocking; } if (try self.processed.insert(coral.heap.allocator, behavior, {}) != null) { - try switch (inherited_traits.is_thread_unsafe) { - true => self.blocking_work.pushGrow(behavior), - false => self.parallel_work.pushGrow(behavior), + try switch (processed.is_blocking) { + true => self.blocking_work.pushGrow(coral.heap.allocator, .{ + .behavior = behavior, + .param_states = edge.param_states, + }), + + false => self.parallel_work.pushGrow(coral.heap.allocator, .{ + .behavior = behavior, + .param_states = edge.param_states, + }), }; } - return inherited_traits; + return processed; } pub fn run(self: *Self, app: *ona.App, tasks: *coral.asio.TaskQueue) (std.mem.Allocator.Error || RunError)!void { @@ -153,19 +187,19 @@ pub fn run(self: *Self, app: *ona.App, tasks: *coral.asio.TaskQueue) (std.mem.Al self.parallel_work.clear(); self.parallel_work_ranges.clear(); - var dependants_edges = self.dependants_edges.keyValues(); + var dependants_edges = self.edges.keyValues(); while (dependants_edges.next()) |dependants_edge| { - const parallel_work_offset = self.parallel_work.values.len; - const flags = try self.process(dependants_edge.key, dependants_edge.value.*); + const parallel_work_offset = self.parallel_work.items.len; + const processed = try self.process(dependants_edge.key, dependants_edge.value.*); - if (flags.is_thread_unsafe) { - std.debug.assert(parallel_work_offset == self.parallel_work.values.len); + if (processed.is_blocking) { + std.debug.assert(parallel_work_offset == self.parallel_work.items.len); } else { - const parallel_work_added = self.parallel_work.values.len - parallel_work_offset; + const parallel_work_added = self.parallel_work.items.len - parallel_work_offset; if (parallel_work_added != 0) { - try self.parallel_work_ranges.pushGrow(parallel_work_added); + try self.parallel_work_ranges.pushGrow(coral.heap.allocator, parallel_work_added); } } } @@ -173,8 +207,8 @@ pub fn run(self: *Self, app: *ona.App, tasks: *coral.asio.TaskQueue) (std.mem.Al var parallel_work_offset: usize = 0; - for (self.parallel_work_ranges.values) |parallel_work_range| { - const work = self.parallel_work.values[parallel_work_offset .. parallel_work_offset + parallel_work_range]; + for (self.parallel_work_ranges.items.slice()) |parallel_work_range| { + const work = self.parallel_work.items.slice()[parallel_work_offset .. parallel_work_offset + parallel_work_range]; try tasks.execute(coral.asio.CallTask(parallelRun).init(.{ app, work })); @@ -183,7 +217,7 @@ pub fn run(self: *Self, app: *ona.App, tasks: *coral.asio.TaskQueue) (std.mem.Al tasks.finish(); - for (self.blocking_work.values) |behavior| { - behavior.on_run(app); + for (self.blocking_work.items.slice()) |work| { + work.behavior.onRun(app, work.param_states); } } diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig index c19a23f..f917136 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -2,7 +2,10 @@ const builtin = @import("builtin"); const coral = @import("coral"); -const ext = @import("ext"); +const ext = @cImport({ + @cInclude("SDL3/SDL.h"); + @cInclude("shaderc/shaderc.h"); +}); const glsl = @import("./gfx/glsl.zig"); @@ -29,7 +32,7 @@ const Context = struct { const injected_source = try glsl.inject(frame_allocator, source, 430, injections); const result = ext.shaderc_compile_into_spv(self.shader_compiler, injected_source.ptr, injected_source.len, switch (kind) { - .fragment => ext.shaderc_glsl_vertex_shader, + .fragment => ext.shaderc_glsl_fragment_shader, .vertex => ext.shaderc_glsl_vertex_shader, }, name, "main", self.spirv_options) orelse { return error.OutOfMemory; @@ -88,7 +91,7 @@ pub const Effect = struct { pipeline: *ext.SDL_GPUGraphicsPipeline, }; -fn compile_shaders(context: ona.Write(Context)) !void { +fn compile_shaders(context: ona.Exclusive(Context), _: ona.Assets(Effect)) !void { const Camera = extern struct { projection: [4]@Vector(4, f32), }; @@ -122,6 +125,7 @@ fn compile_shaders(context: ona.Write(Context)) !void { .code = vertex_spirv.ptr, .code_size = vertex_spirv.len, .format = ext.SDL_GPU_SHADERFORMAT_SPIRV, + .stage = ext.SDL_GPU_SHADERSTAGE_VERTEX, .entrypoint = "main", }); @@ -133,69 +137,28 @@ fn compile_shaders(context: ona.Write(Context)) !void { .code = fragment_spirv.ptr, .code_size = fragment_spirv.len, .format = ext.SDL_GPU_SHADERFORMAT_SPIRV, + .stage = ext.SDL_GPU_SHADERSTAGE_FRAGMENT, .entrypoint = "main", }); defer { ext.SDL_ReleaseGPUShader(context.ptr.gpu_device, effect_fragment); } -} -pub fn poll(exit: ona.Send(ona.App.Exit), hid_events: ona.Send(ona.hid.Event)) !void { - var event: ext.SDL_Event = undefined; - - while (ext.SDL_PollEvent(&event)) { - try hid_events.push(switch (event.type) { - ext.SDL_EVENT_QUIT => { - return exit.push(.success); - }, - - ext.SDL_EVENT_KEY_UP => .{ .key_up = @enumFromInt(event.key.scancode) }, - ext.SDL_EVENT_KEY_DOWN => .{ .key_down = @enumFromInt(event.key.scancode) }, - - ext.SDL_EVENT_MOUSE_BUTTON_UP => .{ - .mouse_up = switch (event.button.button) { - ext.SDL_BUTTON_LEFT => .left, - ext.SDL_BUTTON_RIGHT => .right, - ext.SDL_BUTTON_MIDDLE => .middle, - - else => { - continue; - }, - }, - }, - - ext.SDL_EVENT_MOUSE_BUTTON_DOWN => .{ - .mouse_down = switch (event.button.button) { - ext.SDL_BUTTON_LEFT => .left, - ext.SDL_BUTTON_RIGHT => .right, - ext.SDL_BUTTON_MIDDLE => .middle, - - else => { - continue; - }, - }, - }, - - ext.SDL_EVENT_MOUSE_MOTION => .{ - .mouse_motion = .{ - .relative_position = .{ event.motion.xrel, event.motion.yrel }, - .absolute_position = .{ event.motion.x, event.motion.y }, - }, - }, - - else => { - continue; - }, - }); - } + // _ = try effects.insert(.{ + // .pipeline = ext.SDL_CreateGPUGraphicsPipeline(context.ptr.gpu_device, &.{ + // .vertex_shader = effect_vertex, + // .fragment_shader = effect_fragment, + // .primitive_type = ext.SDL_GPU_PRIMITIVETYPE_TRIANGLELIST, + // }).?, + // }); } pub fn prepare(display: ona.Write(Display)) void { display.ptr.is_hidden = false; } -pub fn render(context: ona.Read(Context)) !void { +pub fn render(context: ona.Exclusive(Context)) !void { const commands = ext.SDL_AcquireGPUCommandBuffer(context.ptr.gpu_device) orelse { return error.SdlFailure; }; @@ -204,16 +167,23 @@ pub fn render(context: ona.Read(Context)) !void { _ = !ext.SDL_CancelGPUCommandBuffer(commands); } - var swapchain_texture: *ext.SDL_Texture = undefined; + var has_swapchain_texture: ?*ext.SDL_GPUTexture = null; + var sawpchain_width, var swapchain_height = [2]u32{ 0, 0 }; - if (!ext.SDL_WaitAndAcquireGPUSwapchainTexture(commands, context.ptr.window, @ptrCast(&swapchain_texture), null, null)) { + if (!ext.SDL_WaitAndAcquireGPUSwapchainTexture( + commands, + context.ptr.window, + &has_swapchain_texture, + &sawpchain_width, + &swapchain_height, + )) { return error.SdlFailure; } - { + if (has_swapchain_texture) |swapchain_texture| { const color_targets = [_]ext.SDL_GPUColorTargetInfo{ .{ - .texture = @ptrCast(swapchain_texture), + .texture = swapchain_texture, .load_op = ext.SDL_GPU_LOADOP_CLEAR, .clear_color = .{ .r = 0, .g = 0.2, .b = 0.4, .a = 1.0 }, }, @@ -288,6 +258,8 @@ pub fn setup(app: *ona.App) !void { .ReleaseFast => ext.shaderc_optimization_level_performance, }); + try ona.registerAsset(app, Effect); + try app.setState(Context{ .frame_arena = .init(coral.heap.allocator), .shader_compiler = shader_compiler, @@ -298,12 +270,11 @@ pub fn setup(app: *ona.App) !void { try app.on(.load, .of(prepare)); try app.on(.load, .of(compile_shaders)); - try app.on(.post_update, .of(poll)); try app.on(.render, .of(render)); try app.on(.finish, .of(synchronize)); } -pub fn synchronize(context: ona.Write(Context), display: ona.Read(Display)) !void { +pub fn synchronize(context: ona.Exclusive(Context), display: ona.Read(Display)) !void { const window_flags = ext.SDL_GetWindowFlags(context.ptr.window); const is_window_hidden = (window_flags & ext.SDL_WINDOW_HIDDEN) != 0; diff --git a/src/ona/hid.zig b/src/ona/hid.zig index 419fa8f..6f2f1fb 100644 --- a/src/ona/hid.zig +++ b/src/ona/hid.zig @@ -1,3 +1,7 @@ +const ext = @cImport({ + @cInclude("SDL3/SDL.h"); +}); + const ona = @import("ona.zig"); pub const Event = union(enum) { @@ -195,3 +199,58 @@ pub const KeyScancode = enum(u32) { right_gui = 0xE7, _, }; + +pub fn poll(exit: ona.Send(ona.App.Exit), hid_events: ona.Send(ona.hid.Event)) !void { + var event: ext.SDL_Event = undefined; + + while (ext.SDL_PollEvent(&event)) { + try hid_events.push(switch (event.type) { + ext.SDL_EVENT_QUIT => { + return exit.push(.success); + }, + + ext.SDL_EVENT_KEY_UP => .{ .key_up = @enumFromInt(event.key.scancode) }, + ext.SDL_EVENT_KEY_DOWN => .{ .key_down = @enumFromInt(event.key.scancode) }, + + ext.SDL_EVENT_MOUSE_BUTTON_UP => .{ + .mouse_up = switch (event.button.button) { + ext.SDL_BUTTON_LEFT => .left, + ext.SDL_BUTTON_RIGHT => .right, + ext.SDL_BUTTON_MIDDLE => .middle, + + else => { + continue; + }, + }, + }, + + ext.SDL_EVENT_MOUSE_BUTTON_DOWN => .{ + .mouse_down = switch (event.button.button) { + ext.SDL_BUTTON_LEFT => .left, + ext.SDL_BUTTON_RIGHT => .right, + ext.SDL_BUTTON_MIDDLE => .middle, + + else => { + continue; + }, + }, + }, + + ext.SDL_EVENT_MOUSE_MOTION => .{ + .mouse_motion = .{ + .relative_position = .{ event.motion.xrel, event.motion.yrel }, + .absolute_position = .{ event.motion.x, event.motion.y }, + }, + }, + + else => { + continue; + }, + }); + } +} + +pub fn setup(app: *ona.App) !void { + try app.on(.post_update, .of(poll)); + try ona.registerChannel(app, Event); +} diff --git a/src/ona/ona.zig b/src/ona/ona.zig index 49ac3ed..ed92fd6 100644 --- a/src/ona/ona.zig +++ b/src/ona/ona.zig @@ -1,7 +1,5 @@ pub const App = @import("./App.zig"); -pub const Assets = @import("./Assets.zig"); - const coral = @import("coral"); pub const gfx = @import("./gfx.zig"); @@ -10,24 +8,133 @@ pub const hid = @import("./hid.zig"); const std = @import("std"); +pub const AssetPath = struct { u32 }; + +pub fn Assets(comptime Asset: type) type { + return struct { + queue: *Queue, + store: *Store, + + pub const Handle = packed struct { + index: u32, + salt: u32, + }; + + const Queue = coral.stack.Sequential(struct { + asset_path: AssetPath, + reserved_handle: Handle, + }); + + const Self = @This(); + + const State = struct { + queue: Queue, + }; + + pub const Store = struct { + released_handles: coral.stack.Sequential(Handle) = .empty, + + assets: coral.stack.Parallel(struct { + asset: Asset, + state: AssetState, + }) = .empty, + + const AssetState = struct { + usage: enum(u32) { vacant, reserved, occupied }, + salt: u32, + }; + + pub fn reserve(self: *Store) std.mem.Allocator.Error!Handle { + if (self.released_handles.pop()) |handle| { + const state = &self.assets.items.slice(.state)[handle.index]; + + std.debug.assert(state.usage == .vacant); + std.debug.assert(state.salt == handle.salt); + + state.usage = .reserved; + + return handle; + } + + const handle = Handle{ + .index = self.assets.items.len, + .salt = 1, + }; + + try self.assets.pushGrow(coral.heap.allocator, .{ + .asset = undefined, + + .state = .{ + .usage = .reserved, + .salt = handle.salt, + }, + }); + + return handle; + } + + pub fn resolve(self: *Store, reserved_handle: Handle, asset: Asset) bool { + const state = self.assets.items.slice(.state)[reserved_handle.index]; + + if (state.usage != .reserved) { + return false; + } + + if (reserved_handle.salt != state.salt) { + return false; + } + + self.assets.items.slice(.asset)[reserved_handle.index] = asset; + + return true; + } + }; + + pub fn bind() State { + return .{ + .queue = .empty, + }; + } + + pub fn init(state: *State, store: *Store) Self { + return .{ + .store = store, + .queue = &state.queue, + }; + } + + pub fn insert(self: Self, asset: Asset) std.mem.Allocator.Error!Handle { + const reserved_handle = try self.store.reserve(); + + std.debug.assert(self.store.resolve(reserved_handle, asset)); + + return reserved_handle; + } + + pub fn load(self: Self, path: AssetPath) std.mem.Allocator.Error!Handle { + const reserved_handle = try self.store.reserve(); + + try self.queue.pushGrow(coral.heap.allocator, .{ + .asset_path = path, + .reserved_handle = reserved_handle, + }); + + return reserved_handle; + } + }; +} + fn Channel(comptime Message: type) type { return struct { - buffers: [2]coral.Stack(Message) = .{ .empty, .empty }, + buffers: [2]coral.stack.Sequential(Message) = .{ .empty, .empty }, swap_index: u1 = 0, ticks: u1 = 0, const Self = @This(); - pub fn bind(app: *App) std.mem.Allocator.Error!void { - if (app.hasState(Self) == null) { - try app.setState(Self{}); - try app.on(.post_update, .of(swap)); - } - } - pub fn deinit(self: *Self) void { for (&self.buffers) |*buffer| { - buffer.deinit(); + buffer.deinit(coral.heap.allocator); } } @@ -42,15 +149,31 @@ fn Channel(comptime Message: type) type { } fn messages(self: Self) []const Message { - return self.buffers[self.swap_index ^ 1].values; + return self.buffers[self.swap_index ^ 1].items.slice(); } fn push(self: *Self, message: Message) std.mem.Allocator.Error!void { - try self.buffers[self.swap_index].pushGrow(message); + try self.buffers[self.swap_index].pushGrow(coral.heap.allocator, message); } }; } +pub fn Exclusive(comptime State: type) type { + return struct { + ptr: *State, + + const Self = @This(); + + pub fn init(state: *State) Self { + return .{ + .ptr = state, + }; + } + + pub const traits = .{.blocking}; + }; +} + pub fn Read(comptime State: type) type { return struct { ptr: *const State, @@ -66,38 +189,36 @@ pub fn Read(comptime State: type) type { } pub fn Receive(comptime Message: type) type { - const TypedChannel = Channel(Message); + const MessageChannel = Channel(Message); return struct { - channel: *const TypedChannel, + has_channel: ?*const MessageChannel, const Self = @This(); - pub const bind = TypedChannel.bind; - - pub fn init(channel: *const TypedChannel) Self { + pub fn init(channel: ?*const MessageChannel) Self { return .{ .channel = channel, }; } pub fn messages(self: Self) []const Message { - return self.channel.messages(); + if (self.has_channel) |channel| { + return channel.messages(); + } } }; } pub fn Send(comptime Message: type) type { - const TypedChannel = Channel(Message); + const MessageChannel = Channel(Message); return struct { - channel: *TypedChannel, + channel: *MessageChannel, const Self = @This(); - pub const bind = TypedChannel.bind; - - pub fn init(channel: *TypedChannel) Self { + pub fn init(channel: *MessageChannel) Self { return .{ .channel = channel, }; @@ -109,16 +230,6 @@ pub fn Send(comptime Message: type) type { }; } -pub const Traits = packed struct { - is_thread_unsafe: bool = false, - - pub fn derived(self: Traits, other: Traits) Traits { - return .{ - .is_thread_unsafe = self.is_thread_unsafe or other.is_thread_unsafe, - }; - } -}; - pub fn Write(comptime State: type) type { return struct { ptr: *State, @@ -135,6 +246,19 @@ pub fn Write(comptime State: type) type { pub const realtime_app = App.Setup.init(run_realtime_loop); +pub fn registerAsset(app: *App, comptime Asset: type) std.mem.Allocator.Error!void { + const AssetStore = Assets(Asset).Store; + + try app.setState(AssetStore{}); +} + +pub fn registerChannel(app: *App, comptime Message: type) std.mem.Allocator.Error!void { + const MessageChannel = Channel(Message); + + try app.on(.post_update, .of(MessageChannel.swap)); + try app.setState(MessageChannel{}); +} + fn run_realtime_loop(app: *App) !void { // ext.SDL_SetLogPriorities(ext.SDL_LOG_PRIORITY_VERBOSE); // ext.SDL_SetLogOutputFunction(sdl_log, null);