Skip to content
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

Optimize Schematic.Deserialize, a lot #189

Merged
merged 11 commits into from
May 7, 2023
4 changes: 2 additions & 2 deletions Circuit/Schematic/Element.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ public event EventHandler LayoutChanged
public virtual bool Intersects(Coord x1, Coord x2)
{
Coord l = LowerBound;
if (l.x >= x2.x || l.y >= x2.y) return false;
if (l.x > x2.x || l.y > x2.y) return false;

Coord u = UpperBound;
if (u.x <= x1.x || u.y <= x1.y) return false;
if (u.x < x1.x || u.y < x1.y) return false;
return true;
}

Expand Down
179 changes: 112 additions & 67 deletions Circuit/Schematic/Schematic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,29 @@ public class Schematic
/// </summary>
public ILog Log { get { return log; } set { log = value; } }

public Schematic(ILog Log) : this() { log = Log; }
public Schematic()
public Schematic(ILog Log, IEnumerable<Element> Elements = null) : this(Elements)
{
log = Log;
}
public Schematic(IEnumerable<Element> Elements = null)
{
elements = new ElementCollection();
if (Elements != null)
{
// If we rely on the normal events for adding/removing these components,
// it's very slow because the nodes get rebuilt every time a new
// component is added. To avoid this, just manually connect everything
// here, and then rebuild the nodes once.
elements.AddRange(Elements);
foreach (Symbol i in elements.OfType<Symbol>())
circuit.Components.Add(i.Component);
RebuildNodes();
ReconnectAllTerminals();
}

// We skipped this usual event setup for the initial elements, add the events now.
foreach (Element i in elements)
i.LayoutChanged += OnLayoutChanged;
elements.ItemAdded += OnElementAdded;
elements.ItemRemoved += OnElementRemoved;
}
Expand All @@ -62,6 +81,9 @@ public Circuit Build(ILog log)

log.WriteLine(MessageType.Info, "Building circuit...");

RebuildNodes();
ReconnectAllTerminals();

