Implement new simulator

This commit is contained in:
Stone_Red
2026-03-26 20:44:40 +01:00
parent 057802c43d
commit 4db32460a8
14 changed files with 200 additions and 230 deletions
+10 -110
View File
@@ -1,25 +1,20 @@
using Microsoft.Xna.Framework.Graphics;
using StoneRed.LogicSimulator.Api.Utilities;
using StoneRed.LogicSimulator.Simulation;
namespace StoneRed.LogicSimulator.Api;
#pragma warning disable S112 // General exceptions should never be thrown
internal abstract class LogicGate
{
private readonly List<LogicGateConnection> logicGateConnections = new();
private bool logicGateConnectionsChnaged = false;
private int currentInput;
private int newInput;
private int output;
private int cachedOutput;
private LogicGateConnection[] logicGateConnectionsArray = Array.Empty<LogicGateConnection>();
private readonly List<LogicGateConnection> logicGateConnections = [];
public abstract int OutputCount { get; set; }
public abstract int InputCount { get; set; }
internal IReadOnlyList<LogicGateConnection> LogicGateConnections => logicGateConnections.AsReadOnly();
internal GraphicsDevice? GraphicsDevice { get; set; }
internal LogicGateWorldData WorldData { get; init; } = new LogicGateWorldData();
internal ulong Id { get; set; }
internal int SimulatorGateId { get; set; } = -1;
public override bool Equals(object? obj)
{
@@ -32,44 +27,14 @@ internal abstract class LogicGate
return Id.GetHashCode();
}
internal void NextTick()
{
currentInput = newInput;
newInput = 0;
output = 0;
}
internal void SetInput(int value, int index)
{
if (index >= InputCount)
{
throw new IndexOutOfRangeException();
}
// Inputs should act as or gates
if (value == 1)
{
newInput.SetBit(1, index);
}
}
internal void Connect(LogicGate logicGate, int inputIndex, int outputIndex)
{
if (inputIndex >= logicGate.InputCount)
{
throw new IndexOutOfRangeException();
}
if (outputIndex >= OutputCount)
{
throw new IndexOutOfRangeException();
}
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(inputIndex, logicGate.InputCount);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(outputIndex, OutputCount);
lock (logicGateConnections)
{
logicGateConnections.Add(new LogicGateConnection(logicGate, inputIndex, outputIndex));
logicGateConnectionsChnaged = true;
}
}
@@ -93,89 +58,24 @@ internal abstract class LogicGate
(!inputIndex.HasValue || c.InputIndex == inputIndex) &&
(!outputIndex.HasValue || c.OutputIndex == outputIndex));
logicGateConnections.RemoveAt(index);
logicGateConnectionsChnaged = true;
}
}
internal void Update()
{
Execute();
cachedOutput = output;
PublishOutput();
}
internal int GetCachedOutputBit(int index)
{
if (index < 0 || index >= OutputCount)
{
throw new IndexOutOfRangeException();
}
return cachedOutput.GetBit(index);
}
protected internal virtual void Initialize()
{ }
protected abstract void Execute();
protected int GetInputBit(int index)
{
if (index < 0 || index >= InputCount)
{
throw new IndexOutOfRangeException();
}
return currentInput.GetBit(index);
}
protected void SetOutputBit(int value, int index)
{
if (index < 0 || index >= OutputCount)
{
throw new IndexOutOfRangeException();
}
output.SetBit(value, index);
}
protected internal abstract void Register(ICircuitSimulator circuitSimulator);
protected Texture2D CreateTexture(int width, int height)
{
return new Texture2D(GraphicsDevice, width, height);
}
private void PublishOutput()
{
if (logicGateConnectionsChnaged)
{
lock (logicGateConnections)
{
logicGateConnectionsArray = logicGateConnections.ToArray();
logicGateConnectionsChnaged = false;
}
}
for (int i = 0; i < logicGateConnectionsArray.Length; i++)
internal class LogicGateConnection(LogicGate logicGate, int inputIndex, int outputIndex)
{
LogicGateConnection connection = logicGateConnectionsArray[i];
connection.LogicGate.SetInput(output.GetBit(connection.OutputIndex), connection.InputIndex);
}
}
}
public LogicGate LogicGate { get; } = logicGate;
#pragma warning restore S112 // General exceptions should never be thrown
internal class LogicGateConnection
{
public LogicGate LogicGate { get; }
public int InputIndex { get; }
public int OutputIndex { get; }
public LogicGateConnection(LogicGate logicGate, int inputIndex, int outputIndex)
{
LogicGate = logicGate;
InputIndex = inputIndex;
OutputIndex = outputIndex;
}
public int InputIndex { get; } = inputIndex;
public int OutputIndex { get; } = outputIndex;
};
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -13,5 +13,8 @@
<PackageReference Include="MonoGame.Extended.Input" Version="3.8.0" />
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StoneRed.LogicSimulator.Simulation\StoneRed.LogicSimulator.Simulation.csproj" />
</ItemGroup>
</Project>
@@ -14,6 +14,8 @@ internal class LogicGateSimulator
private DateTime dateTime;
private bool logicGatesUpdated = false;
private ulong logicGateId = 0;
private ICircuitSimulator? circuitSimulator;
public int TargetTicksPerSecond { get; set; } = 100;
public int ActualTicksPerSecond { get; private set; }
@@ -34,13 +36,18 @@ internal class LogicGateSimulator
public void Start()
{
if (IsRunning)
{
return;
}
IsRunning = true;
Thread thread = new Thread(SimulationThread)
{
IsBackground = true
};
IsRunning = true;
thread.Start();
}
@@ -55,6 +62,11 @@ internal class LogicGateSimulator
logicGatesUpdated = logicGates.TryAdd(gate.Id, gate);
}
public void LogicGatesUpdated()
{
logicGatesUpdated = true;
}
public LogicGate GetLogicGate(ulong id)
{
return logicGates[id];
@@ -75,24 +87,25 @@ internal class LogicGateSimulator
logicGatesUpdated = logicGates.TryRemove(logicGate.Id, out _);
}
public bool GetGateOutput(LogicGate gate)
{
if (circuitSimulator == null || gate.SimulatorGateId < 0)
{
return false;
}
return circuitSimulator.GetOutput(gate.SimulatorGateId);
}
private void SimulationThread()
{
int tps = 0;
float iterations = 10000;
float sleepDelayIterations = 10000;
int sleepDelayMs = 10;
LogicGate[] logicGatesArray = logicGates.Values.ToArray();
circuitSimulator = new CycleCircuitSimulator();
while (IsRunning)
{
tps++;
if (logicGatesUpdated)
{
logicGatesArray = logicGates.Values.ToArray();
logicGatesUpdated = false;
}
if (DateTime.Now > dateTime.AddSeconds(1))
Timer timeCheckTimer = new Timer(_ =>
{
ActualTicksPerSecond = tps;
dateTime = DateTime.Now;
@@ -102,41 +115,62 @@ internal class LogicGateSimulator
{
float percentage = Math.Abs((TargetTicksPerSecond - (float)ActualTicksPerSecond) / Math.Abs((float)ActualTicksPerSecond) * 100);
if (ActualTicksPerSecond / 2 > TargetTicksPerSecond)
{
iterations *= 2;
}
else if (ActualTicksPerSecond * 2 < TargetTicksPerSecond)
{
iterations /= 2;
}
else if (percentage > 5)
{
iterations *= (float)ActualTicksPerSecond / TargetTicksPerSecond;
}
sleepDelayIterations *= (float)ActualTicksPerSecond / TargetTicksPerSecond;
ClockCalibrating = percentage > 5;
}
else
{
// Update cached sleep delay when target TPS changes
sleepDelayMs = Math.Max((int)(1000f / TargetTicksPerSecond), 1);
}
}, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
while (IsRunning)
{
tps++;
if (logicGatesUpdated)
{
circuitSimulator = new CycleCircuitSimulator();
// Register all gates first
foreach (LogicGate gate in logicGates.Values)
{
gate.Register(circuitSimulator);
}
for (int i = 0; i < logicGatesArray.Length; i++)
// Then connect them based on LogicGate connections
foreach (LogicGate gate in logicGates.Values)
{
logicGatesArray[i].NextTick();
foreach (LogicGateConnection connection in gate.LogicGateConnections)
{
// Connect: this gate's output → connected gate's input
circuitSimulator.ConnectGates(
gate.SimulatorGateId,
connection.LogicGate.SimulatorGateId,
connection.InputIndex
);
}
}
for (int i = 0; i < logicGatesArray.Length; i++)
{
logicGatesArray[i].Update();
circuitSimulator.Reset();
logicGatesUpdated = false;
}
circuitSimulator.Step();
if (HighPerformanceClock)
{
Thread.SpinWait((int)iterations);
Thread.SpinWait((int)sleepDelayIterations);
}
else
{
Thread.Sleep(Math.Max((int)(1f / TargetTicksPerSecond * 1000), 1));
Thread.Sleep(sleepDelayMs);
}
}
_ = timeCheckTimer.Change(Timeout.Infinite, Timeout.Infinite);
}
}
@@ -11,8 +11,8 @@ internal class AndGate : LogicGate
public override int OutputCount { get; set; } = 1;
protected override void Execute()
protected internal override void Register(ICircuitSimulator circuitSimulator)
{
SetOutputBit(GetInputBit(0) == 1 && GetInputBit(1) == 1 ? 1 : 0, 0);
SimulatorGateId = circuitSimulator.AddGate(GateKind.And2);
}
}
@@ -11,30 +11,30 @@ namespace StoneRed.LogicSimulator.Simulation.LogicGates;
[LogicGateDescription("A button is a momentary switch that can be turned on and off.")]
internal class Button : LogicGate, IInteractable, IColorable
{
private ICircuitSimulator? circuitSimulator;
private int gateId;
private bool isPressed;
public override int OutputCount { get; set; } = 1;
public override int InputCount { get; set; } = 0;
public bool IsPressed { get; set; }
public Color Color { get; set; } = Color.Purple;
public string Info { get; set; } = "OFF";
public void OnInteraction(MouseStateExtended mouseState, MouseStateExtended previousMouseState, KeyboardStateExtended keyboardStateExtended)
{
IsPressed = mouseState.IsButtonDown(MouseButton.Left);
Color = IsPressed ? Color.Green : Color.Purple;
Info = IsPressed ? "ON" : "OFF";
isPressed = mouseState.IsButtonDown(MouseButton.Left);
Color = isPressed ? Color.Green : Color.Purple;
Info = isPressed ? "ON" : "OFF";
circuitSimulator?.SetSource(gateId, isPressed);
}
protected override void Execute()
protected internal override void Register(ICircuitSimulator circuitSimulator)
{
if (IsPressed)
{
SetOutputBit(1, 0);
}
else
{
SetOutputBit(0, 0);
}
this.circuitSimulator = circuitSimulator;
SimulatorGateId = circuitSimulator.AddGate(GateKind.Source);
gateId = SimulatorGateId;
}
}
@@ -11,8 +11,12 @@ namespace StoneRed.LogicSimulator.Simulation.LogicGates;
[LogicGateDescription("A clock is a circuit that oscillates between a high and a low state.")]
internal class Clock : LogicGate, IInteractable
{
private ICircuitSimulator? circuitSimulator;
private int gateId;
private int count = 0;
private int tickRate = 0;
private bool currentState = false;
public override int OutputCount { get; set; } = 1;
public override int InputCount { get; set; } = 0;
@@ -49,28 +53,41 @@ internal class Clock : LogicGate, IInteractable
tickRate = Math.Clamp(tickRate, 0, int.MaxValue);
}
protected override void Execute()
protected internal override void Register(ICircuitSimulator circuitSimulator)
{
this.circuitSimulator = circuitSimulator;
SimulatorGateId = circuitSimulator.AddGate(GateKind.Source);
gateId = SimulatorGateId;
// Watch own output to count ticks and toggle
circuitSimulator.WatchGate(SimulatorGateId, (oldMask, newMask) =>
{
if (tickRate <= 0)
{
SetOutputBit(0, 0);
count = 0;
currentState = false;
circuitSimulator.SetSource(gateId, false);
return;
}
// Count the tick
count++;
if (count >= tickRate * 2)
{
count = 0;
}
count++;
// Toggle state based on count
bool newState = count > tickRate;
if (newState != currentState)
{
currentState = newState;
circuitSimulator.SetSource(gateId, newState);
}
});
if (count > tickRate)
{
SetOutputBit(1, 0);
}
else
{
SetOutputBit(0, 0);
}
// Initialize to off state
circuitSimulator.SetSource(gateId, false);
}
}
@@ -15,8 +15,15 @@ internal class Lamp : LogicGate, IColorable
public Color Color { get; set; } = Color.CadetBlue;
protected override void Execute()
protected internal override void Register(ICircuitSimulator circuitSimulator)
{
Color = GetInputBit(0) == 0 ? Color.CadetBlue : Color.Yellow;
SimulatorGateId = circuitSimulator.AddGate(GateKind.Sink);
// Watch input to update lamp color
circuitSimulator.WatchGate(SimulatorGateId, (oldMask, newMask) =>
{
bool isOn = (newMask & 1) != 0;
Color = isOn ? Color.Yellow : Color.CadetBlue;
});
}
}
@@ -11,8 +11,8 @@ internal class NotGate : LogicGate
public override int OutputCount { get; set; } = 1;
protected override void Execute()
protected internal override void Register(ICircuitSimulator circuitSimulator)
{
SetOutputBit(GetInputBit(0) == 1 ? 0 : 1, 0);
SimulatorGateId = circuitSimulator.AddGate(GateKind.Not);
}
}
@@ -11,8 +11,8 @@ internal class OrGate : LogicGate
public override int OutputCount { get; set; } = 1;
protected override void Execute()
protected internal override void Register(ICircuitSimulator circuitSimulator)
{
SetOutputBit(GetInputBit(0), 0);
SimulatorGateId = circuitSimulator.AddGate(GateKind.Or2);
}
}
@@ -11,11 +11,8 @@ internal class Pin : LogicGate
public override int InputCount { get; set; } = 1;
protected override void Execute()
protected internal override void Register(ICircuitSimulator circuitSimulator)
{
for (int i = 0; i < InputCount; i++)
{
SetOutputBit(GetInputBit(i), i);
}
SimulatorGateId = circuitSimulator.AddGate(GateKind.Buffer);
}
}
@@ -12,6 +12,8 @@ namespace StoneRed.LogicSimulator.Simulation.LogicGates;
[LogicGateDescription("A switch is a toggleable switch that can be turned on and off.")]
internal class Switch : LogicGate, IInteractable, IColorable
{
private ICircuitSimulator? circuitSimulator;
private int gateId;
private bool isPressed;
public override int OutputCount { get; set; } = 1;
@@ -28,17 +30,15 @@ internal class Switch : LogicGate, IInteractable, IColorable
Color = isPressed ? Color.Green : Color.Purple;
Info = isPressed ? "ON" : "OFF";
circuitSimulator?.SetSource(gateId, isPressed);
}
protected override void Execute()
protected internal override void Register(ICircuitSimulator circuitSimulator)
{
if (isPressed)
{
SetOutputBit(1, 0);
}
else
{
SetOutputBit(0, 0);
}
this.circuitSimulator = circuitSimulator;
SimulatorGateId = circuitSimulator.AddGate(GateKind.Source);
gateId = SimulatorGateId;
}
}
@@ -33,28 +33,38 @@ internal class TestLogicGate : LogicGate, Api.Interfaces.IDrawable
texture = CreateTexture(15, 15);
}
protected override void Execute()
protected internal override void Register(ICircuitSimulator circuitSimulator)
{
SimulatorGateId = circuitSimulator.AddGate(GateKind.Sink);
// Watch all 7 inputs to update display
circuitSimulator.WatchGate(SimulatorGateId, (oldMask, newMask) =>
{
// Extract each bit from the input mask
bool inputA = (newMask & (1 << 0)) != 0;
bool inputB = (newMask & (1 << 1)) != 0;
bool inputC = (newMask & (1 << 2)) != 0;
bool inputD = (newMask & (1 << 3)) != 0;
bool inputE = (newMask & (1 << 4)) != 0;
bool inputF = (newMask & (1 << 5)) != 0;
bool inputG = (newMask & (1 << 6)) != 0;
// Update 7-segment display
// A
SetRangeWithSpacing(20, 24, 1, GetInputBit(0) == 1 ? Color.Red : Color.Gray);
SetRangeWithSpacing(20, 24, 1, inputA ? Color.Red : Color.Gray);
// B
SetRangeWithSpacing(40, 100, 15, GetInputBit(1) == 1 ? Color.Red : Color.Gray);
SetRangeWithSpacing(40, 100, 15, inputB ? Color.Red : Color.Gray);
// C
SetRangeWithSpacing(130, 190, 15, GetInputBit(2) == 1 ? Color.Red : Color.Gray);
SetRangeWithSpacing(130, 190, 15, inputC ? Color.Red : Color.Gray);
// D
SetRangeWithSpacing(200, 204, 1, GetInputBit(3) == 1 ? Color.Red : Color.Gray);
SetRangeWithSpacing(200, 204, 1, inputD ? Color.Red : Color.Gray);
// E
SetRangeWithSpacing(124, 184, 15, GetInputBit(4) == 1 ? Color.Red : Color.Gray);
SetRangeWithSpacing(124, 184, 15, inputE ? Color.Red : Color.Gray);
// F
SetRangeWithSpacing(34, 94, 15, GetInputBit(5) == 1 ? Color.Red : Color.Gray);
SetRangeWithSpacing(34, 94, 15, inputF ? Color.Red : Color.Gray);
// G
SetRangeWithSpacing(110, 114, 1, GetInputBit(6) == 1 ? Color.Red : Color.Gray);
SetRangeWithSpacing(110, 114, 1, inputG ? Color.Red : Color.Gray);
});
}
private void SetRangeWithSpacing(int start, int end, int spacing, Color value)
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<RollForward>Major</RollForward>
<PublishReadyToRun>false</PublishReadyToRun>
<TieredCompilation>false</TieredCompilation>
@@ -35,6 +35,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StoneRed.LogicSimulator.Api\StoneRed.LogicSimulator.Api.csproj" />
<ProjectReference Include="..\StoneRed.LogicSimulator.Simulation\StoneRed.LogicSimulator.Simulation.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="UserInterface\Windows\SettingsWindow.cs">
@@ -141,7 +141,7 @@ internal class WorldScreen : SrlsScreen<Grid>
// Draw logic gate connections
foreach (LogicGateConnection connection in logicGate.LogicGateConnections)
{
Color lineColor = logicGate.GetCachedOutputBit(connection.OutputIndex) == 1 ? Color.Red : Color.LightBlue;
Color lineColor = simulator.GetGateOutput(logicGate) ? Color.Red : Color.LightBlue;
srls.SpriteBatch.DrawLine((logicGate.WorldData.Position * srls.Scale) + (logicGateSize / 2 * srls.Scale), (connection.LogicGate.WorldData.Position * srls.Scale) + (logicGateSize / 2 * srls.Scale), lineColor, 5 * srls.Scale, hideCablesBehindGatesCheckBox.IsChecked ? 0.5f : 0.1f);
}
}
@@ -423,6 +423,7 @@ internal class WorldScreen : SrlsScreen<Grid>
}
}
simulator.LogicGatesUpdated();
connectionContext = null;
}