mirror of
https://github.com/Stone-Red-Code/StoneRed.LogicSimulator.git
synced 2026-09-04 09:06:29 +02:00
Add gate watcher API to observe gate state changes during simulation
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
namespace StoneRed.LogicSimulator.Test;
|
namespace StoneRed.LogicSimulator.Test;
|
||||||
@@ -35,6 +36,10 @@ public sealed class ExprCircuitSimulator
|
|||||||
|
|
||||||
private Action<int[], int[], int[]>? computeOutputs;
|
private Action<int[], int[], int[]>? computeOutputs;
|
||||||
private bool compiled;
|
private bool compiled;
|
||||||
|
private readonly List<GateWatcherEntry> gateWatchers = [];
|
||||||
|
private int nextWatcherId;
|
||||||
|
|
||||||
|
private sealed record GateWatcherEntry(int Id, int GateId, Action<int, int> Callback);
|
||||||
|
|
||||||
public int GateCount => gateKinds.Count;
|
public int GateCount => gateKinds.Count;
|
||||||
|
|
||||||
@@ -211,6 +216,7 @@ public sealed class ExprCircuitSimulator
|
|||||||
EnsureCompiled();
|
EnsureCompiled();
|
||||||
computeOutputs!(inputMasks, outputMasks, sourceStates);
|
computeOutputs!(inputMasks, outputMasks, sourceStates);
|
||||||
Propagate();
|
Propagate();
|
||||||
|
NotifyWatchers();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int RunUntilStable(int maxSteps = 1024)
|
public int RunUntilStable(int maxSteps = 1024)
|
||||||
@@ -528,6 +534,68 @@ public sealed class ExprCircuitSimulator
|
|||||||
return new MacroInstance(name, inputs, outputs);
|
return new MacroInstance(name, inputs, outputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IDisposable WatchGate(int gateId, Action<int, int> 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)
|
public bool TryRunUntilStable(int maxSteps, out int steps)
|
||||||
{
|
{
|
||||||
steps = 0;
|
steps = 0;
|
||||||
@@ -542,7 +610,9 @@ public sealed class ExprCircuitSimulator
|
|||||||
{
|
{
|
||||||
steps++;
|
steps++;
|
||||||
computeOutputs!(inputMasks, outputMasks, sourceStates);
|
computeOutputs!(inputMasks, outputMasks, sourceStates);
|
||||||
if (!PropagateAndSwapDetectChange())
|
bool changed = PropagateAndSwapDetectChange();
|
||||||
|
NotifyWatchers();
|
||||||
|
if (!changed)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,16 @@ internal static class Program
|
|||||||
sim.ConnectGates(a, inv2.Inputs[0], toInputBit: 0);
|
sim.ConnectGates(a, inv2.Inputs[0], toInputBit: 0);
|
||||||
sim.ConnectGates(inv2.Outputs[0], lamp, 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.SetSource(a, value: false);
|
||||||
sim.RunUntilStable();
|
sim.RunUntilStable();
|
||||||
Console.WriteLine($"A=0 => Lamp={sim.GetOutput(lamp)} (expected False)");
|
Console.WriteLine($"A=0 => Lamp={sim.GetOutput(lamp)} (expected False)");
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 18
|
||||||
VisualStudioVersion = 17.6.33815.320
|
VisualStudioVersion = 18.4.11605.240 stable
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StoneRed.LogicSimulator", "StoneRed.LogicSimulator\StoneRed.LogicSimulator.csproj", "{7B259CCA-2947-4DF8-8252-7CF75FE39B8E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StoneRed.LogicSimulator", "StoneRed.LogicSimulator\StoneRed.LogicSimulator.csproj", "{7B259CCA-2947-4DF8-8252-7CF75FE39B8E}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StoneRed.LogicSimulator.Api", "StoneRed.LogicSimulator.Api\StoneRed.LogicSimulator.Api.csproj", "{CD9C3C4D-4AF1-4A31-BDED-34CB91503B4C}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StoneRed.LogicSimulator.Api", "StoneRed.LogicSimulator.Api\StoneRed.LogicSimulator.Api.csproj", "{CD9C3C4D-4AF1-4A31-BDED-34CB91503B4C}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StoneRed.LogicSimulator.Test", "StoneRed.LogicSimulator.Test\StoneRed.LogicSimulator.Test.csproj", "{996500F0-3CD5-912F-03D5-25011C2BD106}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{CD9C3C4D-4AF1-4A31-BDED-34CB91503B4C}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
Reference in New Issue
Block a user