// Check for duplicate names.
foreach (string i in circuit.Components.Select(i => i.Name))
{
Expand Down Expand Up @@ -136,25 +158,18 @@ public IEnumerable<Terminal> TerminalsAt(Coord x)
/// <param name="x"></param>
/// <param name="Self"></param>
/// <returns></returns>
public Node NodeAt(Coord x, Wire Self)
public Node NodeAt(Coord x)
{
IEnumerable<Wire> wires = Wires;
if (Self != null)
wires = wires.Except(Self);

Wire w = wires.FirstOrDefault(i => i.IsConnectedTo(x));
return w?.Node;
foreach (Element e in elements)
if (e is Wire w && w.IsConnectedTo(x))
return w.Node;
return null;
}
public Node NodeAt(Coord x) { return NodeAt(x, null); }

protected void OnElementAdded(object sender, ElementEventArgs e)
{
if (e.Element is Symbol symbol)
{
circuit.Components.Add(symbol.Component);
if (symbol.Component is NamedWire)
RebuildNodes(null, true);
}
OnLayoutChanged(e.Element, null);

e.Element.LayoutChanged += OnLayoutChanged;
Expand All @@ -166,15 +181,15 @@ protected void OnElementRemoved(object sender, ElementEventArgs e)

if (e.Element is Symbol symbol)
{
Component component = symbol.Component;
circuit.Components.Remove(component);
if (component is NamedWire wire)
RebuildNodes(wire.ConnectedTo, true);
circuit.Components.Remove(symbol.Component);
}
else if (e.Element is Wire wire)
{
// If the removed element is a wire, we might have to split the node it was a part of.
RebuildNodes(wire.Node, true);
RebuildNode(wire.Node);

// Reconnect any terminals connected to this wire's node, in case they are no longer connected.
ReconnectAllTerminals(wire.Node.Connected.Select(i => (Element)i.Owner.Tag).ToArray());
}

foreach (Terminal j in e.Element.Terminals)
Expand All @@ -193,49 +208,72 @@ protected void OnLayoutChanged(object sender, EventArgs e)
{
Element of = (Element)sender;
if (of is Wire wire)
RebuildNodes(wire.Node, true);
UpdateTerminals(of);
{
RebuildNode(wire.Node);

// Reconnect all the elements connected to this wire's node. This will disconnect any newly
// disconnected nodes.
ReconnectAllTerminals(wire.Node.Connected.Select(i => (Element)i.Owner.Tag).ToArray());

// Now reconnect all terminals in the bounding box of this wire.
ReconnectAllTerminals(Elements.Where(i => i.Intersects(wire.LowerBound, wire.UpperBound)));
}
else
{
foreach (Terminal i in of.Terminals)
{
Node n = NodeAt(of.MapTerminal(i));

// If this terminal is a named wire, the node needs to be rebuilt.
if (i.ConnectTo(n))
if (i.Owner is NamedWire && n != null)
RebuildNode(n);
}
}
}

// Wires was a single node but it is now two. Break it into two sets of nodes
// and return the one containing Target.
private IEnumerable<Wire> ConnectedTo(IEnumerable<Wire> Wires, Wire Target)
private IEnumerable<Wire> ConnectedTo(IEnumerable<Wire> Wires, Wire Target, HashSet<Wire> visited)
{
// Repeatedly search for connections with the target.
IEnumerable<Wire> connected = new Wire[] { Target };
int count = connected.Count();
while (true)
foreach (Wire i in Wires)
{
connected = Wires.Where(i => connected.Any(j => i.IsConnectedTo(j))).ToArray();
if (connected.Count() == count)
break;
count = connected.Count();
if (!visited.Contains(i) && i.IsConnectedTo(Target))
{
visited.Add(i);
yield return i;
foreach (Wire j in ConnectedTo(Wires, i, visited))
yield return j;
}
}
}

return connected;
// Wires was a single node but it is now two. Break it into two sets of nodes
// and return the one containing Target.
private IEnumerable<Wire> ConnectedTo(IEnumerable<Wire> Wires, Wire Target)
{
HashSet<Wire> visited = new HashSet<Wire>();
return ConnectedTo(Wires.Buffer(), Target, visited);
}

// Merge all of the nodes contained in wires to one.
private Node MergeNode(IEnumerable<Wire> Wires)
{
// All the existing nodes contained in wires.
List<Node> nodes = Wires.Select(i => i.Node).Distinct().ToList();
List<Node> nodes = Wires.Select(i => i.Node).Where(i => i != null).Distinct().ToList();

Node n = null;

// If this set of wires is connected to a NamedWire, use that as the node.
if (n == null)
{
foreach (NamedWire i in Symbols.Select(j => j.Component).OfType<NamedWire>())
{
if (Wires.Any(j => j.IsConnectedTo(((Symbol)i.Tag).MapTerminal(i.Terminal))))
n = circuit.Nodes[i.WireName];
}
foreach (Node i in nodes)
foreach (Terminal j in i.Connected)
if (j.Owner is NamedWire w)
n = circuit.Nodes[w.WireName];
}

// If there are no NamedWires, use one of the nodes already in the set.
if (n == null)
n = nodes.FirstOrDefault(i => i != null);
n = nodes.FirstOrDefault();

// If there were no nodes in the set, just make a new one.
if (n == null)
Expand All @@ -244,14 +282,15 @@ private Node MergeNode(IEnumerable<Wire> Wires)
circuit.Nodes.Add(n);
}

foreach (Wire i in Wires)
foreach (Wire i in Wires.Where(j => j.Node != n))
{
i.Node = n;
UpdateTerminals(i);
foreach (Terminal j in i.Terminals)
j.ConnectTo(n);
}

// Everything connected to a node in nodes should now be connected to n.
foreach (Node i in nodes.Where(j => j != null && j != n))
foreach (Node i in nodes.Where(j => j != n))
{
foreach (Terminal j in i.Connected.ToArray())
j.ConnectTo(n);
Expand All @@ -260,10 +299,31 @@ private Node MergeNode(IEnumerable<Wire> Wires)
return n;
}

private void RebuildNodes(Node At, bool MovedWire)
private void ReconnectAllTerminals(IEnumerable<Element> Elements)
{
HashSet<Node> modified = new HashSet<Node>();
foreach (Element i in Elements)
{
foreach (Terminal j in i.Terminals)
{
Node n = NodeAt(i.MapTerminal(j));
if (j.ConnectTo(n))
if (n != null && j.Owner is NamedWire)
modified.Add(n);
}
}
// Rebuild the nodes modified by a named wire case a named wire changed the name of the nodes.
if (modified.Any())
RebuildNodes(modified);
}
private void ReconnectAllTerminals() { ReconnectAllTerminals(elements); }

private void RebuildNodes(IEnumerable<Node> Nodes = null)
{
// If At is not null, only rebuild the wires that are part of that node.
IEnumerable<Wire> wires = At != null ? Wires.Where(i => i.Node == At).Buffer() : Wires;
HashSet<Wire> wires = new HashSet<Wire>();
foreach (Wire i in Wires)
if (Nodes == null || i.Node == null || Nodes.Contains(i.Node))
wires.Add(i);
while (!wires.Empty())
{
// Find all the wires connected to the first wire in the list.
Expand All @@ -273,32 +333,17 @@ private void RebuildNodes(Node At, bool MovedWire)
Node node = MergeNode(connected);

// Remove the wires we just made into a node from the list.
wires = wires.Except(connected).Buffer();
foreach (Wire i in connected)
wires.Remove(i);

// Any reminaing wires cannot be connected to the new node.
foreach (Wire i in wires.Where(j => j.Node == node))
i.Node = null;
}

if (MovedWire)
foreach (Element i in Elements)
UpdateTerminals(i);
}

private void UpdateTerminals(Element Of)
{
foreach (Terminal i in Of.Terminals)
Connect(i, NodeAt(Of.MapTerminal(i), Of as Wire));

// If Of is a named wire, the nodes might have changed.
if (Of is Symbol symbol && symbol.Component is NamedWire wire)
RebuildNodes(wire.ConnectedTo, false);
}

private void Connect(Terminal T, Node V)
private void RebuildNode(Node At)
{
if (V != T.ConnectedTo)
T.ConnectTo(V);
RebuildNodes(new Node[] { At });
}

private void LogComponents()
Expand Down Expand Up @@ -341,8 +386,8 @@ public XElement Serialize()

public static Schematic Deserialize(XElement X, ILog Log)
{
Schematic s = new Schematic(Log);
s.Elements.AddRange(X.Elements("Element").Select(i => Element.Deserialize(i)));
IEnumerable<Element> elements = X.Elements("Element").Select(i => Element.Deserialize(i));
Schematic s = new Schematic(Log, elements);
s.Circuit.Name = Value(X.Attribute("Name"));
s.Circuit.Description = Value(X.Attribute("Description"));
s.Circuit.PartNumber = Value(X.Attribute("PartNumber"));
Expand Down
6 changes: 4 additions & 2 deletions Circuit/Terminal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ public Node ConnectedTo
/// Connect this terminal to the node.
/// </summary>
/// <param name="n"></param>
public void ConnectTo(Node N)
/// <returns>true if the connection was changed, false if not.</returns>
public bool ConnectTo(Node N)
{
if (connectedTo == N)
return;
return false;

if (connectedTo != null)
connectedTo.Disconnect(this);
Expand All @@ -56,6 +57,7 @@ public void ConnectTo(Node N)
connectedTo.Connect(this);

foreach (EventHandler i in connectionChanged) i(this, null);
return true;
}

private List<EventHandler> connectionChanged = new List<EventHandler>();
Expand Down
7 changes: 5 additions & 2 deletions LiveSPICE/Controls/Editor/WireTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,11 @@ public override void MouseUp(Circuit.Coord At)
{
((PathGeometry)path.Data).Clear();
path.Visibility = Visibility.Hidden;
Editor.AddWire(Editor.FindWirePath(mouse));
mouse = null;
if (mouse != null)
{
Editor.AddWire(Editor.FindWirePath(mouse));
mouse = null;
}

if ((Keyboard.Modifiers & ModifierKeys.Control) == 0)
Target.Tool = new SelectionTool(Editor);
Expand Down