Skip to content

Significantly improved performance by adding caching of combinational chips #507

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Assets/Scripts/Description/Types/ChipDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class ChipDescription
public string Name;
public NameDisplayLocation NameLocation;
public ChipType ChipType;
public bool ShouldBeCached;
public Vector2 Size;
public Color Colour;
public PinDescription[] InputPins;
Expand Down
5 changes: 4 additions & 1 deletion Assets/Scripts/Game/Project/DevChipInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ public void NotifySaved(ChipDescription savedDescription)
LastSavedDescription = savedDescription;

RegenerateParentChipNamesHash();

Simulator.combinationalChipCaches.Remove(savedDescription.Name);
Simulator.chipsKnowToNotBeCombinational.Remove(savedDescription.Name);
}

public void AddNewSubChip(SubChipInstance subChip, bool isLoading)
Expand All @@ -228,7 +231,7 @@ public void AddNewDevPin(DevPinInstance pin, bool isLoadingFromFile)
AddElement(pin);
if (!isLoadingFromFile)
{
Simulator.AddPin(SimChip, pin.ID, pin.IsInputPin);
Simulator.AddPin(SimChip, pin.ID, pin.IsInputPin, pin.BitCount);
}
}

Expand Down
5 changes: 5 additions & 0 deletions Assets/Scripts/Game/Project/Project.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ public void EnterViewMode(SubChipInstance subchip)
{
if (chipLibrary.TryGetChipDescription(subchip.Description.Name, out ChipDescription description))
{
Simulator.useCaching = false; // Disable caching while viewing so subchips actually show what they are doing
Simulator.isCreatingACache = false; // Cancel any cache creation that might be going on

SimChip simChipToView = ViewedChip.SimChip.GetSubChipFromID(subchip.ID);

DevChipInstance viewChip = DevChipInstance.LoadFromDescriptionTest(description, chipLibrary).devChip;
Expand All @@ -140,6 +143,8 @@ public void ReturnToPreviousViewedChip()
chipViewStack.Pop();
controller.CancelEverything();
UpdateViewedChipsString();

if (chipViewStack.Count == 1) Simulator.useCaching = true; // Left View mode, so turn caching back on
}
}

Expand Down
2 changes: 1 addition & 1 deletion Assets/Scripts/Graphics/UI/Menus/BottomBarUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static class BottomBarUI
{
public const float barHeight = 3;
const float padY = 0.3f;
const float buttonSpacing = 0.25f;
public const float buttonSpacing = 0.25f;
const float buttonHeight = barHeight - padY * 2;

const string shortcutTextCol = "<color=#666666ff>";
Expand Down
44 changes: 44 additions & 0 deletions Assets/Scripts/Graphics/UI/Menus/ChipCustomizationMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using DLS.Description;
using DLS.Game;
using DLS.Simulation;
using Seb.Helpers;
using Seb.Vis;
using Seb.Vis.UI;
Expand All @@ -19,6 +20,7 @@ public static class ChipCustomizationMenu
"Name: Hidden"
};

static readonly string[] cachingOptions = { "Caching: Off", "Caching: On" };

// ---- State ----
static SubChipInstance[] subChipsWithDisplays;
Expand All @@ -29,6 +31,7 @@ public static class ChipCustomizationMenu
static readonly UIHandle ID_ColourPicker = new("CustomizeMenu_ChipCol");
static readonly UIHandle ID_ColourHexInput = new("CustomizeMenu_ChipColHexInput");
static readonly UIHandle ID_NameDisplayOptions = new("CustomizeMenu_NameDisplayOptions");
static readonly UIHandle ID_CachingOptions = new("CustomizeMenu_CachingOptions");
static readonly UI.ScrollViewDrawElementFunc drawDisplayScrollEntry = DrawDisplayScroll;
static readonly Func<string, bool> hexStringInputValidator = ValidateHexStringInput;

Expand Down Expand Up @@ -78,6 +81,40 @@ public static void DrawMenu()
UpdateChipColFromHexString(hexColInput.text);
}

