core: Allow tracking of individual struct fields for changes

This commit is contained in:
foxnne 2024-11-27 08:15:29 -06:00 committed by Emi Gutekanst
parent d09990700f
commit 0476999dc4
4 changed files with 186 additions and 37 deletions

View file

@ -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 => {},
}

View file

@ -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
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)
/// Texture format of the framebuffer (read-only)
framebuffer_format: gpu.Texture.Format,
// Width of the framebuffer in texels (read-only)
/// Width of the framebuffer in texels (read-only)
framebuffer_width: u32,
// Height of the framebuffer in texels (read-only)
/// Height of the framebuffer in texels (read-only)
framebuffer_height: u32,
// Width of the window in virtual pixels (read-only)
/// Width of the window in virtual pixels (read-only)
width: u32,
// Height of the window in virtual pixels (read-only)
/// Height of the window in virtual pixels (read-only)
height: u32,
/// 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();

View file

@ -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", .{});
}
}

View file

@ -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");