I’m working on an embedded app which will have fairly complex UI logic and I’m considering using Zig. When I write these kind of apps I like to separate my logic into an “input → state” pass followed by a “state → output” pass. If you’re familiar with React / Redux think of the input logic as your store that handles events & updates the state, and the output logic as your components that take the state and render it to a canonical output format.
In JS (or even C++ with a bit of hacking) I have a pretty good idea of how to achieve these kind of patterns, but I’m struggling a bit to get Zig to work at the level of abstraction I’m used to. Here’s what I’ve got so far:
const std = @import("std");
// Definition of state atom
fn Field(comptime T: type) type {
return struct {
const Self = @This();
value: T,
prev_value: T,
dirty: bool = false,
fn init(v: T) Self {
return Self { .value = v, .prev_value = v };
}
fn set(self: *Self, v: T) void {
self.dirty = true;
self.prev_value = self.value;
self.value = v;
}
fn use(self: *Self) struct {T, T} {
const pv = self.prev_value;
self.prev_value = self.value;
self.dirty = false;
return .{ self.value, pv };
}
};
}
const State = struct {
num: Field(u8) = Field(u8).init(0),
str: Field([]const u8) = Field([]const u8).init("foobar"),
// ...lots more
};
var state = State {};
// Event handler (input)
const EventCode = enum { A, B };
fn handle(event_code: EventCode) void {
switch (event_code) {
.A => {
state.num.set(state.num.value + 1);
},
.B => {
state.str.set("baz");
}
}
}
// Renderer (output)
fn render() void {
if (state.num.dirty) {
const v = state.num.use();
std.debug.print("num changed from {d} to {d}\n", .{v[1], v[0]});
}
if (state.str.dirty) {
const v = state.str.use();
std.debug.print("str changed from {s} to {s}\n", .{v[1], v[0]});
}
}
// Use them like this
pub fn main() void {
handle(EventCode.A);
render();
handle(EventCode.B);
render();
}
This is obviously a trivial example, please try to imagine a lot more complexity. Anyway, the general point here is that the state struct centralizes all your mutable state for the app, and sets a flag when a given field has been changed. The input phase updates the state, and the render phase renders whatever has changed. The previous values are needed because graphics on embedded is v expensive, so you often use optimizations like “if the first digit of the number hasn’t changed only render the second digit”, that sort of thing.
Am I barking up the wrong tree here? Is there a more idiomatic way to achieve this sort of pattern in Zig? Am I overthinking things?