From 900c2c0b12a28c94d766aad10d375b6b4d1fd9d4 Mon Sep 17 00:00:00 2001 From: Jeeves Date: Wed, 12 Feb 2025 04:52:27 -0700 Subject: [PATCH] component implementation --- src/main.zig | 330 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 244 insertions(+), 86 deletions(-) diff --git a/src/main.zig b/src/main.zig index 0fcc146..7eb9252 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,58 +1,173 @@ 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 battery1 = Battery{ .value = -0.5 }; - var battery2 = Battery{ .value = 0.5 }; - var battery3 = Battery{ .value = -1.0 }; + 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 or1_inputs = [_]*Signal{ &input, &input, undefined }; - var or1 = Or{ .inputs = &or1_inputs, .arithmetic_mode = true }; - var or2_inputs = [_]*Signal{ &or1.output, &or1.output, undefined }; - var or2 = Or{ .inputs = &or2_inputs, .arithmetic_mode = true }; + 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 or3_inputs = [_]*Signal{ &input, &battery1.output }; - var or3 = Or{ .inputs = &or3_inputs }; - var or4_inputs = [_]*Signal{ &or1.output, &battery1.output }; - var or4 = Or{ .inputs = &or4_inputs }; - var or5_inputs = [_]*Signal{ &or2.output, &battery1.output }; - var or5 = Or{ .inputs = &or5_inputs }; + 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 or6_inputs = [_]*Signal{ &battery2.output, &or3.output }; - var or6 = Or{ .inputs = &or6_inputs, .arithmetic_mode = true }; - var and1_inputs = [_]*Signal{ &battery3.output, &or6.output }; - var and1 = And{ .inputs = &and1_inputs, .arithmetic_mode = true }; - var or7_inputs = [_]*Signal{ &battery2.output, &or4.output }; - var or7 = Or{ .inputs = &or7_inputs, .arithmetic_mode = true }; - var and2_inputs = [_]*Signal{ &battery3.output, &or7.output }; - var and2 = And{ .inputs = &and2_inputs, .arithmetic_mode = true }; - var or8_inputs = [_]*Signal{ &battery2.output, &or5.output }; - var or8 = Or{ .inputs = &or8_inputs, .arithmetic_mode = true }; + 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); - or1_inputs[2] = &and1.output; - or2_inputs[2] = &and2.output; + 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.process(); - battery2.process(); - battery3.process(); - or3.process(); - or6.process(); - and1.process(); - or1.process(); - or4.process(); - or7.process(); - and2.process(); - or2.process(); - or5.process(); - or8.process(); + 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.output, or2.output }); - std.debug.print("{}\n{}\n{}\n\n", .{ or3.output, or4.output, or5.output }); - std.debug.print("{}\n{}\n{}\n{}\n{}\n\n", .{ or6.output, and1.output, or7.output, and2.output, or8.output }); + 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, @@ -75,67 +190,99 @@ pub const Signal = struct { }; pub const Battery = struct { - value: f32, + component: Component, - output: Signal = .{}, + value: f32 = 1.0, - pub fn process(self: *Battery) void { - self.output.digital = @intFromFloat(std.math.sign(self.value)); - self.output.analog = self.value; + 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 { - input: *Signal, - output: Signal = .{}, + component: Component, invert_output: bool = true, - pub fn process(self: *Not) void { + 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) { - self.output.digital = 1 - @as(i2, @intCast(@abs(self.input.digital))); - self.output.analog = 1.0 - @abs(self.input.analog); + 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 { - self.output.digital = self.input.digital; - self.output.analog = self.input.analog; + component.outputs.items[0].digital = component.inputs.items[0].digital; + component.outputs.items[0].analog = component.inputs.items[0].analog; } - self.output.analog = std.math.clamp(self.output.analog, -1.0, 1.0); + component.outputs.items[0].analog = std.math.clamp(component.outputs.items[0].analog, -1.0, 1.0); } }; pub const And = struct { - inputs: []*Signal, - output: Signal = .{}, + 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(self: *And) void { + pub fn process(component: *Component) void { + const self: *And = @fieldParentPtr("component", component); if (self.arithmetic_mode) { - self.output.digital = self.inputs[0].digital; - self.output.analog = self.inputs[0].analog; - for (self.inputs[1..]) |input| { - self.output.digital = 0; // TODO - self.output.analog *= input.analog; + 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 { - self.output.digital = self.inputs[0].digital; - self.output.analog = self.inputs[0].analog; - for (self.inputs[1..]) |input| { - self.output.digital = 0; // TODO - self.output.analog = switch (std.math.order(@abs(self.output.analog), @abs(input.analog))) { - .lt => self.output.analog, - .eq => @min(self.output.analog, input.analog), // TODO what does this *actually* do? + 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, }; } } - self.output.analog = std.math.clamp(self.output.analog, -1.0, 1.0); + 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 }; @@ -153,38 +300,49 @@ test "min" { } pub const Or = struct { - inputs: []*Signal, - output: Signal = .{}, + 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(self: *Or) void { + pub fn process(component: *Component) void { + const self: *Or = @fieldParentPtr("component", component); if (self.arithmetic_mode) { - self.output.digital = self.inputs[0].digital; - self.output.analog = self.inputs[0].analog; - for (self.inputs[1..]) |input| { - self.output.digital = 0; // TODO - self.output.analog += input.analog; + 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 { - self.output.digital = self.inputs[0].digital; - self.output.analog = self.inputs[0].analog; - for (self.inputs[1..]) |input| { - self.output.digital = 0; // TODO - self.output.analog = switch (std.math.order(@abs(self.output.analog), @abs(input.analog))) { + 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(self.output.analog, input.analog), // TODO what does this *actually* do? - .gt => self.output.analog, + .eq => @max(component.outputs.items[0].analog, input.analog), // TODO what does this *actually* do? + .gt => component.outputs.items[0].analog, }; } } - self.output.analog = std.math.clamp(self.output.analog, -1.0, 1.0); + 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 };