From 6d0ca85b7fb1b35b9b7c02954415872694f2e2e7 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Sat, 29 Jul 2023 23:38:48 +0200 Subject: [PATCH] Add save and loader classes and some QoL improvements --- .../Content/ContextMenu.xmmp | 4 +- .../Content/QuickMenu.xmmp | 6 +- .../Content/WorldScreen.xmmp | 11 +- StoneRed.LogicSimulator/GlobalSuppressions.cs | 8 + .../Simulation/LogicGateSimulator.cs | 25 +- .../LogicGateDescriptionAttribute.cs | 14 + .../Attributes/LogicGateNameAttribute.cs | 14 + .../Simulation/LogicGates/Button.cs | 12 +- .../Simulation/LogicGates/Clock.cs | 12 +- .../LogicGates/Interfaces/LogicGate.cs | 27 +- .../Simulation/LogicGates/Lamp.cs | 7 +- ...cGateMetadata.cs => LogicGateWorldData.cs} | 4 +- .../Simulation/LogicGates/NotGate.cs | 18 ++ .../Simulation/LogicGates/Pin.cs | 9 +- .../Simulation/LogicGates/Switch.cs | 12 +- .../Simulation/LogicGates/TestLogicGate.cs | 20 -- StoneRed.LogicSimulator/Srls.cs | 20 +- .../StoneRed.LogicSimulator.csproj | 1 + .../UserInterface/Screens/LoadingScreen.cs | 58 ++-- .../UserInterface/Screens/SrlsScreen.cs | 15 +- .../UserInterface/Screens/StartScreen.cs | 27 +- .../UserInterface/Screens/WorldScreen.cs | 255 ++++++++++++++---- .../UserInterface/Windows/QuickMenu.cs | 3 + .../Utilities/FrequencyCalculator.cs | 30 +++ .../Utilities/InputHelper.cs | 31 +++ .../Utilities/LogicGatesManager.cs | 108 ++++++++ .../WorldSaveSystem/WorldLoader.cs | 41 +++ .../WorldReaders/IWorldReader.cs | 14 + .../WorldReaders/WorldReaderV1.cs | 113 ++++++++ .../WorldSaveSystem/WorldSaveLoadProgress.cs | 14 + .../WorldSaveSystem/WorldSaver.cs | 5 + .../WorldWriters/IWorldWriter.cs | 14 + .../WorldWriters/WorldWriterV1.cs | 76 ++++++ 33 files changed, 875 insertions(+), 153 deletions(-) create mode 100644 StoneRed.LogicSimulator/GlobalSuppressions.cs create mode 100644 StoneRed.LogicSimulator/Simulation/LogicGates/Attributes/LogicGateDescriptionAttribute.cs create mode 100644 StoneRed.LogicSimulator/Simulation/LogicGates/Attributes/LogicGateNameAttribute.cs rename StoneRed.LogicSimulator/Simulation/LogicGates/{LogicGateMetadata.cs => LogicGateWorldData.cs} (72%) create mode 100644 StoneRed.LogicSimulator/Simulation/LogicGates/NotGate.cs delete mode 100644 StoneRed.LogicSimulator/Simulation/LogicGates/TestLogicGate.cs create mode 100644 StoneRed.LogicSimulator/Utilities/FrequencyCalculator.cs create mode 100644 StoneRed.LogicSimulator/Utilities/InputHelper.cs create mode 100644 StoneRed.LogicSimulator/Utilities/LogicGatesManager.cs create mode 100644 StoneRed.LogicSimulator/WorldSaveSystem/WorldLoader.cs create mode 100644 StoneRed.LogicSimulator/WorldSaveSystem/WorldReaders/IWorldReader.cs create mode 100644 StoneRed.LogicSimulator/WorldSaveSystem/WorldReaders/WorldReaderV1.cs create mode 100644 StoneRed.LogicSimulator/WorldSaveSystem/WorldSaveLoadProgress.cs create mode 100644 StoneRed.LogicSimulator/WorldSaveSystem/WorldSaver.cs create mode 100644 StoneRed.LogicSimulator/WorldSaveSystem/WorldWriters/IWorldWriter.cs create mode 100644 StoneRed.LogicSimulator/WorldSaveSystem/WorldWriters/WorldWriterV1.cs diff --git a/StoneRed.LogicSimulator/Content/ContextMenu.xmmp b/StoneRed.LogicSimulator/Content/ContextMenu.xmmp index 35e1025..ff69662 100644 --- a/StoneRed.LogicSimulator/Content/ContextMenu.xmmp +++ b/StoneRed.LogicSimulator/Content/ContextMenu.xmmp @@ -1,8 +1,8 @@ - \ No newline at end of file diff --git a/StoneRed.LogicSimulator/Content/QuickMenu.xmmp b/StoneRed.LogicSimulator/Content/QuickMenu.xmmp index dfbe2a8..8c42bab 100644 --- a/StoneRed.LogicSimulator/Content/QuickMenu.xmmp +++ b/StoneRed.LogicSimulator/Content/QuickMenu.xmmp @@ -1,9 +1,11 @@ - - + + + + diff --git a/StoneRed.LogicSimulator/Content/WorldScreen.xmmp b/StoneRed.LogicSimulator/Content/WorldScreen.xmmp index 2ad4125..bf8d425 100644 --- a/StoneRed.LogicSimulator/Content/WorldScreen.xmmp +++ b/StoneRed.LogicSimulator/Content/WorldScreen.xmmp @@ -1,6 +1,6 @@ - + @@ -8,13 +8,20 @@ diff --git a/StoneRed.LogicSimulator/GlobalSuppressions.cs b/StoneRed.LogicSimulator/GlobalSuppressions.cs new file mode 100644 index 0000000..8d8f930 --- /dev/null +++ b/StoneRed.LogicSimulator/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Minor Code Smell", "S1643:Strings should not be concatenated using '+' in a loop", Justification = "", Scope = "member", Target = "~M:StoneRed.LogicSimulator.Utilities.LogicGatesManager.LoadLogicGates")] diff --git a/StoneRed.LogicSimulator/Simulation/LogicGateSimulator.cs b/StoneRed.LogicSimulator/Simulation/LogicGateSimulator.cs index befedb3..c7c8602 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGateSimulator.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGateSimulator.cs @@ -10,8 +10,10 @@ namespace StoneRed.LogicSimulator.Simulation; internal class LogicGateSimulator { - private readonly ConcurrentDictionary logicGates = new ConcurrentDictionary(); + private readonly ConcurrentDictionary logicGates = new ConcurrentDictionary(); private DateTime dateTime; + private bool logicGatesUpdated = false; + private ulong logicGateId = 0; public int TargetTicksPerSecond { get; set; } = 100; public int ActualTicksPerSecond { get; private set; } @@ -22,7 +24,7 @@ internal class LogicGateSimulator public bool HighPerformanceClock { get; set; } - public LogicGateSimulator(List logicGates) + public LogicGateSimulator(IEnumerable logicGates) { foreach (LogicGate logicGate in logicGates) { @@ -49,11 +51,11 @@ internal class LogicGateSimulator public void AddLogicGate(LogicGate gate) { - gate.Id = logicGates.Count; - _ = logicGates.TryAdd(gate.Id, gate); + gate.Id = logicGateId++; + logicGatesUpdated = logicGates.TryAdd(gate.Id, gate); } - public LogicGate GetLogicGate(int id) + public LogicGate GetLogicGate(ulong id) { return logicGates[id]; } @@ -65,7 +67,12 @@ internal class LogicGateSimulator public void RemoveLogicGate(LogicGate logicGate) { - _ = logicGates.TryRemove(logicGate.Id, out _); + foreach (LogicGate otherLogicGate in logicGates.Values.Where(l => l.IsConnectedTo(logicGate))) + { + otherLogicGate.Disconnect(logicGate); + } + + logicGatesUpdated = logicGates.TryRemove(logicGate.Id, out _); } public void SimulationThread() @@ -79,6 +86,12 @@ internal class LogicGateSimulator { tps++; + if (logicGatesUpdated) + { + logicGatesArray = logicGates.Values.ToArray(); + logicGatesUpdated = false; + } + if (DateTime.Now > dateTime.AddSeconds(1)) { ActualTicksPerSecond = tps; diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/Attributes/LogicGateDescriptionAttribute.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/Attributes/LogicGateDescriptionAttribute.cs new file mode 100644 index 0000000..d614360 --- /dev/null +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/Attributes/LogicGateDescriptionAttribute.cs @@ -0,0 +1,14 @@ +using System; + +namespace StoneRed.LogicSimulator.Simulation.LogicGates.Attributes; + +[AttributeUsage(AttributeTargets.All)] +internal class LogicGateDescriptionAttribute : Attribute +{ + public string Description { get; } + + public LogicGateDescriptionAttribute(string description) + { + Description = description; + } +} \ No newline at end of file diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/Attributes/LogicGateNameAttribute.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/Attributes/LogicGateNameAttribute.cs new file mode 100644 index 0000000..b1c20bb --- /dev/null +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/Attributes/LogicGateNameAttribute.cs @@ -0,0 +1,14 @@ +using System; + +namespace StoneRed.LogicSimulator.Simulation.LogicGates.Attributes; + +[AttributeUsage(AttributeTargets.Class)] +internal class LogicGateNameAttribute : Attribute +{ + public string Name { get; } + + public LogicGateNameAttribute(string name) + { + Name = name; + } +} \ No newline at end of file diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/Button.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/Button.cs index 27dced2..e531b9f 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGates/Button.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/Button.cs @@ -2,25 +2,23 @@ using MonoGame.Extended.Input; +using StoneRed.LogicSimulator.Simulation.LogicGates.Attributes; using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces; namespace StoneRed.LogicSimulator.Simulation.LogicGates; +[LogicGateName("Button")] +[LogicGateDescription("A button is a momentary switch that can be turned on and off.")] internal class Button : LogicGate, IInteractable, IColorable { - public override int OutputCount => 1; + public override int OutputCount { get; set; } = 1; - public override int InputCount => 0; + 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 Button() - { - Metadata.Name = "Button"; - } - public void OnInteraction(MouseStateExtended mouseState, MouseStateExtended previousMouseState, KeyboardStateExtended keyboardStateExtended) { IsPressed = mouseState.IsButtonDown(MouseButton.Left); diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/Clock.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/Clock.cs index ee37044..6b54b88 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGates/Clock.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/Clock.cs @@ -1,18 +1,21 @@ using MonoGame.Extended.Input; +using StoneRed.LogicSimulator.Simulation.LogicGates.Attributes; using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces; using System; namespace StoneRed.LogicSimulator.Simulation.LogicGates; +[LogicGateName("Clock")] +[LogicGateDescription("A clock is a circuit that oscillates between a high and a low state.")] internal class Clock : LogicGate, IInteractable { private int count = 0; private int tickRate = 0; - public override int OutputCount => 1; + public override int OutputCount { get; set; } = 1; - public override int InputCount => 0; + public override int InputCount { get; set; } = 0; public string Info { @@ -27,11 +30,6 @@ internal class Clock : LogicGate, IInteractable } } - public Clock() - { - Metadata.Name = "Clock"; - } - public void OnInteraction(MouseStateExtended mouseState, MouseStateExtended previousMouseState, KeyboardStateExtended keyboardStateExtended) { if (mouseState.DeltaScrollWheelValue == 0 || !keyboardStateExtended.IsShiftDown()) diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/Interfaces/LogicGate.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/Interfaces/LogicGate.cs index fa05d99..d6e5317 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGates/Interfaces/LogicGate.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/Interfaces/LogicGate.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; namespace StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces; #pragma warning disable S112 // General exceptions should never be thrown @@ -14,12 +15,12 @@ internal abstract class LogicGate private int output; private int cachedOutput; - public LogicGateMetadata Metadata { get; set; } = new LogicGateMetadata(); - public int Id { get; set; } + public LogicGateWorldData WorldData { get; init; } = new LogicGateWorldData(); + public ulong Id { get; internal set; } public IReadOnlyList LogicGateConnections => logicGateConnections.AsReadOnly(); - public abstract int OutputCount { get; } + public abstract int OutputCount { get; set; } - public abstract int InputCount { get; } + public abstract int InputCount { get; set; } public void NextTick() { @@ -58,6 +59,13 @@ internal abstract class LogicGate logicGateConnections.Add(new LogicGateConnection(logicGate, inputIndex, outputIndex)); } + public bool IsConnectedTo(LogicGate logicGate) + { +#pragma warning disable S6605 // Collection-specific "Exists" method should be used instead of the "Any" extension + return logicGateConnections.Any(c => c.LogicGate.Id == logicGate.Id); +#pragma warning restore S6605 // Collection-specific "Exists" method should be used instead of the "Any" extension + } + public void Disconnect(LogicGate logicGate) { int index = logicGateConnections.FindIndex(c => c.LogicGate.Id == logicGate.Id); @@ -81,6 +89,17 @@ internal abstract class LogicGate return cachedOutput.GetBit(index); } + public override bool Equals(object? obj) + { + return obj is LogicGate gate && + Id == gate.Id; + } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } + protected abstract void Execute(); protected int GetInputBit(int index) diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/Lamp.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/Lamp.cs index 6d0d40b..ccbf2b4 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGates/Lamp.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/Lamp.cs @@ -1,14 +1,17 @@ using Microsoft.Xna.Framework; +using StoneRed.LogicSimulator.Simulation.LogicGates.Attributes; using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces; namespace StoneRed.LogicSimulator.Simulation.LogicGates; +[LogicGateName("Lamp")] +[LogicGateDescription("A lamp is a light source that can be turned on and off.")] internal class Lamp : LogicGate, IColorable { - public override int OutputCount => 0; + public override int OutputCount { get; set; } = 0; - public override int InputCount => 1; + public override int InputCount { get; set; } = 1; public Color Color { get; set; } = Color.CadetBlue; diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/LogicGateMetadata.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/LogicGateWorldData.cs similarity index 72% rename from StoneRed.LogicSimulator/Simulation/LogicGates/LogicGateMetadata.cs rename to StoneRed.LogicSimulator/Simulation/LogicGates/LogicGateWorldData.cs index 31e12d3..369b4a7 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGates/LogicGateMetadata.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/LogicGateWorldData.cs @@ -2,12 +2,10 @@ namespace StoneRed.LogicSimulator.Simulation.LogicGates; -internal record LogicGateMetadata +internal record LogicGateWorldData { public Vector2 Position { get; set; } - public Vector2 Size { get; set; } = new Vector2(100, 100); - public string Name { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; } \ No newline at end of file diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/NotGate.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/NotGate.cs new file mode 100644 index 0000000..b068e18 --- /dev/null +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/NotGate.cs @@ -0,0 +1,18 @@ +using StoneRed.LogicSimulator.Simulation.LogicGates.Attributes; +using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces; + +namespace StoneRed.LogicSimulator.Simulation.LogicGates; + +[LogicGateName("Not Gate")] +[LogicGateDescription("A not gate is a gate that inverts the input.")] +internal class NotGate : LogicGate +{ + public override int InputCount { get; set; } = 1; + + public override int OutputCount { get; set; } = 1; + + protected override void Execute() + { + SetOutputBit(GetInputBit(0) == 1 ? 0 : 1, 0); + } +} \ No newline at end of file diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/Pin.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/Pin.cs index ce430d7..5b7b1fb 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGates/Pin.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/Pin.cs @@ -1,12 +1,15 @@ -using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces; +using StoneRed.LogicSimulator.Simulation.LogicGates.Attributes; +using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces; namespace StoneRed.LogicSimulator.Simulation.LogicGates; +[LogicGateName("Pin")] +[LogicGateDescription("A pin is a input or output.")] internal class Pin : LogicGate { - public override int OutputCount => 1; + public override int OutputCount { get; set; } = 1; - public override int InputCount => 1; + public override int InputCount { get; set; } = 1; protected override void Execute() { diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/Switch.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/Switch.cs index f379c39..47614c3 100644 --- a/StoneRed.LogicSimulator/Simulation/LogicGates/Switch.cs +++ b/StoneRed.LogicSimulator/Simulation/LogicGates/Switch.cs @@ -2,25 +2,23 @@ using MonoGame.Extended.Input; +using StoneRed.LogicSimulator.Simulation.LogicGates.Attributes; using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces; namespace StoneRed.LogicSimulator.Simulation.LogicGates; +[LogicGateName("Switch")] +[LogicGateDescription("A switch is a toggleable switch that can be turned on and off.")] internal class Switch : LogicGate, IInteractable, IColorable { - public override int OutputCount => 1; + public override int OutputCount { get; set; } = 1; - public override int InputCount => 0; + 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 Switch() - { - Metadata.Name = "Switch"; - } - public void OnInteraction(MouseStateExtended mouseState, MouseStateExtended previousMouseState, KeyboardStateExtended keyboardStateExtended) { if (mouseState.IsButtonDown(MouseButton.Left) && !previousMouseState.IsButtonDown(MouseButton.Left)) diff --git a/StoneRed.LogicSimulator/Simulation/LogicGates/TestLogicGate.cs b/StoneRed.LogicSimulator/Simulation/LogicGates/TestLogicGate.cs deleted file mode 100644 index 1ea8aed..0000000 --- a/StoneRed.LogicSimulator/Simulation/LogicGates/TestLogicGate.cs +++ /dev/null @@ -1,20 +0,0 @@ -using StoneRed.LogicSimulator.Simulation.LogicGates.Interfaces; - -namespace StoneRed.LogicSimulator.Simulation.LogicGates; - -internal class TestLogicGate : LogicGate -{ - public override int InputCount => 1; - - public override int OutputCount => 1; - - public TestLogicGate() - { - Metadata.Name = "Test"; - } - - protected override void Execute() - { - SetOutputBit(GetInputBit(0) == 1 ? 0 : 1, 0); - } -} \ No newline at end of file diff --git a/StoneRed.LogicSimulator/Srls.cs b/StoneRed.LogicSimulator/Srls.cs index d7ef1b2..9e6ab88 100644 --- a/StoneRed.LogicSimulator/Srls.cs +++ b/StoneRed.LogicSimulator/Srls.cs @@ -2,7 +2,6 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework.Input; using MonoGame.Extended.Screens; @@ -33,6 +32,8 @@ internal class Srls : Game public FontSystem FontSystem { get; private set; } = null!; public AssetManager AssetManager { get; private set; } = null!; + public LogicGatesManager LogicGatesManager { get; private set; } = null!; + public Settings Settings { get; set; } public string ContentPath { get; } @@ -98,13 +99,17 @@ internal class Srls : Game currentSrlsWindow = window; } - public void ShowContextMenu(string title, Point position, params MenuItem[] menuItems) + public TextButton ShowContextMenu(string title, Point position, MenuItem[] menuItems, bool showButton = false) { string data = File.ReadAllText(Path.Combine(ContentPath, "ContextMenu.xmmp")); VerticalStackPanel contextMenu = (VerticalStackPanel)Project.LoadFromXml(data, AssetManager).Root; contextMenu.FindChildById