// ---- Chip caching UI ----
UI.DrawText("Chip Caching:", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeDefault, NextPos(1), Anchor.TopLeft, Color.white);
SimChip chip = Project.ActiveProject.ViewedChip.SimChip;
if (chip.IsCombinational())
{
int numberOfInputBits = chip.CalculateNumberOfInputBits();
if (numberOfInputBits <= Simulator.MAX_NUM_INPUT_BITS_WHEN_AUTO_CACHING)
{
UI.DrawText("This chip is being cached.", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white);
}
else if (numberOfInputBits <= Simulator.MAX_NUM_INPUT_BITS_WHEN_USER_CACHING)
{
int shouldBeCachedNum = UI.WheelSelector(ID_CachingOptions, cachingOptions, NextPos(), new Vector2(pw, DrawSettings.ButtonHeight), theme.OptionsWheel, Anchor.TopLeft);
bool shouldBeCached = false;
if (shouldBeCachedNum == 1) shouldBeCached = true;
ChipSaveMenu.ActiveCustomizeDescription.ShouldBeCached = shouldBeCached;
UI.DrawText("WARNING: Caching chips with many", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white);
UI.DrawText("input bits significantly", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white);
UI.DrawText("increases the time required to", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white);
UI.DrawText("create the cache and may also", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white);
UI.DrawText("increase memory consumption!", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white);
}
else
{
UI.DrawText("This chip has too many input", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white);
UI.DrawText("bits to be cached.", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white);
}
}
else
{
UI.DrawText("Non-combinational chips", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white);
UI.DrawText("can not be cached.", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white);
}

// ---- Displays UI ----
Color labelCol = ColHelper.Darken(theme.MenuPanelCol, 0.01f);
Vector2 labelPos = NextPos(1);
Expand Down Expand Up @@ -151,6 +188,13 @@ static void InitUIFromChipDescription()
// Init name display mode
WheelSelectorState nameDisplayWheelState = UI.GetWheelSelectorState(ID_NameDisplayOptions);
nameDisplayWheelState.index = (int)ChipSaveMenu.ActiveCustomizeDescription.NameLocation;

// Init cache setting
WheelSelectorState cacheSettingWheelState = UI.GetWheelSelectorState(ID_CachingOptions);
bool cacheBool = ChipSaveMenu.ActiveCustomizeDescription.ShouldBeCached;
int cacheInt = 0;
if (cacheBool) cacheInt = 1;
cacheSettingWheelState.index = cacheInt;
}

static void UpdateCustomizeDescription()
Expand Down
20 changes: 20 additions & 0 deletions Assets/Scripts/Graphics/UI/Menus/CreateCacheUI.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using DLS.Simulation;
using Seb.Helpers;
using Seb.Vis;
using Seb.Vis.UI;
using UnityEngine;

namespace DLS.Graphics
{
public static class CreateCacheUI
{
public static void DrawCreatingCacheInfo()
{
string chipName = Simulator.nameOfChipWhoseCacheIsBeingCreated;
int percentage = (int)(Simulator.cacheCreatingProgress * 100);
string text = $"Creating Cache ({percentage}%): {chipName}";
Vector2 textSize = UI.CalculateTextSize(text, UIThemeLibrary.FontSizeDefault, UIThemeLibrary.DefaultFont);
UI.TextWithBackground(new Vector2(BottomBarUI.buttonSpacing, BottomBarUI.barHeight + BottomBarUI.buttonSpacing), new Vector2(textSize.x + 1, textSize.y + 1), Anchor.BottomLeft, text, UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeDefault, Color.yellow, ColHelper.MakeCol255(40));
}
}
}
2 changes: 2 additions & 0 deletions Assets/Scripts/Graphics/UI/Menus/CreateCacheUI.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Assets/Scripts/Graphics/UI/UIDrawer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using DLS.Game;
using DLS.Simulation;
using Seb.Vis.UI;

