From b1b71a2392b1aa355d69d3c5bef284b580cf0655 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 25 Mar 2026 13:14:06 +0100 Subject: [PATCH] Add gate watcher API to observe gate state changes during simulation --- .../ExprCircuitSimulator.cs | 72 ++++++++++++++++++- StoneRed.LogicSimulator.Test/Program.cs | 10 +++ StoneRed.LogicSimulator.sln | 10 ++- 3 files changed, 89 insertions(+), 3 deletions(-) diff --git a/StoneRed.LogicSimulator.Test/ExprCircuitSimulator.cs b/StoneRed.LogicSimulator.Test/ExprCircuitSimulator.cs index 10e6af3..ccfbd7b 100644 --- a/StoneRed.LogicSimulator.Test/ExprCircuitSimulator.cs +++ b/StoneRed.LogicSimulator.Test/ExprCircuitSimulator.cs @@ -1,3 +1,4 @@ +using System; using System.Linq.Expressions; namespace StoneRed.LogicSimulator.Test; @@ -35,6 +36,10 @@ public sealed class ExprCircuitSimulator private Action? computeOutputs; private bool compiled; + private readonly List gateWatchers = []; + private int nextWatcherId; + + private sealed record GateWatcherEntry(int Id, int GateId, Action Callback); public int GateCount => gateKinds.Count; @@ -211,6 +216,7 @@ public sealed class ExprCircuitSimulator EnsureCompiled(); computeOutputs!(inputMasks, outputMasks, sourceStates); Propagate(); + NotifyWatchers(); } public int RunUntilStable(int maxSteps = 1024) @@ -528,6 +534,68 @@ public sealed class ExprCircuitSimulator return new MacroInstance(name, inputs, outputs); } + public IDisposable WatchGate(int gateId, Action callback) + { + if (callback is null) + { + throw new ArgumentNullException(nameof(callback)); + } + + if ((uint)gateId >= (uint)gateKinds.Count) + { + throw new ArgumentOutOfRangeException(nameof(gateId)); + } + + int id = nextWatcherId++; + var entry = new GateWatcherEntry(id, gateId, callback); + gateWatchers.Add(entry); + return new GateWatcherSubscription(this, id); + } + + private void RemoveWatcher(int id) + { + gateWatchers.RemoveAll(w => w.Id == id); + } + + private void NotifyWatchers() + { + if (gateWatchers.Count == 0) + { + return; + } + + GateWatcherEntry[] snapshot = gateWatchers.ToArray(); + for (int i = 0; i < snapshot.Length; i++) + { + GateWatcherEntry entry = snapshot[i]; + entry.Callback(entry.GateId, outputMasks[entry.GateId]); + } + } + + private sealed class GateWatcherSubscription : IDisposable + { + private readonly ExprCircuitSimulator simulator; + private readonly int id; + private bool disposed; + + public GateWatcherSubscription(ExprCircuitSimulator simulator, int id) + { + this.simulator = simulator; + this.id = id; + } + + public void Dispose() + { + if (disposed) + { + return; + } + + disposed = true; + simulator.RemoveWatcher(id); + } + } + public bool TryRunUntilStable(int maxSteps, out int steps) { steps = 0; @@ -542,7 +610,9 @@ public sealed class ExprCircuitSimulator { steps++; computeOutputs!(inputMasks, outputMasks, sourceStates); - if (!PropagateAndSwapDetectChange()) + bool changed = PropagateAndSwapDetectChange(); + NotifyWatchers(); + if (!changed) { return true; } diff --git a/StoneRed.LogicSimulator.Test/Program.cs b/StoneRed.LogicSimulator.Test/Program.cs index d9f4979..f4e0f5b 100644 --- a/StoneRed.LogicSimulator.Test/Program.cs +++ b/StoneRed.LogicSimulator.Test/Program.cs @@ -35,6 +35,16 @@ internal static class Program sim.ConnectGates(a, inv2.Inputs[0], toInputBit: 0); sim.ConnectGates(inv2.Outputs[0], lamp, toInputBit: 0); + int httpSink = sim.AddGate(GateKind.Sink); + sim.ConnectGates(inv2.Outputs[0], httpSink, toInputBit: 0); + using var httpWatcher = sim.WatchGate(httpSink, (gateId, mask) => + { + if ((mask & 1) != 0) + { + Console.WriteLine($"HTTP gate {gateId} fired at mask={mask:X}"); + } + }); + sim.SetSource(a, value: false); sim.RunUntilStable(); Console.WriteLine($"A=0 => Lamp={sim.GetOutput(lamp)} (expected False)"); diff --git a/StoneRed.LogicSimulator.sln b/StoneRed.LogicSimulator.sln index 9330d31..3253862 100644 --- a/StoneRed.LogicSimulator.sln +++ b/StoneRed.LogicSimulator.sln @@ -1,12 +1,14 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.6.33815.320 +# Visual Studio Version 18 +VisualStudioVersion = 18.4.11605.240 stable MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StoneRed.LogicSimulator", "StoneRed.LogicSimulator\StoneRed.LogicSimulator.csproj", "{7B259CCA-2947-4DF8-8252-7CF75FE39B8E}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StoneRed.LogicSimulator.Api", "StoneRed.LogicSimulator.Api\StoneRed.LogicSimulator.Api.csproj", "{CD9C3C4D-4AF1-4A31-BDED-34CB91503B4C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StoneRed.LogicSimulator.Test", "StoneRed.LogicSimulator.Test\StoneRed.LogicSimulator.Test.csproj", "{996500F0-3CD5-912F-03D5-25011C2BD106}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {CD9C3C4D-4AF1-4A31-BDED-34CB91503B4C}.Debug|Any CPU.Build.0 = Debug|Any CPU {CD9C3C4D-4AF1-4A31-BDED-34CB91503B4C}.Release|Any CPU.ActiveCfg = Release|Any CPU {CD9C3C4D-4AF1-4A31-BDED-34CB91503B4C}.Release|Any CPU.Build.0 = Release|Any CPU + {996500F0-3CD5-912F-03D5-25011C2BD106}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {996500F0-3CD5-912F-03D5-25011C2BD106}.Debug|Any CPU.Build.0 = Debug|Any CPU + {996500F0-3CD5-912F-03D5-25011C2BD106}.Release|Any CPU.ActiveCfg = Release|Any CPU + {996500F0-3CD5-912F-03D5-25011C2BD106}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE