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 Microsoft.Xna.Framework.Graphics;
using StoneRed.LogicSimulator.Api.Utilities; using StoneRed.LogicSimulator.Api.Utilities;
using StoneRed.LogicSimulator.Simulation;
namespace StoneRed.LogicSimulator.Api; namespace StoneRed.LogicSimulator.Api;
#pragma warning disable S112 // General exceptions should never be thrown
internal abstract class LogicGate internal abstract class LogicGate
{ {
private readonly List<LogicGateConnection> logicGateConnections = new(); private readonly List<LogicGateConnection> logicGateConnections = [];
private bool logicGateConnectionsChnaged = false;
private int currentInput;
private int newInput;
private int output;
private int cachedOutput;
private LogicGateConnection[] logicGateConnectionsArray = Array.Empty<LogicGateConnection>();
public abstract int OutputCount { get; set; } public abstract int OutputCount { get; set; }
public abstract int InputCount { get; set; } public abstract int InputCount { get; set; }
internal IReadOnlyList<LogicGateConnection> LogicGateConnections => logicGateConnections.AsReadOnly(); internal IReadOnlyList<LogicGateConnection> LogicGateConnections => logicGateConnections.AsReadOnly();
internal GraphicsDevice? GraphicsDevice { get; set; } internal GraphicsDevice? GraphicsDevice { get; set; }
internal LogicGateWorldData WorldData { get; init; } = new LogicGateWorldData(); internal LogicGateWorldData WorldData { get; init; } = new LogicGateWorldData();
internal ulong Id { get; set; } internal ulong Id { get; set; }
internal int SimulatorGateId { get; set; } = -1;
public override bool Equals(object? obj) public override bool Equals(object? obj)
{ {
@@ -32,44 +27,14 @@ internal abstract class LogicGate
return Id.GetHashCode(); 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) internal void Connect(LogicGate logicGate, int inputIndex, int outputIndex)
{ {
if (inputIndex >= logicGate.InputCount) ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(inputIndex, logicGate.InputCount);
{ ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(outputIndex, OutputCount);
throw new IndexOutOfRangeException();
}
if (outputIndex >= OutputCount)
{
throw new IndexOutOfRangeException();
}
lock (logicGateConnections) lock (logicGateConnections)
{ {
logicGateConnections.Add(new LogicGateConnection(logicGate, inputIndex, outputIndex)); logicGateConnections.Add(new LogicGateConnection(logicGate, inputIndex, outputIndex));
logicGateConnectionsChnaged = true;
} }
} }
@@ -93,89 +58,24 @@ internal abstract class LogicGate
(!inputIndex.HasValue || c.InputIndex == inputIndex) && (!inputIndex.HasValue || c.InputIndex == inputIndex) &&
(!outputIndex.HasValue || c.OutputIndex == outputIndex)); (!outputIndex.HasValue || c.OutputIndex == outputIndex));
logicGateConnections.RemoveAt(index); 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 internal virtual void Initialize()
{ } { }
protected abstract void Execute(); protected internal abstract void Register(ICircuitSimulator circuitSimulator);
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 Texture2D CreateTexture(int width, int height) protected Texture2D CreateTexture(int width, int height)
{ {
return new Texture2D(GraphicsDevice, width, 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(LogicGate logicGate, int inputIndex, int outputIndex)
internal class LogicGateConnection
{ {
public LogicGate LogicGate { get; } public LogicGate LogicGate { get; } = logicGate;
public int InputIndex { get; } public int InputIndex { get; } = inputIndex;
public int OutputIndex { get; } public int OutputIndex { get; } = outputIndex;
public LogicGateConnection(LogicGate logicGate, int inputIndex, int outputIndex)
{
LogicGate = logicGate;
InputIndex = inputIndex;
OutputIndex = outputIndex;
}
}; };
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
@@ -13,5 +13,8 @@
<PackageReference Include="MonoGame.Extended.Input" Version="3.8.0" /> <PackageReference Include="MonoGame.Extended.Input" Version="3.8.0" />
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" /> <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StoneRed.LogicSimulator.Simulation\StoneRed.LogicSimulator.Simulation.csproj" />
</ItemGroup>
</Project> </Project>
@@ -14,6 +14,8 @@ internal class LogicGateSimulator
private DateTime dateTime; private DateTime dateTime;
private bool logicGatesUpdated = false; private bool logicGatesUpdated = false;
private ulong logicGateId = 0; private ulong logicGateId = 0;
private ICircuitSimulator? circuitSimulator;
public int TargetTicksPerSecond { get; set; } = 100; public int TargetTicksPerSecond { get; set; } = 100;
public int ActualTicksPerSecond { get; private set; } public int ActualTicksPerSecond { get; private set; }
@@ -34,13 +36,18 @@ internal class LogicGateSimulator
public void Start() public void Start()
{ {
if (IsRunning)
{
return;
}
IsRunning = true;
Thread thread = new Thread(SimulationThread) Thread thread = new Thread(SimulationThread)
{ {
IsBackground = true IsBackground = true
}; };
IsRunning = true;
thread.Start(); thread.Start();
} }
@@ -55,6 +62,11 @@ internal class LogicGateSimulator
logicGatesUpdated = logicGates.TryAdd(gate.Id, gate); logicGatesUpdated = logicGates.TryAdd(gate.Id, gate);
} }
public void LogicGatesUpdated()
{
logicGatesUpdated = true;
}
public LogicGate GetLogicGate(ulong id) public LogicGate GetLogicGate(ulong id)
{ {
return logicGates[id]; return logicGates[id];
@@ -75,24 +87,25 @@ internal class LogicGateSimulator
logicGatesUpdated = logicGates.TryRemove(logicGate.Id, out _); 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() private void SimulationThread()
{ {
int tps = 0; int tps = 0;
float iterations = 10000; float sleepDelayIterations = 10000;
int sleepDelayMs = 10;
LogicGate[] logicGatesArray = logicGates.Values.ToArray(); circuitSimulator = new CycleCircuitSimulator();
while (IsRunning) Timer timeCheckTimer = new Timer(_ =>
{
tps++;
if (logicGatesUpdated)
{
logicGatesArray = logicGates.Values.ToArray();
logicGatesUpdated = false;
}
if (DateTime.Now > dateTime.AddSeconds(1))
{ {
ActualTicksPerSecond = tps; ActualTicksPerSecond = tps;
dateTime = DateTime.Now; dateTime = DateTime.Now;
@@ -102,41 +115,62 @@ internal class LogicGateSimulator
{ {
float percentage = Math.Abs((TargetTicksPerSecond - (float)ActualTicksPerSecond) / Math.Abs((float)ActualTicksPerSecond) * 100); float percentage = Math.Abs((TargetTicksPerSecond - (float)ActualTicksPerSecond) / Math.Abs((float)ActualTicksPerSecond) * 100);
if (ActualTicksPerSecond / 2 > TargetTicksPerSecond) sleepDelayIterations *= (float)ActualTicksPerSecond / TargetTicksPerSecond;
{
iterations *= 2;
}
else if (ActualTicksPerSecond * 2 < TargetTicksPerSecond)
{
iterations /= 2;
}
else if (percentage > 5)
{
iterations *= (float)ActualTicksPerSecond / TargetTicksPerSecond;
}
ClockCalibrating = percentage > 5; 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++) circuitSimulator.Reset();
{
logicGatesArray[i].Update(); logicGatesUpdated = false;
} }
circuitSimulator.Step();
if (HighPerformanceClock) if (HighPerformanceClock)
{ {
Thread.SpinWait((int)iterations); Thread.SpinWait((int)sleepDelayIterations);
} }
else 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; 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.")] [LogicGateDescription("A button is a momentary switch that can be turned on and off.")]
internal class Button : LogicGate, IInteractable, IColorable 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 OutputCount { get; set; } = 1;
public override int InputCount { get; set; } = 0; public override int InputCount { get; set; } = 0;
public bool IsPressed { get; set; }
public Color Color { get; set; } = Color.Purple; public Color Color { get; set; } = Color.Purple;
public string Info { get; set; } = "OFF"; public string Info { get; set; } = "OFF";
public void OnInteraction(MouseStateExtended mouseState, MouseStateExtended previousMouseState, KeyboardStateExtended keyboardStateExtended) public void OnInteraction(MouseStateExtended mouseState, MouseStateExtended previousMouseState, KeyboardStateExtended keyboardStateExtended)
{ {
IsPressed = mouseState.IsButtonDown(MouseButton.Left); isPressed = mouseState.IsButtonDown(MouseButton.Left);
Color = IsPressed ? Color.Green : Color.Purple; Color = isPressed ? Color.Green : Color.Purple;
Info = IsPressed ? "ON" : "OFF"; Info = isPressed ? "ON" : "OFF";
circuitSimulator?.SetSource(gateId, isPressed);
} }
protected override void Execute() protected internal override void Register(ICircuitSimulator circuitSimulator)
{ {
if (IsPressed) this.circuitSimulator = circuitSimulator;
{ SimulatorGateId = circuitSimulator.AddGate(GateKind.Source);
SetOutputBit(1, 0); gateId = SimulatorGateId;
}
else
{
SetOutputBit(0, 0);
}
} }
} }
@@ -11,8 +11,12 @@ namespace StoneRed.LogicSimulator.Simulation.LogicGates;
[LogicGateDescription("A clock is a circuit that oscillates between a high and a low state.")] [LogicGateDescription("A clock is a circuit that oscillates between a high and a low state.")]
internal class Clock : LogicGate, IInteractable internal class Clock : LogicGate, IInteractable
{ {
private ICircuitSimulator? circuitSimulator;
private int gateId;
private int count = 0; private int count = 0;
private int tickRate = 0; private int tickRate = 0;
private bool currentState = false;
public override int OutputCount { get; set; } = 1; public override int OutputCount { get; set; } = 1;
public override int InputCount { get; set; } = 0; public override int InputCount { get; set; } = 0;
@@ -49,28 +53,41 @@ internal class Clock : LogicGate, IInteractable
tickRate = Math.Clamp(tickRate, 0, int.MaxValue); 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) if (tickRate <= 0)
{ {
SetOutputBit(0, 0); count = 0;
currentState = false;
circuitSimulator.SetSource(gateId, false);
return; return;
} }
// Count the tick
count++;
if (count >= tickRate * 2) if (count >= tickRate * 2)
{ {
count = 0; count = 0;
} }
count++; // Toggle state based on count
bool newState = count > tickRate;
if (newState != currentState)
{
currentState = newState;
circuitSimulator.SetSource(gateId, newState);
}
});
if (count > tickRate) // Initialize to off state
{ circuitSimulator.SetSource(gateId, false);
SetOutputBit(1, 0);
}
else
{
SetOutputBit(0, 0);
}
} }
} }
@@ -15,8 +15,15 @@ internal class Lamp : LogicGate, IColorable
public Color Color { get; set; } = Color.CadetBlue; 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; 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; 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; 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++) SimulatorGateId = circuitSimulator.AddGate(GateKind.Buffer);
{
SetOutputBit(GetInputBit(i), i);
}
} }
} }
@@ -12,6 +12,8 @@ namespace StoneRed.LogicSimulator.Simulation.LogicGates;
[LogicGateDescription("A switch is a toggleable switch that can be turned on and off.")] [LogicGateDescription("A switch is a toggleable switch that can be turned on and off.")]
internal class Switch : LogicGate, IInteractable, IColorable internal class Switch : LogicGate, IInteractable, IColorable
{ {
private ICircuitSimulator? circuitSimulator;
private int gateId;
private bool isPressed; private bool isPressed;
public override int OutputCount { get; set; } = 1; public override int OutputCount { get; set; } = 1;
@@ -28,17 +30,15 @@ internal class Switch : LogicGate, IInteractable, IColorable
Color = isPressed ? Color.Green : Color.Purple; Color = isPressed ? Color.Green : Color.Purple;
Info = isPressed ? "ON" : "OFF"; Info = isPressed ? "ON" : "OFF";
circuitSimulator?.SetSource(gateId, isPressed);
} }
protected override void Execute() protected internal override void Register(ICircuitSimulator circuitSimulator)
{ {
if (isPressed) this.circuitSimulator = circuitSimulator;
{
SetOutputBit(1, 0); SimulatorGateId = circuitSimulator.AddGate(GateKind.Source);
} gateId = SimulatorGateId;
else
{
SetOutputBit(0, 0);
}
} }
} }
@@ -33,28 +33,38 @@ internal class TestLogicGate : LogicGate, Api.Interfaces.IDrawable
texture = CreateTexture(15, 15); 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 // A
SetRangeWithSpacing(20, 24, 1, GetInputBit(0) == 1 ? Color.Red : Color.Gray); SetRangeWithSpacing(20, 24, 1, inputA ? Color.Red : Color.Gray);
// B // B
SetRangeWithSpacing(40, 100, 15, GetInputBit(1) == 1 ? Color.Red : Color.Gray); SetRangeWithSpacing(40, 100, 15, inputB ? Color.Red : Color.Gray);
// C // C
SetRangeWithSpacing(130, 190, 15, GetInputBit(2) == 1 ? Color.Red : Color.Gray); SetRangeWithSpacing(130, 190, 15, inputC ? Color.Red : Color.Gray);
// D // D
SetRangeWithSpacing(200, 204, 1, GetInputBit(3) == 1 ? Color.Red : Color.Gray); SetRangeWithSpacing(200, 204, 1, inputD ? Color.Red : Color.Gray);
// E // E
SetRangeWithSpacing(124, 184, 15, GetInputBit(4) == 1 ? Color.Red : Color.Gray); SetRangeWithSpacing(124, 184, 15, inputE ? Color.Red : Color.Gray);
// F // F
SetRangeWithSpacing(34, 94, 15, GetInputBit(5) == 1 ? Color.Red : Color.Gray); SetRangeWithSpacing(34, 94, 15, inputF ? Color.Red : Color.Gray);
// G // 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) private void SetRangeWithSpacing(int start, int end, int spacing, Color value)
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RollForward>Major</RollForward> <RollForward>Major</RollForward>
<PublishReadyToRun>false</PublishReadyToRun> <PublishReadyToRun>false</PublishReadyToRun>
<TieredCompilation>false</TieredCompilation> <TieredCompilation>false</TieredCompilation>
@@ -35,6 +35,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\StoneRed.LogicSimulator.Api\StoneRed.LogicSimulator.Api.csproj" /> <ProjectReference Include="..\StoneRed.LogicSimulator.Api\StoneRed.LogicSimulator.Api.csproj" />
<ProjectReference Include="..\StoneRed.LogicSimulator.Simulation\StoneRed.LogicSimulator.Simulation.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="UserInterface\Windows\SettingsWindow.cs"> <Compile Update="UserInterface\Windows\SettingsWindow.cs">
@@ -141,7 +141,7 @@ internal class WorldScreen : SrlsScreen<Grid>
// Draw logic gate connections // Draw logic gate connections
foreach (LogicGateConnection connection in logicGate.LogicGateConnections) 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); 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; connectionContext = null;
} }