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
|
|
@ -33,7 +33,7 @@ pub fn init(
|
||||||
|
|
||||||
// Color target describes e.g. the pixel format of the window we are rendering to.
|
// Color target describes e.g. the pixel format of the window we are rendering to.
|
||||||
const color_target = gpu.ColorTargetState{
|
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,
|
.blend = &blend,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -69,6 +69,13 @@ pub fn init(
|
||||||
pub fn tick(app: *App, core: *mach.Core) void {
|
pub fn tick(app: *App, core: *mach.Core) void {
|
||||||
while (core.nextEvent()) |event| {
|
while (core.nextEvent()) |event| {
|
||||||
switch (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(),
|
.close => core.exit(),
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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 };
|
pub const mach_systems = .{ .main, .init, .tick, .presentFrame, .deinit };
|
||||||
|
|
||||||
windows: mach.Objects(struct {
|
windows: mach.Objects(
|
||||||
// Window title string
|
struct {
|
||||||
// TODO: document how to set this using a format string
|
/// Window title string
|
||||||
// TODO: allocation/free strategy
|
// TODO: document how to set this using a format string
|
||||||
title: []const u8,
|
// 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,
|
framebuffer_format: gpu.Texture.Format,
|
||||||
|
|
||||||
// Width of the framebuffer in texels (read-only)
|
/// Width of the framebuffer in texels (read-only)
|
||||||
framebuffer_width: u32,
|
framebuffer_width: u32,
|
||||||
|
|
||||||
// Height of the framebuffer in texels (read-only)
|
/// Height of the framebuffer in texels (read-only)
|
||||||
framebuffer_height: u32,
|
framebuffer_height: u32,
|
||||||
|
|
||||||
// Width of the window in virtual pixels (read-only)
|
/// Width of the window in virtual pixels (read-only)
|
||||||
width: u32,
|
width: u32,
|
||||||
|
|
||||||
// Height of the window in virtual pixels (read-only)
|
/// Height of the window in virtual pixels (read-only)
|
||||||
height: u32,
|
height: u32,
|
||||||
|
|
||||||
/// Whether the window is fullscreen (read-only)
|
/// Whether the window is fullscreen (read-only)
|
||||||
fullscreen: bool,
|
fullscreen: bool,
|
||||||
}),
|
},
|
||||||
|
.{
|
||||||
|
.track_fields = true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
/// Callback system invoked per tick (e.g. per-frame)
|
/// Callback system invoked per tick (e.g. per-frame)
|
||||||
on_tick: ?mach.FunctionID = null,
|
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.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
|
// TODO(important): update this information upon framebuffer resize events
|
||||||
var w = core.windows.get(core.main_window).?;
|
// var w = core.windows.get(core.main_window).?;
|
||||||
w.framebuffer_format = core.descriptor.format;
|
// w.framebuffer_format = core.descriptor.format;
|
||||||
w.framebuffer_width = core.descriptor.width;
|
// w.framebuffer_width = core.descriptor.width;
|
||||||
w.framebuffer_height = core.descriptor.height;
|
// w.framebuffer_height = core.descriptor.height;
|
||||||
w.width = core.platform.size.width;
|
// w.width = core.platform.size.width;
|
||||||
w.height = core.platform.size.height;
|
// w.height = core.platform.size.height;
|
||||||
core.windows.set(core.main_window, w);
|
// core.windows.setAll(core.main_window, w);
|
||||||
|
|
||||||
core.frame = .{ .target = 0 };
|
core.frame = .{ .target = 0 };
|
||||||
core.input = .{ .target = 1 };
|
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.run(core.on_tick.?);
|
||||||
core_mod.call(.presentFrame);
|
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;
|
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
|
// TODO(important): update this information in response to resize events rather than
|
||||||
// after frame submission
|
// after frame submission
|
||||||
var win = core.windows.get(core.main_window).?;
|
// var win = core.windows.getAll(core.main_window).?;
|
||||||
win.framebuffer_format = core.descriptor.format;
|
// win.framebuffer_format = core.descriptor.format;
|
||||||
win.framebuffer_width = core.descriptor.width;
|
// win.framebuffer_width = core.descriptor.width;
|
||||||
win.framebuffer_height = core.descriptor.height;
|
// win.framebuffer_height = core.descriptor.height;
|
||||||
win.width = core.platform.size.width;
|
// win.width = core.platform.size.width;
|
||||||
win.height = core.platform.size.height;
|
// win.height = core.platform.size.height;
|
||||||
core.windows.set(core.main_window, win);
|
// core.windows.setAll(core.main_window, win);
|
||||||
|
|
||||||
// Record to frame rate frequency monitor that a frame was finished.
|
// Record to frame rate frequency monitor that a frame was finished.
|
||||||
core.frame.tick();
|
core.frame.tick();
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,8 @@ pub fn setSize(darwin: *Darwin, size: Size) void {
|
||||||
frame.size.height = @floatFromInt(size.height);
|
frame.size.height = @floatFromInt(size.height);
|
||||||
frame.size.width = @floatFromInt(size.width);
|
frame.size.width = @floatFromInt(size.width);
|
||||||
window.setFrame_display_animate(frame, true, true);
|
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,
|
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 {
|
return struct {
|
||||||
internal: struct {
|
internal: struct {
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
|
@ -59,6 +65,10 @@ pub fn Objects(comptime T: type) type {
|
||||||
|
|
||||||
/// Global pointer to object relations graph
|
/// Global pointer to object relations graph
|
||||||
graph: *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;
|
pub const IsMachObjects = void;
|
||||||
|
|
@ -149,6 +159,7 @@ pub fn Objects(comptime T: type) type {
|
||||||
const dead = &objs.internal.dead;
|
const dead = &objs.internal.dead;
|
||||||
const generation = &objs.internal.generation;
|
const generation = &objs.internal.generation;
|
||||||
const recycling_bin = &objs.internal.recycling_bin;
|
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
|
// 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
|
// 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 dead.resize(allocator, data.capacity, true);
|
||||||
try generation.ensureUnusedCapacity(allocator, 1);
|
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;
|
const index = data.len;
|
||||||
data.appendAssumeCapacity(value);
|
data.appendAssumeCapacity(value);
|
||||||
dead.unset(index);
|
dead.unset(index);
|
||||||
generation.appendAssumeCapacity(0);
|
generation.appendAssumeCapacity(0);
|
||||||
|
|
||||||
return @bitCast(PackedID{
|
return @bitCast(PackedID{
|
||||||
.type_id = objs.internal.type_id,
|
.type_id = objs.internal.type_id,
|
||||||
.generation = 0,
|
.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 data = &objs.internal.data;
|
||||||
|
|
||||||
const unpacked = objs.validateAndUnpack(id, "set");
|
const unpacked = objs.validateAndUnpack(id, "setAllRaw");
|
||||||
data.set(unpacked.index, value);
|
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 data = &objs.internal.data;
|
||||||
|
|
||||||
const unpacked = objs.validateAndUnpack(id, "get");
|
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);
|
return data.get(unpacked.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,6 +334,29 @@ pub fn Objects(comptime T: type) type {
|
||||||
return unpacked;
|
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.
|
/// 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 {
|
pub fn is(objs: *const @This(), id: ObjectID) bool {
|
||||||
const unpacked = objs.validateAndUnpack(id, "is");
|
const unpacked = objs.validateAndUnpack(id, "is");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue