mirror of
https://github.com/Stone-Red-Code/StoneRed.LogicSimulator.git
synced 2026-09-04 00:56:23 +02:00
Notify watchers only on output changes and pass previous values to callbacks
This commit is contained in:
@@ -6,6 +6,7 @@ public sealed class CycleCircuitSimulator : SimulatorBase
|
|||||||
{
|
{
|
||||||
private int[] nextInputMasks = [];
|
private int[] nextInputMasks = [];
|
||||||
private Action<int[], int[], int[]> computeOutputs = (_, _, _) => { };
|
private Action<int[], int[], int[]> computeOutputs = (_, _, _) => { };
|
||||||
|
private int[] previousOutputMasks = [];
|
||||||
|
|
||||||
protected override void EnsureStorage()
|
protected override void EnsureStorage()
|
||||||
{
|
{
|
||||||
@@ -13,6 +14,7 @@ public sealed class CycleCircuitSimulator : SimulatorBase
|
|||||||
if (nextInputMasks.Length != gateKinds.Count)
|
if (nextInputMasks.Length != gateKinds.Count)
|
||||||
{
|
{
|
||||||
nextInputMasks = new int[gateKinds.Count];
|
nextInputMasks = new int[gateKinds.Count];
|
||||||
|
previousOutputMasks = new int[gateKinds.Count];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,10 +22,9 @@ public sealed class CycleCircuitSimulator : SimulatorBase
|
|||||||
{
|
{
|
||||||
base.Reset();
|
base.Reset();
|
||||||
Array.Clear(nextInputMasks);
|
Array.Clear(nextInputMasks);
|
||||||
|
Array.Clear(previousOutputMasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnSourceChanged(int gateId) { }
|
|
||||||
|
|
||||||
public override void Step()
|
public override void Step()
|
||||||
{
|
{
|
||||||
EnsureCompiled();
|
EnsureCompiled();
|
||||||
@@ -32,9 +33,13 @@ public sealed class CycleCircuitSimulator : SimulatorBase
|
|||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(outputMasks, previousOutputMasks) = (previousOutputMasks, outputMasks);
|
||||||
computeOutputs(inputMasks, outputMasks, sourceStates);
|
computeOutputs(inputMasks, outputMasks, sourceStates);
|
||||||
PropagateAndSwap();
|
PropagateAndSwap();
|
||||||
NotifyAllWatchers();
|
if (hasAnyWatchers)
|
||||||
|
{
|
||||||
|
NotifyAllWatchers(previousOutputMasks);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PropagateAndSwap()
|
private void PropagateAndSwap()
|
||||||
@@ -103,9 +108,13 @@ public sealed class CycleCircuitSimulator : SimulatorBase
|
|||||||
while (changed && steps < maxSteps)
|
while (changed && steps < maxSteps)
|
||||||
{
|
{
|
||||||
steps++;
|
steps++;
|
||||||
|
(outputMasks, previousOutputMasks) = (previousOutputMasks, outputMasks);
|
||||||
computeOutputs(inputMasks, outputMasks, sourceStates);
|
computeOutputs(inputMasks, outputMasks, sourceStates);
|
||||||
changed = PropagateAndSwapDetectChange();
|
changed = PropagateAndSwapDetectChange();
|
||||||
NotifyAllWatchers();
|
if (hasAnyWatchers)
|
||||||
|
{
|
||||||
|
NotifyAllWatchers(previousOutputMasks);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return !changed;
|
return !changed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ public abstract class SimulatorBase : ICircuitSimulator
|
|||||||
private readonly List<GateWatcherEntry> allWatchers = [];
|
private readonly List<GateWatcherEntry> allWatchers = [];
|
||||||
private Action<int, int>[][] watcherCache = [];
|
private Action<int, int>[][] watcherCache = [];
|
||||||
private int[] gatesWithWatchers = [];
|
private int[] gatesWithWatchers = [];
|
||||||
|
protected bool hasAnyWatchers;
|
||||||
protected int nextWatcherId;
|
protected int nextWatcherId;
|
||||||
|
|
||||||
protected sealed record GateWatcherEntry(int Id, int GateId, Action<int, int> Callback);
|
protected sealed record GateWatcherEntry(int Id, int GateId, Action<int, int> Callback);
|
||||||
@@ -169,6 +170,7 @@ public abstract class SimulatorBase : ICircuitSimulator
|
|||||||
GateWatcherEntry entry = new GateWatcherEntry(id, gateId, callback);
|
GateWatcherEntry entry = new GateWatcherEntry(id, gateId, callback);
|
||||||
allWatchers.Add(entry);
|
allWatchers.Add(entry);
|
||||||
RebuildWatcherCache();
|
RebuildWatcherCache();
|
||||||
|
compiled = false;
|
||||||
return new GateWatcherSubscription(this, id);
|
return new GateWatcherSubscription(this, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +178,7 @@ public abstract class SimulatorBase : ICircuitSimulator
|
|||||||
{
|
{
|
||||||
_ = allWatchers.RemoveAll(w => w.Id == id);
|
_ = allWatchers.RemoveAll(w => w.Id == id);
|
||||||
RebuildWatcherCache();
|
RebuildWatcherCache();
|
||||||
|
compiled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RebuildWatcherCache()
|
private void RebuildWatcherCache()
|
||||||
@@ -193,18 +196,22 @@ public abstract class SimulatorBase : ICircuitSimulator
|
|||||||
}
|
}
|
||||||
|
|
||||||
gatesWithWatchers = [.. activeGates];
|
gatesWithWatchers = [.. activeGates];
|
||||||
|
hasAnyWatchers = allWatchers.Count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void NotifyAllWatchers()
|
protected void NotifyAllWatchers(int[] previousOutputMasks)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < gatesWithWatchers.Length; i++)
|
for (int i = 0; i < gatesWithWatchers.Length; i++)
|
||||||
{
|
{
|
||||||
int gateId = gatesWithWatchers[i];
|
int gateId = gatesWithWatchers[i];
|
||||||
Action<int, int>[] callbacks = watcherCache[gateId];
|
if (outputMasks[gateId] != previousOutputMasks[gateId])
|
||||||
int val = outputMasks[gateId];
|
|
||||||
for (int j = 0; j < callbacks.Length; j++)
|
|
||||||
{
|
{
|
||||||
callbacks[j](gateId, val);
|
Action<int, int>[] callbacks = watcherCache[gateId];
|
||||||
|
int val = outputMasks[gateId];
|
||||||
|
for (int j = 0; j < callbacks.Length; j++)
|
||||||
|
{
|
||||||
|
callbacks[j](gateId, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,7 +282,7 @@ public abstract class SimulatorBase : ICircuitSimulator
|
|||||||
GateKind.Buffer => Expression.Condition(Expression.NotEqual(Expression.And(inMask, Expression.Constant(1)), Expression.Constant(0)), Expression.Constant(1), Expression.Constant(0)),
|
GateKind.Buffer => Expression.Condition(Expression.NotEqual(Expression.And(inMask, Expression.Constant(1)), Expression.Constant(0)), Expression.Constant(1), Expression.Constant(0)),
|
||||||
GateKind.Sink => Expression.Condition(Expression.NotEqual(Expression.And(inMask, Expression.Constant(1)), Expression.Constant(0)), Expression.Constant(1), Expression.Constant(0)),
|
GateKind.Sink => Expression.Condition(Expression.NotEqual(Expression.And(inMask, Expression.Constant(1)), Expression.Constant(0)), Expression.Constant(1), Expression.Constant(0)),
|
||||||
GateKind.Lut => Expression.ArrayIndex(lutDataConst, Expression.Add(Expression.Constant(lutOffsets[gateId]), Expression.And(inMask, Expression.Constant(lutMasks[gateId])))),
|
GateKind.Lut => Expression.ArrayIndex(lutDataConst, Expression.Add(Expression.Constant(lutOffsets[gateId]), Expression.And(inMask, Expression.Constant(lutMasks[gateId])))),
|
||||||
_ => throw new ArgumentOutOfRangeException(),
|
_ => throw new InvalidOperationException($"Unknown gate kind: {gateKinds[gateId]}")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ public abstract class SimulatorTestsBase
|
|||||||
Assert.AreEqual(expected, sim.GetOutput(sink), $"AND2 failed for {i1} & {i2}");
|
Assert.AreEqual(expected, sim.GetOutput(sink), $"AND2 failed for {i1} & {i2}");
|
||||||
}
|
}
|
||||||
|
|
||||||
sim.Reset();
|
|
||||||
Check(false, false, false);
|
Check(false, false, false);
|
||||||
Check(false, true, false);
|
Check(false, true, false);
|
||||||
Check(true, false, false);
|
Check(true, false, false);
|
||||||
@@ -73,7 +72,6 @@ public abstract class SimulatorTestsBase
|
|||||||
Assert.AreEqual(expected, sim.GetOutput(sink), $"OR2 failed for {i1} | {i2}");
|
Assert.AreEqual(expected, sim.GetOutput(sink), $"OR2 failed for {i1} | {i2}");
|
||||||
}
|
}
|
||||||
|
|
||||||
sim.Reset();
|
|
||||||
Check(false, false, false);
|
Check(false, false, false);
|
||||||
Check(false, true, true);
|
Check(false, true, true);
|
||||||
Check(true, false, true);
|
Check(true, false, true);
|
||||||
@@ -90,7 +88,6 @@ public abstract class SimulatorTestsBase
|
|||||||
sim.ConnectGates(s1, not, 0);
|
sim.ConnectGates(s1, not, 0);
|
||||||
sim.ConnectGates(not, sink, 0);
|
sim.ConnectGates(not, sink, 0);
|
||||||
|
|
||||||
sim.Reset();
|
|
||||||
sim.SetSource(s1, false);
|
sim.SetSource(s1, false);
|
||||||
_ = sim.RunUntilStable();
|
_ = sim.RunUntilStable();
|
||||||
Assert.IsTrue(sim.GetOutput(sink), "NOT(0) should be 1");
|
Assert.IsTrue(sim.GetOutput(sink), "NOT(0) should be 1");
|
||||||
@@ -118,7 +115,6 @@ public abstract class SimulatorTestsBase
|
|||||||
sim.ConnectGates(s3, lut, 2);
|
sim.ConnectGates(s3, lut, 2);
|
||||||
sim.ConnectGates(lut, sink, 0);
|
sim.ConnectGates(lut, sink, 0);
|
||||||
|
|
||||||
sim.Reset();
|
|
||||||
_ = sim.RunUntilStable();
|
_ = sim.RunUntilStable();
|
||||||
|
|
||||||
sim.SetSource(s1, true); sim.SetSource(s2, true); sim.SetSource(s3, false);
|
sim.SetSource(s1, true); sim.SetSource(s2, true); sim.SetSource(s3, false);
|
||||||
@@ -149,9 +145,50 @@ public abstract class SimulatorTestsBase
|
|||||||
sim.ConnectGates(src, inst.Inputs[0], 0);
|
sim.ConnectGates(src, inst.Inputs[0], 0);
|
||||||
sim.ConnectGates(inst.Outputs[0], sink, 0);
|
sim.ConnectGates(inst.Outputs[0], sink, 0);
|
||||||
|
|
||||||
sim.Reset();
|
|
||||||
sim.SetSource(src, true);
|
sim.SetSource(src, true);
|
||||||
_ = sim.RunUntilStable();
|
_ = sim.RunUntilStable();
|
||||||
Assert.IsFalse(sim.GetOutput(sink), "Macro NOT(1) should be 0");
|
Assert.IsFalse(sim.GetOutput(sink), "Macro NOT(1) should be 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestWatcherNotification()
|
||||||
|
{
|
||||||
|
ICircuitSimulator sim = CreateSimulator();
|
||||||
|
int source = sim.AddGate(GateKind.Source);
|
||||||
|
int not = sim.AddGate(GateKind.Not);
|
||||||
|
sim.ConnectGates(source, not, 0);
|
||||||
|
|
||||||
|
int watcher1Count = 0;
|
||||||
|
int watcher2Count = 0;
|
||||||
|
|
||||||
|
IDisposable sub1 = sim.WatchGate(not, (id, val) => watcher1Count++);
|
||||||
|
IDisposable sub2 = sim.WatchGate(not, (id, val) => watcher2Count++);
|
||||||
|
|
||||||
|
// Initial state
|
||||||
|
sim.SetSource(source, false);
|
||||||
|
_ = sim.RunUntilStable();
|
||||||
|
Assert.AreEqual(1, watcher1Count, "Watcher 1 should fire on initial change");
|
||||||
|
Assert.AreEqual(1, watcher2Count, "Watcher 2 should fire on initial change");
|
||||||
|
|
||||||
|
// No change
|
||||||
|
sim.SetSource(source, false);
|
||||||
|
_ = sim.RunUntilStable();
|
||||||
|
Assert.AreEqual(1, watcher1Count, "Watcher 1 should not fire if no change");
|
||||||
|
Assert.AreEqual(1, watcher2Count, "Watcher 2 should not fire if no change");
|
||||||
|
|
||||||
|
// Change
|
||||||
|
sim.SetSource(source, true);
|
||||||
|
_ = sim.RunUntilStable();
|
||||||
|
Assert.AreEqual(2, watcher1Count, "Watcher 1 should fire on second change");
|
||||||
|
Assert.AreEqual(2, watcher2Count, "Watcher 2 should fire on second change");
|
||||||
|
|
||||||
|
// Dispose one watcher
|
||||||
|
sub2.Dispose();
|
||||||
|
|
||||||
|
// Change again
|
||||||
|
sim.SetSource(source, false);
|
||||||
|
_ = sim.RunUntilStable();
|
||||||
|
Assert.AreEqual(3, watcher1Count, "Watcher 1 should fire after sub2 is disposed");
|
||||||
|
Assert.AreEqual(2, watcher2Count, "Watcher 2 should NOT fire after being disposed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user