namespace DLS.Graphics
Expand Down Expand Up @@ -57,6 +58,7 @@ static void DrawProjectMenus(Project project)

if (menuToDraw != MenuType.ChipCustomization) BottomBarUI.DrawUI(project);

bool aMenuIsOpen = true;
if (menuToDraw == MenuType.ChipSave) ChipSaveMenu.DrawMenu();
else if (menuToDraw == MenuType.ChipLibrary) ChipLibraryMenu.DrawMenu();
else if (menuToDraw == MenuType.ChipCustomization) ChipCustomizationMenu.DrawMenu();
Expand All @@ -73,8 +75,12 @@ static void DrawProjectMenus(Project project)
bool showSimPausedBanner = project.simPaused;
if (showSimPausedBanner) SimPausedUI.DrawPausedBanner();
if (project.chipViewStack.Count > 1) ViewedChipsBar.DrawViewedChipsBanner(project, showSimPausedBanner);
if (Simulator.isCreatingACache) CreateCacheUI.DrawCreatingCacheInfo();
aMenuIsOpen = false;
}

if (aMenuIsOpen) Simulator.isCreatingACache = false; // Cancel current caching process when a menu gets opened

ContextMenu.Update();
}

Expand Down
1 change: 1 addition & 0 deletions Assets/Scripts/SaveSystem/DescriptionCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public static ChipDescription CreateChipDescription(DevChipInstance chip)
NameLocation = hasSavedDesc ? descOld.NameLocation : NameDisplayLocation.Centre,
Size = size,
Colour = col,
ShouldBeCached = hasSavedDesc ? descOld.ShouldBeCached : false,

SubChips = subchips,
InputPins = inputPins,
Expand Down
5 changes: 5 additions & 0 deletions Assets/Scripts/SaveSystem/Loader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using DLS.Description;
using DLS.Game;
using DLS.Simulation;

