diff --git a/StoneRed.LogicSimulator.Api/LogicGate.cs b/StoneRed.LogicSimulator.Api/LogicGate.cs index 6dabdaf..af0f3d2 100644 --- a/StoneRed.LogicSimulator.Api/LogicGate.cs +++ b/StoneRed.LogicSimulator.Api/LogicGate.cs @@ -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 logicGateConnections = new(); - private bool logicGateConnectionsChnaged = false; - private int currentInput; - private int newInput; - private int output; - private int cachedOutput; - private LogicGateConnection[] logicGateConnectionsArray = Array.Empty(); + private readonly List logicGateConnections = []; public abstract int OutputCount { get; set; } public abstract int InputCount { get; set; } internal IReadOnlyList 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++) - { - LogicGateConnection connection = logicGateConnectionsArray[i]; - connection.LogicGate.SetInput(output.GetBit(connection.OutputIndex), connection.InputIndex); - } - } } -#pragma warning restore S112 // General exceptions should never be thrown - -internal class LogicGateConnection +internal class LogicGateConnection(LogicGate logicGate, int inputIndex, int outputIndex) { - public LogicGate LogicGate { get; } + public LogicGate LogicGate { get; } = logicGate; - 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; }; \ No newline at end of file diff --git a/StoneRed.LogicSimulator.Api/StoneRed.LogicSimulator.Api.csproj b/StoneRed.LogicSimulator.Api/StoneRed.LogicSimulator.Api.csproj index d37b560..077a3a5 100644 --- a/StoneRed.LogicSimulator.Api/StoneRed.LogicSimulator.Api.csproj +++ b/StoneRed.LogicSimulator.Api/StoneRed.LogicSimulator.Api.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 enable enable @@ -13,5 +13,8 @@ + + + diff --git a/StoneRed.LogicSimulator/Simulation/LogicGateSimulator.cs b/StoneRed.LogicSimulator/Simulation/LogicGateSimulator.cs index 89cc8dc..a54dd39 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGateSimulator.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGateSimulator.cs @@ -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,12 +87,44 @@ 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(); + + Timer timeCheckTimer = new Timer(_ => + { + ActualTicksPerSecond = tps; + dateTime = DateTime.Now; + tps = 0; + + if (HighPerformanceClock) + { + float percentage = Math.Abs((TargetTicksPerSecond - (float)ActualTicksPerSecond) / Math.Abs((float)ActualTicksPerSecond) * 100); + + 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) { @@ -88,55 +132,45 @@ internal class LogicGateSimulator if (logicGatesUpdated) { - logicGatesArray = logicGates.Values.ToArray(); + circuitSimulator = new CycleCircuitSimulator(); + + // Register all gates first + foreach (LogicGate gate in logicGates.Values) + { + gate.Register(circuitSimulator); + } + + // Then connect them based on LogicGate connections + foreach (LogicGate gate in logicGates.Values) + { + foreach (LogicGateConnection connection in gate.LogicGateConnections) + { + // Connect: this gate's output → connected gate's input + circuitSimulator.ConnectGates( + gate.SimulatorGateId, + connection.LogicGate.SimulatorGateId, + connection.InputIndex + ); + } + } + + circuitSimulator.Reset(); + logicGatesUpdated = false; } - if (DateTime.Now > dateTime.AddSeconds(1)) - { - ActualTicksPerSecond = tps; - dateTime = DateTime.Now; - tps = 0; - - if (HighPerformanceClock) - { - 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; - } - - ClockCalibrating = percentage > 5; - } - } - - for (int i = 0; i < logicGatesArray.Length; i++) - { - logicGatesArray[i].NextTick(); - } - - for (int i = 0; i < logicGatesArray.Length; i++) - { - logicGatesArray[i].Update(); - } + 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); } } \ No newline at end of file diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/AndGate.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/AndGate.cs index ca5050a..4786e89 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGates/AndGate.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/AndGate.cs @@ -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); } } \ No newline at end of file diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/Button.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/Button.cs index 1188ce5..0537aff 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGates/Button.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/Button.cs @@ -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; } } \ No newline at end of file diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/Clock.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/Clock.cs index 35816ec..663de5f 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGates/Clock.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/Clock.cs @@ -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) { - if (tickRate <= 0) + this.circuitSimulator = circuitSimulator; + SimulatorGateId = circuitSimulator.AddGate(GateKind.Source); + gateId = SimulatorGateId; + + // Watch own output to count ticks and toggle + circuitSimulator.WatchGate(SimulatorGateId, (oldMask, newMask) => { - SetOutputBit(0, 0); - return; - } + if (tickRate <= 0) + { + count = 0; + currentState = false; + circuitSimulator.SetSource(gateId, false); + return; + } - if (count >= tickRate * 2) - { - count = 0; - } + // Count the tick + count++; - count++; + if (count >= tickRate * 2) + { + count = 0; + } - if (count > tickRate) - { - SetOutputBit(1, 0); - } - else - { - SetOutputBit(0, 0); - } + // Toggle state based on count + bool newState = count > tickRate; + if (newState != currentState) + { + currentState = newState; + circuitSimulator.SetSource(gateId, newState); + } + }); + + // Initialize to off state + circuitSimulator.SetSource(gateId, false); } } \ No newline at end of file diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/Lamp.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/Lamp.cs index 9f9fb6f..46f9d5e 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGates/Lamp.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/Lamp.cs @@ -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; + }); } } \ No newline at end of file diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/NotGate.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/NotGate.cs index 43a48a1..67b370e 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGates/NotGate.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/NotGate.cs @@ -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); } } \ No newline at end of file diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/OrGate.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/OrGate.cs index b8f688d..c2265e9 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGates/OrGate.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/OrGate.cs @@ -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); } } \ No newline at end of file diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/Pin.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/Pin.cs index 04d07f4..ecd88fa 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGates/Pin.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/Pin.cs @@ -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); } } \ No newline at end of file diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/Switch.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/Switch.cs index 50feeff..c8a1be2 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGates/Switch.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/Switch.cs @@ -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; } } \ No newline at end of file diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/TestLogicGate.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/TestLogicGate.cs index 521c3f5..58eed52 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGates/TestLogicGate.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/TestLogicGate.cs @@ -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) { - // A - SetRangeWithSpacing(20, 24, 1, GetInputBit(0) == 1 ? Color.Red : Color.Gray); - - // B - SetRangeWithSpacing(40, 100, 15, GetInputBit(1) == 1 ? Color.Red : Color.Gray); - - // C - SetRangeWithSpacing(130, 190, 15, GetInputBit(2) == 1 ? Color.Red : Color.Gray); - - // D - SetRangeWithSpacing(200, 204, 1, GetInputBit(3) == 1 ? Color.Red : Color.Gray); - - // E - SetRangeWithSpacing(124, 184, 15, GetInputBit(4) == 1 ? Color.Red : Color.Gray); - - // F - SetRangeWithSpacing(34, 94, 15, GetInputBit(5) == 1 ? Color.Red : Color.Gray); - - // G - SetRangeWithSpacing(110, 114, 1, GetInputBit(6) == 1 ? Color.Red : Color.Gray); + 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, inputA ? Color.Red : Color.Gray); + // B + SetRangeWithSpacing(40, 100, 15, inputB ? Color.Red : Color.Gray); + // C + SetRangeWithSpacing(130, 190, 15, inputC ? Color.Red : Color.Gray); + // D + SetRangeWithSpacing(200, 204, 1, inputD ? Color.Red : Color.Gray); + // E + SetRangeWithSpacing(124, 184, 15, inputE ? Color.Red : Color.Gray); + // F + SetRangeWithSpacing(34, 94, 15, inputF ? Color.Red : Color.Gray); + // G + SetRangeWithSpacing(110, 114, 1, inputG ? Color.Red : Color.Gray); + }); } private void SetRangeWithSpacing(int start, int end, int spacing, Color value) diff --git a/StoneRed.LogicSimulator/StoneRed.LogicSimulator.csproj b/StoneRed.LogicSimulator/StoneRed.LogicSimulator.csproj index 39aa621..985a4ef 100644 --- a/StoneRed.LogicSimulator/StoneRed.LogicSimulator.csproj +++ b/StoneRed.LogicSimulator/StoneRed.LogicSimulator.csproj @@ -1,7 +1,7 @@ WinExe - net6.0 + net8.0 Major false false @@ -35,6 +35,7 @@ + diff --git a/StoneRed.LogicSimulator/UserInterface/Screens/WorldScreen.cs b/StoneRed.LogicSimulator/UserInterface/Screens/WorldScreen.cs index a5c90c7..4858038 100644 --- a/StoneRed.LogicSimulator/UserInterface/Screens/WorldScreen.cs +++ b/StoneRed.LogicSimulator/UserInterface/Screens/WorldScreen.cs @@ -141,7 +141,7 @@ internal class WorldScreen : SrlsScreen // 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 } } + simulator.LogicGatesUpdated(); connectionContext = null; }