const std = @import("std"); pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); var input = Signal{ .digital = 1, .analog = 0.5 }; var not1 = try Not.init(allocator); defer not1.deinit(allocator); not1.invert_output = false; not1.component.inputs.items[0] = &input; // manually set the input here var battery1 = try Battery.init(allocator); defer battery1.deinit(allocator); var battery2 = try Battery.init(allocator); defer battery2.deinit(allocator); var battery3 = try Battery.init(allocator); defer battery3.deinit(allocator); battery1.value = -0.5; battery2.value = 0.5; battery3.value = -1.0; var or1 = try Or.init(allocator); defer or1.deinit(allocator); var or2 = try Or.init(allocator); defer or2.deinit(allocator); or1.arithmetic_mode = true; or2.arithmetic_mode = true; try or1.component.setNumInputs(allocator, 3); try or2.component.setNumInputs(allocator, 3); var or3 = try Or.init(allocator); defer or3.deinit(allocator); var or4 = try Or.init(allocator); defer or4.deinit(allocator); var or5 = try Or.init(allocator); defer or5.deinit(allocator); var or6 = try Or.init(allocator); defer or6.deinit(allocator); var or7 = try Or.init(allocator); defer or7.deinit(allocator); var or8 = try Or.init(allocator); defer or8.deinit(allocator); var and1 = try And.init(allocator); defer and1.deinit(allocator); var and2 = try And.init(allocator); defer and2.deinit(allocator); or6.arithmetic_mode = true; or7.arithmetic_mode = true; or8.arithmetic_mode = true; and1.arithmetic_mode = true; and2.arithmetic_mode = true; battery1.component.connect(0, &or3.component, 1); battery1.component.connect(0, &or4.component, 1); battery1.component.connect(0, &or5.component, 1); battery2.component.connect(0, &or6.component, 0); battery2.component.connect(0, &or7.component, 0); battery2.component.connect(0, &or8.component, 0); battery3.component.connect(0, &and1.component, 0); battery3.component.connect(0, &and2.component, 0); not1.component.connect(0, &or3.component, 0); not1.component.connect(0, &or1.component, 0); not1.component.connect(0, &or1.component, 1); or1.component.connect(0, &or4.component, 0); or1.component.connect(0, &or2.component, 0); or1.component.connect(0, &or2.component, 1); or2.component.connect(0, &or5.component, 0); or3.component.connect(0, &or6.component, 1); or4.component.connect(0, &or7.component, 1); or5.component.connect(0, &or8.component, 1); or6.component.connect(0, &and1.component, 1); and1.component.connect(0, &or1.component, 2); or7.component.connect(0, &and2.component, 1); and2.component.connect(0, &or2.component, 2); battery1.component.process(); battery2.component.process(); battery3.component.process(); not1.component.process(); or3.component.process(); or6.component.process(); and1.component.process(); or1.component.process(); or4.component.process(); or7.component.process(); and2.component.process(); or2.component.process(); or5.component.process(); or8.component.process(); std.debug.print("Input:\n{}\n\n", .{input}); std.debug.print("{}\n{}\n\n", .{ or1.component.outputs.items[0], or2.component.outputs.items[0] }); std.debug.print("{}\n{}\n{}\n\n", .{ or3.component.outputs.items[0], or4.component.outputs.items[0], or5.component.outputs.items[0] }); std.debug.print("{}\n{}\n{}\n{}\n{}\n\n", .{ or6.component.outputs.items[0], and1.component.outputs.items[0], or7.component.outputs.items[0], and2.component.outputs.items[0], or8.component.outputs.items[0] }); } var null_signal = Signal{}; pub const Component = struct { inputs: Inputs, outputs: Outputs, processFn: *const fn (*Component) void, pub fn init(allocator: std.mem.Allocator, inputs_len: usize, outputs_len: usize, processFn: *const fn (*Component) void) !Component { var inputs = Inputs.empty; errdefer inputs.deinit(allocator); try inputs.resize(allocator, inputs_len); for (0..inputs.items.len) |i| inputs.items[i] = &null_signal; var outputs = Outputs.empty; errdefer outputs.deinit(allocator); try outputs.resize(allocator, outputs_len); for (0..outputs.items.len) |i| outputs.items[i] = Signal{}; return .{ .inputs = inputs, .outputs = outputs, .processFn = processFn, }; } pub fn deinit(self: *Component, allocator: std.mem.Allocator) void { self.inputs.deinit(allocator); self.outputs.deinit(allocator); } pub fn process(self: *Component) void { self.processFn(self); } // TODO allow inserting the new elements at an arbitrary index pub fn setNumInputs(self: *Component, allocator: std.mem.Allocator, new_len: usize) !void { const old_len = self.inputs.items.len; try self.inputs.resize(allocator, new_len); if (new_len > old_len) for (old_len..new_len) |i| { self.inputs.items[i] = &null_signal; }; } // TODO allow inserting the new elements at an arbitrary index // TODO is this allocating the new output Signals on the stack or dumbthing? pub fn setNumOutputs(self: *Component, allocator: std.mem.Allocator, new_len: usize) !void { const old_len = self.outputs.items.len; try self.outputs.resize(allocator, new_len); if (new_len > old_len) for (old_len..new_len) |i| { self.outputs.items[i] = Signal{}; }; } pub fn connect(self: *Component, self_idx: usize, to: *Component, to_idx: usize) void { to.inputs.items[to_idx] = &self.outputs.items[self_idx]; } pub const Input = *Signal; pub const Output = Signal; const Inputs = std.ArrayListUnmanaged(Input); const Outputs = std.ArrayListUnmanaged(Output); }; pub const Signal = struct { digital: i2 = 0, analog: f32 = 0.0, color: u24 = 0, pub fn format( self: Signal, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) !void { _ = .{ fmt, options }; try writer.writeAll("Signal("); if (self.digital < 0) try writer.writeByte('-') else try writer.writeByte('+'); try writer.print("{d} / {d:0>1.4})", .{ @abs(self.digital), self.analog, }); } }; pub const Battery = struct { component: Component, value: f32 = 1.0, pub fn init(allocator: std.mem.Allocator) !Battery { return .{ .component = try Component.init(allocator, 0, 1, &process), }; } pub fn deinit(self: *Battery, allocator: std.mem.Allocator) void { self.component.deinit(allocator); } pub fn process(component: *Component) void { const self: *Battery = @fieldParentPtr("component", component); component.outputs.items[0].digital = @intFromFloat(std.math.sign(self.value)); component.outputs.items[0].analog = self.value; } }; pub const Not = struct { component: Component, invert_output: bool = true, pub fn init(allocator: std.mem.Allocator) !Not { return .{ .component = try Component.init(allocator, 1, 1, &process), }; } pub fn deinit(self: *Not, allocator: std.mem.Allocator) void { self.component.deinit(allocator); } pub fn process(component: *Component) void { const self: *Not = @fieldParentPtr("component", component); if (self.invert_output) { component.outputs.items[0].digital = 1 - @as(i2, @intCast(@abs(component.inputs.items[0].digital))); component.outputs.items[0].analog = 1.0 - @abs(component.inputs.items[0].analog); } else { component.outputs.items[0].digital = component.inputs.items[0].digital; component.outputs.items[0].analog = component.inputs.items[0].analog; } component.outputs.items[0].analog = std.math.clamp(component.outputs.items[0].analog, -1.0, 1.0); } }; pub const And = struct { component: Component, // if false, is in Minimum Input mode // if true, is in Multiply Inputs mode arithmetic_mode: bool = false, pub fn init(allocator: std.mem.Allocator) !And { return .{ .component = try Component.init(allocator, 2, 1, &process), }; } pub fn deinit(self: *And, allocator: std.mem.Allocator) void { self.component.deinit(allocator); } // TODO check implementation pub fn process(component: *Component) void { const self: *And = @fieldParentPtr("component", component); if (self.arithmetic_mode) { component.outputs.items[0].digital = component.inputs.items[0].digital; component.outputs.items[0].analog = component.inputs.items[0].analog; for (component.inputs.items[1..]) |input| { component.outputs.items[0].digital = 0; // TODO component.outputs.items[0].analog *= input.analog; } } else { component.outputs.items[0].digital = component.inputs.items[0].digital; component.outputs.items[0].analog = component.inputs.items[0].analog; for (component.inputs.items[1..]) |input| { component.outputs.items[0].digital = 0; // TODO component.outputs.items[0].analog = switch (std.math.order(@abs(component.outputs.items[0].analog), @abs(input.analog))) { .lt => component.outputs.items[0].analog, .eq => @min(component.outputs.items[0].analog, input.analog), // TODO what does this *actually* do? .gt => input.analog, }; } } component.outputs.items[0].analog = std.math.clamp(component.outputs.items[0].analog, -1.0, 1.0); } }; // TODO update test to use new Component interface test "min" { var a = Signal{ .analog = 0.0 }; var b = Signal{ .analog = 1.0 }; var inputs = [_]*Signal{ &a, &b }; var and1 = And{ .inputs = &inputs }; and1.process(); try std.testing.expectEqual(0.0, and1.output.analog); a.analog = -0.5; b.analog = -0.2; and1.process(); try std.testing.expectEqual(-0.2, and1.output.analog); } pub const Or = struct { component: Component, // if false, is in Maximum Input mode // if true, is in Add Inputs mode arithmetic_mode: bool = false, pub fn init(allocator: std.mem.Allocator) !Or { return .{ .component = try Component.init(allocator, 2, 1, &process), }; } pub fn deinit(self: *Or, allocator: std.mem.Allocator) void { self.component.deinit(allocator); } // TODO check implementation pub fn process(component: *Component) void { const self: *Or = @fieldParentPtr("component", component); if (self.arithmetic_mode) { component.outputs.items[0].digital = component.inputs.items[0].digital; component.outputs.items[0].analog = component.inputs.items[0].analog; for (component.inputs.items[1..]) |input| { component.outputs.items[0].digital = 0; // TODO component.outputs.items[0].analog += input.analog; } } else { component.outputs.items[0].digital = component.inputs.items[0].digital; component.outputs.items[0].analog = component.inputs.items[0].analog; for (component.inputs.items[1..]) |input| { component.outputs.items[0].digital = 0; // TODO component.outputs.items[0].analog = switch (std.math.order(@abs(component.outputs.items[0].analog), @abs(input.analog))) { .lt => input.analog, .eq => @max(component.outputs.items[0].analog, input.analog), // TODO what does this *actually* do? .gt => component.outputs.items[0].analog, }; } } component.outputs.items[0].analog = std.math.clamp(component.outputs.items[0].analog, -1.0, 1.0); } }; // TODO update test to use new Component interface test "max" { var a = Signal{ .analog = 0.0 }; var b = Signal{ .analog = 1.0 }; var inputs = [_]*Signal{ &a, &b }; var or1 = Or{ .inputs = &inputs }; or1.process(); try std.testing.expectEqual(1.0, or1.output.analog); a.analog = -0.5; b.analog = -0.2; or1.process(); try std.testing.expectEqual(-0.5, or1.output.analog); }