mirror of
https://github.com/Stone-Red-Code/StoneRed.LogicSimulator.git
synced 2026-09-06 16:05:59 +02:00
Switch gate watchers to notify on input mask changes, add DivideByNCounter
This commit is contained in:
@@ -17,6 +17,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 = [];
|
private int[] previousOutputMasks = [];
|
||||||
|
private int[] previousInputMasks = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ensures internal storage arrays are properly sized for the current gate count.
|
/// Ensures internal storage arrays are properly sized for the current gate count.
|
||||||
@@ -28,6 +29,7 @@ public sealed class CycleCircuitSimulator : SimulatorBase
|
|||||||
{
|
{
|
||||||
nextInputMasks = new int[gateKinds.Count];
|
nextInputMasks = new int[gateKinds.Count];
|
||||||
previousOutputMasks = new int[gateKinds.Count];
|
previousOutputMasks = new int[gateKinds.Count];
|
||||||
|
previousInputMasks = new int[gateKinds.Count];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +41,7 @@ public sealed class CycleCircuitSimulator : SimulatorBase
|
|||||||
base.Reset();
|
base.Reset();
|
||||||
Array.Clear(nextInputMasks);
|
Array.Clear(nextInputMasks);
|
||||||
Array.Clear(previousOutputMasks);
|
Array.Clear(previousOutputMasks);
|
||||||
|
Array.Clear(previousInputMasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -55,10 +58,14 @@ public sealed class CycleCircuitSimulator : SimulatorBase
|
|||||||
|
|
||||||
(outputMasks, previousOutputMasks) = (previousOutputMasks, outputMasks);
|
(outputMasks, previousOutputMasks) = (previousOutputMasks, outputMasks);
|
||||||
computeOutputs(inputMasks, outputMasks, sourceStates);
|
computeOutputs(inputMasks, outputMasks, sourceStates);
|
||||||
|
if (hasAnyWatchers)
|
||||||
|
{
|
||||||
|
Array.Copy(inputMasks, previousInputMasks, inputMasks.Length);
|
||||||
|
}
|
||||||
PropagateAndSwap();
|
PropagateAndSwap();
|
||||||
if (hasAnyWatchers)
|
if (hasAnyWatchers)
|
||||||
{
|
{
|
||||||
NotifyAllWatchers(previousOutputMasks);
|
NotifyAllWatchers(previousInputMasks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,10 +143,14 @@ public sealed class CycleCircuitSimulator : SimulatorBase
|
|||||||
steps++;
|
steps++;
|
||||||
(outputMasks, previousOutputMasks) = (previousOutputMasks, outputMasks);
|
(outputMasks, previousOutputMasks) = (previousOutputMasks, outputMasks);
|
||||||
computeOutputs(inputMasks, outputMasks, sourceStates);
|
computeOutputs(inputMasks, outputMasks, sourceStates);
|
||||||
|
if (hasAnyWatchers)
|
||||||
|
{
|
||||||
|
Array.Copy(inputMasks, previousInputMasks, inputMasks.Length);
|
||||||
|
}
|
||||||
changed = PropagateAndSwapDetectChange();
|
changed = PropagateAndSwapDetectChange();
|
||||||
if (hasAnyWatchers)
|
if (hasAnyWatchers)
|
||||||
{
|
{
|
||||||
NotifyAllWatchers(previousOutputMasks);
|
NotifyAllWatchers(previousInputMasks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return !changed;
|
return !changed;
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ public sealed class EventCircuitSimulator : SimulatorBase
|
|||||||
if (outputMasks[gateId] != oldOutput)
|
if (outputMasks[gateId] != oldOutput)
|
||||||
{
|
{
|
||||||
Propagate(gateId);
|
Propagate(gateId);
|
||||||
NotifyGateWatchers(gateId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,6 +102,10 @@ public sealed class EventCircuitSimulator : SimulatorBase
|
|||||||
if (oldBit != outVal)
|
if (oldBit != outVal)
|
||||||
{
|
{
|
||||||
inputMasks[toGate] ^= 1 << bit;
|
inputMasks[toGate] ^= 1 << bit;
|
||||||
|
if (hasAnyWatchers)
|
||||||
|
{
|
||||||
|
NotifyGateWatchers(toGate);
|
||||||
|
}
|
||||||
Enqueue(toGate);
|
Enqueue(toGate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,11 +114,11 @@ public interface ICircuitSimulator
|
|||||||
bool TryRunUntilStable(int maxSteps, out int steps);
|
bool TryRunUntilStable(int maxSteps, out int steps);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Subscribes to output changes on a specific gate.
|
/// Subscribes to input mask changes on a specific gate.
|
||||||
/// The callback is invoked whenever the gate's output value changes.
|
/// The callback is invoked whenever the gate's input mask changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="gateId">The gate ID to watch.</param>
|
/// <param name="gateId">The gate ID to watch.</param>
|
||||||
/// <param name="callback">Action to invoke on change. Parameters are (gateId, newOutputMask).</param>
|
/// <param name="callback">Action to invoke on change. Parameters are (gateId, newInputMask).</param>
|
||||||
/// <returns>An <see cref="IDisposable"/> that unsubscribes the watcher when disposed.</returns>
|
/// <returns>An <see cref="IDisposable"/> that unsubscribes the watcher when disposed.</returns>
|
||||||
/// <exception cref="ArgumentNullException">Thrown when callback is null.</exception>
|
/// <exception cref="ArgumentNullException">Thrown when callback is null.</exception>
|
||||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when gateId is invalid.</exception>
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when gateId is invalid.</exception>
|
||||||
|
|||||||
@@ -246,18 +246,18 @@ public abstract class SimulatorBase : ICircuitSimulator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies all registered watchers of gates that changed between the previous and current output states.
|
/// Notifies all registered watchers of gates that changed between the previous and current input states.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="previousOutputMasks">The output states from before the change.</param>
|
/// <param name="previousInputMasks">The input states from before the change.</param>
|
||||||
protected void NotifyAllWatchers(int[] previousOutputMasks)
|
protected void NotifyAllWatchers(int[] previousInputMasks)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < gatesWithWatchers.Length; i++)
|
for (int i = 0; i < gatesWithWatchers.Length; i++)
|
||||||
{
|
{
|
||||||
int gateId = gatesWithWatchers[i];
|
int gateId = gatesWithWatchers[i];
|
||||||
if (outputMasks[gateId] != previousOutputMasks[gateId])
|
if (inputMasks[gateId] != previousInputMasks[gateId])
|
||||||
{
|
{
|
||||||
Action<int, int>[] callbacks = watcherCache[gateId];
|
Action<int, int>[] callbacks = watcherCache[gateId];
|
||||||
int val = outputMasks[gateId];
|
int val = inputMasks[gateId];
|
||||||
for (int j = 0; j < callbacks.Length; j++)
|
for (int j = 0; j < callbacks.Length; j++)
|
||||||
{
|
{
|
||||||
callbacks[j](gateId, val);
|
callbacks[j](gateId, val);
|
||||||
@@ -267,7 +267,7 @@ public abstract class SimulatorBase : ICircuitSimulator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies watchers of a specific gate that its output has changed.
|
/// Notifies watchers of a specific gate that its input has changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="gateId">The ID of the gate that changed.</param>
|
/// <param name="gateId">The ID of the gate that changed.</param>
|
||||||
protected void NotifyGateWatchers(int gateId)
|
protected void NotifyGateWatchers(int gateId)
|
||||||
@@ -283,7 +283,7 @@ public abstract class SimulatorBase : ICircuitSimulator
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int val = outputMasks[gateId];
|
int val = inputMasks[gateId];
|
||||||
for (int i = 0; i < callbacks.Length; i++)
|
for (int i = 0; i < callbacks.Length; i++)
|
||||||
{
|
{
|
||||||
callbacks[i](gateId, val);
|
callbacks[i](gateId, val);
|
||||||
|
|||||||
@@ -167,20 +167,20 @@ public abstract class SimulatorTestsBase
|
|||||||
// Initial state
|
// Initial state
|
||||||
sim.SetSource(source, false);
|
sim.SetSource(source, false);
|
||||||
_ = sim.RunUntilStable();
|
_ = sim.RunUntilStable();
|
||||||
Assert.AreEqual(1, watcher1Count, "Watcher 1 should fire on initial change");
|
Assert.AreEqual(0, watcher1Count, "Watcher 1 should not fire when input mask does not change");
|
||||||
Assert.AreEqual(1, watcher2Count, "Watcher 2 should fire on initial change");
|
Assert.AreEqual(0, watcher2Count, "Watcher 2 should not fire when input mask does not change");
|
||||||
|
|
||||||
// No change
|
// No change
|
||||||
sim.SetSource(source, false);
|
sim.SetSource(source, false);
|
||||||
_ = sim.RunUntilStable();
|
_ = sim.RunUntilStable();
|
||||||
Assert.AreEqual(1, watcher1Count, "Watcher 1 should not fire if no change");
|
Assert.AreEqual(0, watcher1Count, "Watcher 1 should not fire if no change");
|
||||||
Assert.AreEqual(1, watcher2Count, "Watcher 2 should not fire if no change");
|
Assert.AreEqual(0, watcher2Count, "Watcher 2 should not fire if no change");
|
||||||
|
|
||||||
// Change
|
// Change
|
||||||
sim.SetSource(source, true);
|
sim.SetSource(source, true);
|
||||||
_ = sim.RunUntilStable();
|
_ = sim.RunUntilStable();
|
||||||
Assert.AreEqual(2, watcher1Count, "Watcher 1 should fire on second change");
|
Assert.AreEqual(1, watcher1Count, "Watcher 1 should fire on input change");
|
||||||
Assert.AreEqual(2, watcher2Count, "Watcher 2 should fire on second change");
|
Assert.AreEqual(1, watcher2Count, "Watcher 2 should fire on input change");
|
||||||
|
|
||||||
// Dispose one watcher
|
// Dispose one watcher
|
||||||
sub2.Dispose();
|
sub2.Dispose();
|
||||||
@@ -188,7 +188,7 @@ public abstract class SimulatorTestsBase
|
|||||||
// Change again
|
// Change again
|
||||||
sim.SetSource(source, false);
|
sim.SetSource(source, false);
|
||||||
_ = sim.RunUntilStable();
|
_ = sim.RunUntilStable();
|
||||||
Assert.AreEqual(3, watcher1Count, "Watcher 1 should fire after sub2 is disposed");
|
Assert.AreEqual(2, watcher1Count, "Watcher 1 should fire after sub2 is disposed");
|
||||||
Assert.AreEqual(2, watcher2Count, "Watcher 2 should NOT fire after being disposed");
|
Assert.AreEqual(1, watcher2Count, "Watcher 2 should NOT fire after being disposed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
using MonoGame.Extended.Input;
|
||||||
|
|
||||||
|
using StoneRed.LogicSimulator.Api;
|
||||||
|
using StoneRed.LogicSimulator.Api.Attributes;
|
||||||
|
using StoneRed.LogicSimulator.Api.Interfaces;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
|
||||||
|
|
||||||
|
[LogicGateName("DivideByNCounter")]
|
||||||
|
[LogicGateDescription("A clock is a circuit that oscillates between a high and a low state.")]
|
||||||
|
internal class DivideByNCounter : LogicGate, IInteractable
|
||||||
|
{
|
||||||
|
private ICircuitSimulator? circuitSimulator;
|
||||||
|
private int gateId;
|
||||||
|
private int count = 0;
|
||||||
|
private int tickRate = 0;
|
||||||
|
private bool currentState = false;
|
||||||
|
private bool inputInitialized = false;
|
||||||
|
private bool lastInputState = false;
|
||||||
|
|
||||||
|
public override int OutputCount { get; set; } = 1;
|
||||||
|
|
||||||
|
public override int InputCount { get; set; } = 1;
|
||||||
|
|
||||||
|
public string Info
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (tickRate <= 0)
|
||||||
|
{
|
||||||
|
return "Disabled";
|
||||||
|
}
|
||||||
|
|
||||||
|
return tickRate + "\n" + ((count > tickRate) ? count - tickRate : count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnInteraction(MouseStateExtended mouseState, MouseStateExtended previousMouseState, KeyboardStateExtended keyboardStateExtended)
|
||||||
|
{
|
||||||
|
if (mouseState.DeltaScrollWheelValue == 0 || !keyboardStateExtended.IsShiftDown())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mouseState.DeltaScrollWheelValue < 0 && tickRate < int.MaxValue - 10)
|
||||||
|
{
|
||||||
|
tickRate += keyboardStateExtended.IsControlDown() ? 10 : 1;
|
||||||
|
}
|
||||||
|
else if (tickRate >= 1)
|
||||||
|
{
|
||||||
|
tickRate -= keyboardStateExtended.IsControlDown() ? 10 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
tickRate = Math.Clamp(tickRate, 0, int.MaxValue);
|
||||||
|
|
||||||
|
if (tickRate <= 0)
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
currentState = false;
|
||||||
|
circuitSimulator?.SetSource(gateId, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal override void Register(ICircuitSimulator circuitSimulator)
|
||||||
|
{
|
||||||
|
this.circuitSimulator = circuitSimulator;
|
||||||
|
SimulatorGateId = circuitSimulator.AddGate(GateKind.Source);
|
||||||
|
gateId = SimulatorGateId;
|
||||||
|
count = 0;
|
||||||
|
currentState = false;
|
||||||
|
inputInitialized = false;
|
||||||
|
lastInputState = false;
|
||||||
|
circuitSimulator.SetSource(gateId, false);
|
||||||
|
_ = circuitSimulator.WatchGate(SimulatorGateId, (_, newInputMask) => OnInputChanged((newInputMask & 1) != 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInputChanged(bool inputState)
|
||||||
|
{
|
||||||
|
if (circuitSimulator == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inputInitialized)
|
||||||
|
{
|
||||||
|
inputInitialized = true;
|
||||||
|
lastInputState = inputState;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastInputState == inputState)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastInputState = inputState;
|
||||||
|
|
||||||
|
if (tickRate <= 0)
|
||||||
|
{
|
||||||
|
if (count != 0 || currentState)
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
currentState = false;
|
||||||
|
circuitSimulator.SetSource(gateId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (count >= tickRate * 2)
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool newState = count > tickRate;
|
||||||
|
if (newState != currentState)
|
||||||
|
{
|
||||||
|
currentState = newState;
|
||||||
|
circuitSimulator.SetSource(gateId, newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user