Implement global clock synchronization for new simulator

This commit is contained in:
Stone_Red
2026-03-27 02:22:32 +01:00
parent c9371c356c
commit 5b3cdc186f
2 changed files with 30 additions and 83 deletions
@@ -11,10 +11,13 @@ namespace StoneRed.LogicSimulator.Simulation;
internal class LogicGateSimulator internal class LogicGateSimulator
{ {
private readonly ConcurrentDictionary<ulong, LogicGate> logicGates = new ConcurrentDictionary<ulong, LogicGate>(); private readonly ConcurrentDictionary<ulong, LogicGate> logicGates = new ConcurrentDictionary<ulong, LogicGate>();
private readonly List<int> globalClockTargets = [];
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; private ICircuitSimulator? circuitSimulator;
private int globalClockSourceGateId = -1;
private bool globalClockState;
public int TargetTicksPerSecond { get; set; } = 100; public int TargetTicksPerSecond { get; set; } = 100;
@@ -110,14 +113,15 @@ internal class LogicGateSimulator
private void SimulationThread() private void SimulationThread()
{ {
int tps = 0; int tps = 0;
float sleepDelayIterations = 10000; int sleepDelayIterations = 10000;
int sleepDelayMs = 10; int sleepDelayMs = 10;
circuitSimulator = CreateSimulator(); circuitSimulator = CreateSimulator();
Timer timeCheckTimer = new Timer(_ => Timer timeCheckTimer = new Timer(_ =>
{ {
ActualTicksPerSecond = tps; // Compensate for any time drift by calculating actual TPS and adjusting sleep delay accordingly
ActualTicksPerSecond = (int)Math.Round(tps / (DateTime.Now - dateTime).TotalSeconds);
dateTime = DateTime.Now; dateTime = DateTime.Now;
tps = 0; tps = 0;
@@ -125,7 +129,8 @@ 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);
sleepDelayIterations *= (float)ActualTicksPerSecond / TargetTicksPerSecond; sleepDelayIterations = Math.Max(sleepDelayIterations, 1);
sleepDelayIterations *= (int)((float)ActualTicksPerSecond / TargetTicksPerSecond);
ClockCalibrating = percentage > 5; ClockCalibrating = percentage > 5;
} }
@@ -143,11 +148,25 @@ internal class LogicGateSimulator
if (logicGatesUpdated) if (logicGatesUpdated)
{ {
circuitSimulator = CreateSimulator(); circuitSimulator = CreateSimulator();
globalClockTargets.Clear();
globalClockSourceGateId = circuitSimulator.AddGate(GateKind.Source);
globalClockState = false;
circuitSimulator.SetSource(globalClockSourceGateId, false);
// Register all gates first // Register all gates first
foreach (LogicGate gate in logicGates.Values) foreach (LogicGate gate in logicGates.Values)
{ {
gate.Register(circuitSimulator); gate.Register(circuitSimulator);
if (gate is LogicGates.Clock)
{
globalClockTargets.Add(gate.SimulatorGateId);
}
}
// Connect the global clock source to all clock gates
for (int i = 0; i < globalClockTargets.Count; i++)
{
circuitSimulator.ConnectGates(globalClockSourceGateId, globalClockTargets[i], 0);
} }
// Then connect them based on LogicGate connections // Then connect them based on LogicGate connections
@@ -169,11 +188,14 @@ internal class LogicGateSimulator
logicGatesUpdated = false; logicGatesUpdated = false;
} }
globalClockState = !globalClockState;
circuitSimulator.SetSource(globalClockSourceGateId, globalClockState);
circuitSimulator.Step(); circuitSimulator.Step();
if (HighPerformanceClock) if (HighPerformanceClock)
{ {
Thread.SpinWait((int)sleepDelayIterations); Thread.SpinWait(sleepDelayIterations);
} }
else else
{ {
@@ -193,4 +215,4 @@ internal class LogicGateSimulator
_ => throw new NotImplementedException() _ => throw new NotImplementedException()
}; };
} }
} }
@@ -1,93 +1,18 @@
using MonoGame.Extended.Input;
using StoneRed.LogicSimulator.Api; using StoneRed.LogicSimulator.Api;
using StoneRed.LogicSimulator.Api.Attributes; using StoneRed.LogicSimulator.Api.Attributes;
using StoneRed.LogicSimulator.Api.Interfaces;
using System;
namespace StoneRed.LogicSimulator.Simulation.LogicGates; namespace StoneRed.LogicSimulator.Simulation.LogicGates;
[LogicGateName("Clock")] [LogicGateName("Clock")]
[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
{ {
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 OutputCount { get; set; } = 1;
public override int InputCount { get; set; } = 0; public override int InputCount { get; set; } = 0;
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);
}
protected internal override void Register(ICircuitSimulator circuitSimulator) protected internal override void Register(ICircuitSimulator circuitSimulator)
{ {
this.circuitSimulator = circuitSimulator; SimulatorGateId = circuitSimulator.AddGate(GateKind.Buffer);
SimulatorGateId = circuitSimulator.AddGate(GateKind.Source);
gateId = SimulatorGateId;
// Watch own output to count ticks and toggle
circuitSimulator.WatchGate(SimulatorGateId, (oldMask, newMask) =>
{
if (tickRate <= 0)
{
count = 0;
currentState = false;
circuitSimulator.SetSource(gateId, false);
return;
}
// Count the tick
count++;
if (count >= tickRate * 2)
{
count = 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);
} }
} }