core: Allow tracking of individual struct fields for changes
This commit is contained in:
parent
d09990700f
commit
0476999dc4
4 changed files with 186 additions and 37 deletions
86
src/Core.zig
86
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();
|
||||
|
|
|
|||
|
|
@ -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", .{});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
126
src/module.zig
126
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");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue