From 0476999dc46ec56ebe83d3c814592a72a46371d1 Mon Sep 17 00:00:00 2001 From: foxnne Date: Wed, 27 Nov 2024 08:15:29 -0600 Subject: [PATCH] core: Allow tracking of individual struct fields for changes --- examples/core-triangle/App.zig | 9 ++- src/Core.zig | 86 +++++++++++++--------- src/core/Darwin.zig | 2 + src/module.zig | 126 +++++++++++++++++++++++++++++++-- 4 files changed, 186 insertions(+), 37 deletions(-) diff --git a/examples/core-triangle/App.zig b/examples/core-triangle/App.zig index 2299f344..d1d10791 100644 --- a/examples/core-triangle/App.zig +++ b/examples/core-triangle/App.zig @@ -33,7 +33,7 @@ pub fn init( // Color target describes e.g. the pixel format of the window we are rendering to. const color_target = gpu.ColorTargetState{ - .format = core.windows.get(core.main_window).?.framebuffer_format, + .format = core.windows.get(core.main_window, .framebuffer_format).?, .blend = &blend, }; @@ -69,6 +69,13 @@ pub fn init( pub fn tick(app: *App, core: *mach.Core) void { while (core.nextEvent()) |event| { switch (event) { + .key_press => |ev| { + switch (ev.key) { + .right => core.windows.set(core.main_window, .width, core.windows.get(core.main_window, .width).? + 10), + .left => core.windows.set(core.main_window, .width, core.windows.get(core.main_window, .width).? - 10), + else => {}, + } + }, .close => core.exit(), else => {}, } diff --git a/src/Core.zig b/src/Core.zig index 0c9ab8b2..464005cc 100644 --- a/src/Core.zig +++ b/src/Core.zig @@ -32,30 +32,35 @@ pub const mach_module = .mach_core; pub const mach_systems = .{ .main, .init, .tick, .presentFrame, .deinit }; -windows: mach.Objects(struct { - // Window title string - // TODO: document how to set this using a format string - // TODO: allocation/free strategy - title: []const u8, +windows: mach.Objects( + struct { + /// Window title string + // TODO: document how to set this using a format string + // TODO: allocation/free strategy + title: []const u8, - // Texture format of the framebuffer (read-only) - framebuffer_format: gpu.Texture.Format, + /// Texture format of the framebuffer (read-only) + framebuffer_format: gpu.Texture.Format, - // Width of the framebuffer in texels (read-only) - framebuffer_width: u32, + /// Width of the framebuffer in texels (read-only) + framebuffer_width: u32, - // Height of the framebuffer in texels (read-only) - framebuffer_height: u32, + /// Height of the framebuffer in texels (read-only) + framebuffer_height: u32, - // Width of the window in virtual pixels (read-only) - width: u32, + /// Width of the window in virtual pixels (read-only) + width: u32, - // Height of the window in virtual pixels (read-only) - height: u32, + /// Height of the window in virtual pixels (read-only) + height: u32, - /// Whether the window is fullscreen (read-only) - fullscreen: bool, -}), + /// Whether the window is fullscreen (read-only) + fullscreen: bool, + }, + .{ + .track_fields = true, + }, +), /// Callback system invoked per tick (e.g. per-frame) on_tick: ?mach.FunctionID = null, @@ -218,14 +223,17 @@ pub fn init(core: *Core) !void { }; core.swap_chain = core.device.createSwapChain(core.surface, &core.descriptor); + core.windows.setRaw(core.main_window, .framebuffer_format, core.descriptor.format); + core.windows.setRaw(core.main_window, .framebuffer_width, core.descriptor.width); + core.windows.setRaw(core.main_window, .framebuffer_height, core.descriptor.height); // TODO(important): update this information upon framebuffer resize events - var w = core.windows.get(core.main_window).?; - w.framebuffer_format = core.descriptor.format; - w.framebuffer_width = core.descriptor.width; - w.framebuffer_height = core.descriptor.height; - w.width = core.platform.size.width; - w.height = core.platform.size.height; - core.windows.set(core.main_window, w); + // var w = core.windows.get(core.main_window).?; + // w.framebuffer_format = core.descriptor.format; + // w.framebuffer_width = core.descriptor.width; + // w.framebuffer_height = core.descriptor.height; + // w.width = core.platform.size.width; + // w.height = core.platform.size.height; + // core.windows.setAll(core.main_window, w); core.frame = .{ .target = 0 }; core.input = .{ .target = 1 }; @@ -281,6 +289,20 @@ fn platform_update_callback(core: *Core, core_mod: mach.Mod(Core)) !bool { core_mod.run(core.on_tick.?); core_mod.call(.presentFrame); + if (core.windows.updated(core.main_window, .width) or core.windows.updated(core.main_window, .height)) { + const window = core.windows.getAll(core.main_window); + + if (window) |main_window| { + core.platform.setSize(.{ + .width = main_window.width, + .height = main_window.height, + }); + } + + core.windows.setUpdated(core.main_window, .width, false); + core.windows.setUpdated(core.main_window, .height, false); + } + return core.state != .exited; } @@ -607,13 +629,13 @@ pub fn presentFrame(core: *Core, core_mod: mach.Mod(Core)) !void { // TODO(important): update this information in response to resize events rather than // after frame submission - var win = core.windows.get(core.main_window).?; - win.framebuffer_format = core.descriptor.format; - win.framebuffer_width = core.descriptor.width; - win.framebuffer_height = core.descriptor.height; - win.width = core.platform.size.width; - win.height = core.platform.size.height; - core.windows.set(core.main_window, win); + // var win = core.windows.getAll(core.main_window).?; + // win.framebuffer_format = core.descriptor.format; + // win.framebuffer_width = core.descriptor.width; + // win.framebuffer_height = core.descriptor.height; + // win.width = core.platform.size.width; + // win.height = core.platform.size.height; + // core.windows.setAll(core.main_window, win); // Record to frame rate frequency monitor that a frame was finished. core.frame.tick(); diff --git a/src/core/Darwin.zig b/src/core/Darwin.zig index 71d78595..d403b674 100644 --- a/src/core/Darwin.zig +++ b/src/core/Darwin.zig @@ -206,6 +206,8 @@ pub fn setSize(darwin: *Darwin, size: Size) void { frame.size.height = @floatFromInt(size.height); frame.size.width = @floatFromInt(size.width); window.setFrame_display_animate(frame, true, true); + + std.log.debug("setSize successfully called", .{}); } } diff --git a/src/module.zig b/src/module.zig index a5d90e06..c9e21080 100644 --- a/src/module.zig +++ b/src/module.zig @@ -25,7 +25,13 @@ const PackedObjectTypeID = packed struct(u16) { object_name_id: u6, }; -pub fn Objects(comptime T: type) type { +pub const ObjectsOptions = struct { + /// If this option is true, the internal field `update` will + /// contain a list of bitsets representing each object field + track_fields: bool = false, +}; + +pub fn Objects(comptime T: type, options: ObjectsOptions) type { return struct { internal: struct { allocator: std.mem.Allocator, @@ -59,6 +65,10 @@ pub fn Objects(comptime T: type) type { /// Global pointer to object relations graph graph: *Graph, + + /// This is mach's way of determining which fields are updated and + /// need to trigger some other logic + updated: std.ArrayListUnmanaged(std.bit_set.DynamicBitSetUnmanaged) = .{}, }, pub const IsMachObjects = void; @@ -149,6 +159,7 @@ pub fn Objects(comptime T: type) type { const dead = &objs.internal.dead; const generation = &objs.internal.generation; const recycling_bin = &objs.internal.recycling_bin; + const _updated = &objs.internal.updated; // The recycling bin should always be big enough, but we check at this point if 10% of // all objects have been thrown on the floor. If they have, we find them and grow the @@ -176,10 +187,16 @@ pub fn Objects(comptime T: type) type { try dead.resize(allocator, data.capacity, true); try generation.ensureUnusedCapacity(allocator, 1); + if (options.track_fields) { + try _updated.ensureUnusedCapacity(allocator, 1); + _updated.appendAssumeCapacity(try std.bit_set.DynamicBitSetUnmanaged.initEmpty(allocator, @typeInfo(T).@"struct".fields.len)); + } + const index = data.len; data.appendAssumeCapacity(value); dead.unset(index); generation.appendAssumeCapacity(0); + return @bitCast(PackedID{ .type_id = objs.internal.type_id, .generation = 0, @@ -187,17 +204,95 @@ pub fn Objects(comptime T: type) type { }); } - pub fn set(objs: *@This(), id: ObjectID, value: T) void { + /// This function will set all fields of an object. + /// + /// Note that this function has no tracking, and will not trigger + /// any internal mach functions. + /// + /// Example: core.windows.setAllRaw(id, value) will NOT trigger the Platform + /// to update the window's size, etc. + pub fn setAllRaw(objs: *@This(), id: ObjectID, value: T) void { const data = &objs.internal.data; - const unpacked = objs.validateAndUnpack(id, "set"); + const unpacked = objs.validateAndUnpack(id, "setAllRaw"); data.set(unpacked.index, value); } - pub fn get(objs: *@This(), id: ObjectID) ?T { + /// This function will set all fields of an object. + /// + /// Note that this function also sets a bit set value + /// for each field, informing mach that the field has + /// been updated and can be passed to internals that need it. + /// + /// Example: core.windows.setAll(id, value) can trigger the Platform + /// to update the window's size, etc. + pub fn setAll(objs: *@This(), id: ObjectID, value: T) void { + const data = &objs.internal.data; + + const unpacked = objs.validateAndUnpack(id, "setAll"); + data.set(unpacked.index, value); + + if (options.track_fields) + objs.internal.updated.items[unpacked.index].setAll(); + } + + /// This function will set a single field of an object. + /// + /// Note that this function has no tracking, and will not trigger + /// any internal mach functions. + /// + /// Example: core.windows.setRaw(id, .width, value) will NOT trigger the Platform + /// to update the window's size. + pub fn setRaw(objs: *@This(), id: ObjectID, comptime field_name: anytype, value: anytype) void { + if (@typeInfo(@TypeOf(field_name)) != .enum_literal) @compileError("mach: invalid field name, expected `.field` enum literal, found: " ++ @typeName(@TypeOf(field_name))); + + const data = &objs.internal.data; + const unpacked = objs.validateAndUnpack(id, "setRaw"); + + var current = data.get(unpacked.index); + @field(current, @tagName(field_name)) = value; + + data.set(unpacked.index, current); + } + + /// This function will set a single field of an object. + /// + /// Note that this function also sets a bit set value + /// for each object field, informing mach that the field has + /// been updated and can be passed to internals that need it. + /// + /// Example: core.windows.set(id, .width, value) will trigger the Platform + /// to update the window's size. + pub fn set(objs: *@This(), id: ObjectID, comptime field_name: anytype, value: anytype) void { + if (@typeInfo(@TypeOf(field_name)) != .enum_literal) @compileError("mach: invalid field name, expected `.field` enum literal, found: " ++ @typeName(@TypeOf(field_name))); + + const data = &objs.internal.data; + const unpacked = objs.validateAndUnpack(id, "set"); + + var current = data.get(unpacked.index); + @field(current, @tagName(field_name)) = value; + + data.set(unpacked.index, current); + + if (options.track_fields) + if (std.meta.fieldIndex(T, @tagName(field_name))) |field_index| + objs.internal.updated.items[unpacked.index].set(field_index); + } + + /// Get a single field. + pub fn get(objs: *@This(), id: ObjectID, field_name: anytype) ?std.meta.FieldType(T, field_name) { const data = &objs.internal.data; const unpacked = objs.validateAndUnpack(id, "get"); + const d = data.get(unpacked.index); + return @field(d, @tagName(field_name)); + } + + /// Get all fields. + pub fn getAll(objs: *@This(), id: ObjectID) ?T { + const data = &objs.internal.data; + + const unpacked = objs.validateAndUnpack(id, "getAll"); return data.get(unpacked.index); } @@ -239,6 +334,29 @@ pub fn Objects(comptime T: type) type { return unpacked; } + /// Returns true if the field has an `updated` bit set in internal. + /// + /// Internal functions may set this bit back to false. + pub fn updated(objs: *@This(), id: ObjectID, field_name: anytype) bool { + if (options.track_fields) { + const unpacked = objs.validateAndUnpack(id, "updated"); + if (std.meta.fieldIndex(T, @tagName(field_name))) |field_index| + return objs.internal.updated.items[unpacked.index].isSet(field_index); + } + return false; + } + + /// Returns true if the field has an `updated` bit set in internal. + /// + /// Internal functions may set this bit back to false. + pub fn setUpdated(objs: *@This(), id: ObjectID, field_name: anytype, value: bool) void { + if (options.track_fields) { + const unpacked = objs.validateAndUnpack(id, "setUpdated"); + if (std.meta.fieldIndex(T, @tagName(field_name))) |field_index| + return objs.internal.updated.items[unpacked.index].setValue(field_index, value); + } + } + /// Tells if the given object (which must be alive and valid) is from this pool of objects. pub fn is(objs: *const @This(), id: ObjectID) bool { const unpacked = objs.validateAndUnpack(id, "is");