namespace DLS.SaveSystem
{
Expand All @@ -24,6 +25,10 @@ public static Project LoadProject(string projectName)
{
ProjectDescription projectDescription = LoadProjectDescription(projectName);
ChipLibrary chipLibrary = LoadChipLibrary(projectDescription);

Simulator.combinationalChipCaches.Clear();
Simulator.chipsKnowToNotBeCombinational.Clear();

return new Project(projectDescription, chipLibrary);
}

Expand Down
134 changes: 133 additions & 1 deletion Assets/Scripts/Simulation/SimChip.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using DLS.Description;

Expand All @@ -8,13 +9,15 @@ public class SimChip
{
public readonly ChipType ChipType;
public readonly int ID;
public readonly string Name;

// Some builtin chips, such as RAM, require an internal state for memory
// (can also be used for other arbitrary chip-specific data)
public readonly uint[] InternalState = Array.Empty<uint>();
public readonly bool IsBuiltin;
public SimPin[] InputPins = Array.Empty<SimPin>();
public int numConnectedInputs;
public bool shouldBeCached; // True, if the user specifically wanted this chip to be cached

public int numInputsReady;
public SimPin[] OutputPins = Array.Empty<SimPin>();
Expand All @@ -30,7 +33,9 @@ public SimChip(ChipDescription desc, int id, uint[] internalState, SimChip[] sub
{
SubChips = subChips;
ID = id;
Name = desc.Name;
ChipType = desc.ChipType;
shouldBeCached = desc.ShouldBeCached;
IsBuiltin = ChipType != ChipType.Custom;

// ---- Create pins (don't allocate unnecessarily as very many sim chips maybe created!) ----
Expand Down Expand Up @@ -85,6 +90,133 @@ public SimChip(ChipDescription desc, int id, uint[] internalState, SimChip[] sub
}
}

// Returns true, when this chip is purely combinational / stateless. This is the case, when the outputs of this chip depend entirely on the inputs and on nothing else.
public bool IsCombinational()
{
// Handle built in chips
switch (ChipType)
{
case ChipType.Nand:
case ChipType.TriStateBuffer:
case ChipType.Merge_1To4Bit:
case ChipType.Merge_1To8Bit:
case ChipType.Merge_4To8Bit:
case ChipType.Split_4To1Bit:
case ChipType.Split_8To4Bit:
case ChipType.Split_8To1Bit:
return true;
case ChipType.Clock:
case ChipType.Pulse:
case ChipType.dev_Ram_8Bit:
case ChipType.Rom_256x16:
case ChipType.SevenSegmentDisplay:
case ChipType.DisplayRGB:
case ChipType.DisplayDot:
case ChipType.DisplayLED:
case ChipType.Key:
case ChipType.Buzzer:
return false;
}

// Chip isn't combinational, if any of the subChips inputPins has more than one connection
foreach (SimChip subChip in SubChips)
{
foreach (SimPin inputPin in subChip.InputPins)
{
if (inputPin.numInputConnections > 1) return false;
}
}

// Can only be combinational if all subchips are combinational
foreach (SimChip subChip in SubChips)
{
// recursively make sure, that subchip is combinational
if (!subChip.IsCombinational()) return false;
}

// Check for loops in wiring using topo sort
Dictionary<int, List<int>> graph = new(); // chipID -> list of dependent chipIDs
Dictionary<int, int> inDegree = new(); // chipID -> number of incoming edges
// Build Graph
foreach (SimChip chip in SubChips)
{
int chipID = chip.ID;
if (!graph.ContainsKey(chipID)) graph[chipID] = new List<int>();
if (!inDegree.ContainsKey(chipID)) inDegree[chipID] = 0;

foreach (SimPin output in chip.OutputPins)
{
foreach (SimPin target in output.ConnectedTargetPins)
{
SimChip targetChip = target.parentChip;
if (targetChip == null || targetChip.ID == chipID) continue;

// Add edge: chip -> targetChip
if (!graph[chipID].Contains(targetChip.ID))
{
graph[chipID].Add(targetChip.ID);

// Update in-degree for topological sort
if (!inDegree.ContainsKey(targetChip.ID)) inDegree[targetChip.ID] = 0;

inDegree[targetChip.ID]++;
}
}
}
}
// Run topo sort
Queue<int> zeroInDegree = new();
foreach (var kvp in inDegree)
{
if (kvp.Value == 0) zeroInDegree.Enqueue(kvp.Key);
}
int visitedCount = 0;
while (zeroInDegree.Count > 0)
{
int chipID = zeroInDegree.Dequeue();
visitedCount++;

if (!graph.ContainsKey(chipID)) continue;

foreach (int neighborID in graph[chipID])
{
inDegree[neighborID]--;
if (inDegree[neighborID] == 0) zeroInDegree.Enqueue(neighborID);
}
}

// If we couldn't visit all chips, a cycle exists
if (visitedCount != inDegree.Count) return false;

return true;
}

public int CalculateNumberOfInputBits()
{
int numberOfBits = 0;
foreach (SimPin pin in InputPins)
{
numberOfBits += (int)pin.numberOfBits;
}
return numberOfBits;
}

public void ResetReceivedFlagsOnAllPins()
{
foreach(SimPin pin in InputPins)
{
pin.numInputsReceivedThisFrame = 0;
}
foreach (SimPin pin in OutputPins)
{
pin.numInputsReceivedThisFrame = 0;
}
foreach (SimChip subChip in SubChips)
{
subChip.ResetReceivedFlagsOnAllPins();
}
}

public void UpdateInternalState(uint[] source) => Array.Copy(source, InternalState, InternalState.Length);


Expand Down Expand Up @@ -218,7 +350,7 @@ public void AddPin(SimPin pin, bool isInput)
}
}

static SimPin CreateSimPinFromDescription(PinDescription desc, bool isInput, SimChip parent) => new(desc.ID, isInput, parent);
static SimPin CreateSimPinFromDescription(PinDescription desc, bool isInput, SimChip parent) => new(desc.ID, isInput, desc.BitCount, parent);

public void RemovePin(int removePinID)
{
Expand Down
Loading