Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Reusing GraphManagerService.AddNewConnectionBetween during new node t…
…ype creation
  • Loading branch information
snakex64 committed Oct 9, 2024
commit ad925b4679133045b82bc2e1cfd6e3f8840f5b3a
96 changes: 37 additions & 59 deletions src/NodeDev.Blazor/Components/GraphCanvas.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ public partial class GraphCanvas : ComponentBase, IDisposable, IGraphCanvas
[Inject]
internal DebuggedPathService DebuggedPathService { get; set; } = null!;

private GraphManagerService? _GraphManagerService;
private GraphManagerService GraphManagerService => _GraphManagerService ??= new GraphManagerService(this);
private GraphManagerService? _GraphManagerService;
private GraphManagerService GraphManagerService => _GraphManagerService ??= new GraphManagerService(this);

private int PopupX = 0;
private int PopupX = 0;
private int PopupY = 0;
private Vector2 PopupNodePosition;
private Connection? PopupNodeConnection;
Expand Down Expand Up @@ -233,15 +233,15 @@ private void OnConnectionUpdated(BaseLinkModel baseLinkModel, Anchor old, Anchor
DisableConnectionUpdate = true;
var old = baseLinkModel.Source;
baseLinkModel.SetSource(baseLinkModel.Target); // this is necessary as everything assumes that the source is an output and vice versa
baseLinkModel.SetTarget(old);
baseLinkModel.SetTarget(old);
DisableConnectionUpdate = false;

var tmp = source;
source = destination;
destination = tmp;
}

GraphManagerService.AddNewConnectionBetween(source.Connection, destination.Connection);
GraphManagerService.AddNewConnectionBetween(source.Connection, destination.Connection);
});
}

Expand Down Expand Up @@ -271,12 +271,12 @@ public void OnConnectionAdded(BaseLinkModel baseLinkModel, bool force)
}
}

/// <summary>
/// Return the output connection except for execs, in that case we return the input connection.
/// This is because vertices are stored for the port, and execs conveniently only have one output connection while other types only have one input connection.
/// </summary>
/// <returns></returns>
private Connection GetConnectionContainingVertices(Connection source, Connection destination)
/// <summary>
/// Return the output connection except for execs, in that case we return the input connection.
/// This is because vertices are stored for the port, and execs conveniently only have one output connection while other types only have one input connection.
/// </summary>
/// <returns></returns>
private Connection GetConnectionContainingVertices(Connection source, Connection destination)
{
if (source.Type.IsExec) // execs can only have one connection, therefor they always contains the vertex information
return source;
Expand Down Expand Up @@ -391,11 +391,13 @@ private void OnNewNodeTypeSelected(NodeProvider.NodeSearchResult searchResult)

Diagram.Batch(() =>
{
CreateGraphNodeModel(node);

if (PopupNodeConnection != null && PopupNode != null)
{
// check if the source was an input or output and choose the proper destination based on that
List<Connection> sources, destinations;
bool isPopupNodeInput = PopupNode.Inputs.Contains(PopupNodeConnection);
bool isPopupNodeInput = PopupNodeConnection.IsInput;
if (isPopupNodeInput)
{
sources = PopupNode.Inputs;
Expand All @@ -416,38 +418,14 @@ private void OnNewNodeTypeSelected(NodeProvider.NodeSearchResult searchResult)
// if we found a connection, connect them together
if (destination != null)
{
Graph.Connect(PopupNodeConnection, destination, false);

if (destination.Connections.Count == 1 && destination.Type.AllowTextboxEdit)
UpdatePortTypeAndColor(destination);
if (PopupNodeConnection.Connections.Count == 1 && PopupNodeConnection.Type.AllowTextboxEdit)
UpdatePortTypeAndColor(PopupNodeConnection);

var source = isPopupNodeInput ? destination : PopupNodeConnection;
var ? PopupNodeConnection : destination;

// check if we need to propagate some generic
if (!destination.Type.IsExec && source.Type.IsAssignableTo(target.Type, out var changedGenerics))
{
GraphManagerService.PropagateNewGeneric(node, changedGenerics, false);
GraphManagerService.PropagateNewGeneric(destination.Parent, changedGenerics, false);
}
else if (source.Type.IsExec && source.Connections.Count > 1) // check if we have to disconnect the previously connected exec
{
Diagram.Links.Remove(Diagram.Links.First(x => (x.Source.Model as GraphPortModel)?.Connection == source && (x.Target.Model as GraphPortModel)?.Connection != target));
var toRemove = source.Connections.FirstOrDefault(x => x != target);
if (toRemove != null)
Graph.Disconnect(source, toRemove, false);
}
GraphManagerService.AddNewConnectionBetween(source, target);
}
}

CancelPopup();

CreateGraphNodeModel(node);
AddNodeLinks(node, false);

UpdateNodes(Graph.Nodes.Values.ToList());
});

}
Expand All @@ -471,9 +449,9 @@ private void OnNewOverloadSelected(Node.AlternateOverload overload)
if (PopupNode == null)
return;

GraphManagerService.SelectNodeOverload(PopupNode, overload);
GraphManagerService.SelectNodeOverload(PopupNode, overload);

CancelPopup();
CancelPopup();
}

#endregion
Expand Down Expand Up @@ -590,31 +568,31 @@ private void CancelPopup()
PopupNodeConnection = null;
}

#endregion
#endregion

#region RemoveLink
#region RemoveLink

public void RemoveLinkFromGraphCanvas(Connection source, Connection destination)
{
Graph.Invoke(() =>
{
DisableConnectionUpdate = true;
try
{
Diagram.Links.Remove(Diagram.Links.First(x => (x.Source.Model as GraphPortModel)?.Connection == source && (x.Target.Model as GraphPortModel)?.Connection == destination));
}
finally
{
DisableConnectionUpdate = false;
}
});
}
public void RemoveLinkFromGraphCanvas(Connection source, Connection destination)
{
Graph.Invoke(() =>
{
DisableConnectionUpdate = true;
try
{
Diagram.Links.Remove(Diagram.Links.First(x => (x.Source.Model as GraphPortModel)?.Connection == source && (x.Target.Model as GraphPortModel)?.Connection == destination));
}
finally
{
DisableConnectionUpdate = false;
}
});
}

#endregion
#endregion

#region CreateGraphNodeModel
#region CreateGraphNodeModel

private void CreateGraphNodeModel(Node node)
private void CreateGraphNodeModel(Node node)
{
var nodeModel = Diagram.Nodes.Add(new GraphNodeModel(node));
foreach (var connection in node.InputsAndOutputs)
Expand Down
157 changes: 74 additions & 83 deletions src/NodeDev.Blazor/Services/GraphManager/GraphManagerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,90 +11,81 @@ namespace NodeDev.Blazor.Services.GraphManager;
/// </summary>
public class GraphManagerService
{
private readonly IGraphCanvas GraphCanvas;

private Graph Graph => GraphCanvas.Graph;

public GraphManagerService(IGraphCanvas graphCanvas)
{
GraphCanvas = graphCanvas;
}

public void AddNewConnectionBetween(Connection source, Connection destination)
{
Graph.Connect(source, destination, false);

// we're plugging something something with a generic into something without a generic
if (source.Type.HasUndefinedGenerics && !destination.Type.HasUndefinedGenerics)
{
if (source.Type.IsAssignableTo(destination.Type, out var newTypes) && newTypes.Count != 0)
{
PropagateNewGeneric(source.Parent, newTypes, false);
}
}
else if (destination.Type.HasUndefinedGenerics && !source.Type.HasUndefinedGenerics)
{
if (source.Type.IsAssignableTo(destination.Type, out var newTypes) && newTypes.Count != 0)
{
PropagateNewGeneric(destination.Parent, newTypes, false);
}
}

GraphCanvas.UpdatePortTypeAndColor(source);
GraphCanvas.UpdatePortTypeAndColor(destination);

// we have to disconnect the previously connected exec, since exec outputs can only have one connection
if (source.Type.IsExec && source.Connections.Count > 1)
DisconnectConnectionBetween(source, source.Connections.First(x => x != destination));
else if (!destination.Type.IsExec && destination.Connections.Count > 1) // non-exec inputs can only have one connection
DisconnectConnectionBetween(destination.Connections.First(x => x != source), destination);

Graph.RaiseGraphChanged(true);
}

public void DisconnectConnectionBetween(Connection source, Connection destination)
{
Graph.Disconnect(source, destination, false);
GraphCanvas.RemoveLinkFromGraphCanvas(source, destination);

GraphCanvas.UpdatePortTypeAndColor(source);
GraphCanvas.UpdatePortTypeAndColor(destination);
}

public void PropagateNewGeneric(Node node, IReadOnlyDictionary<UndefinedGenericType, TypeBase> changedGenerics, bool requireUIRefresh)
{
foreach (var port in node.InputsAndOutputs) // check if any of the ports have the generic we just solved
{
var previousType = port.Type;

if (!previousType.GetUndefinedGenericTypes().Any(changedGenerics.ContainsKey))
continue;

// update port.Type property as well as the textbox visibility if necessary
port.UpdateTypeAndTextboxVisibility(previousType.ReplaceUndefinedGeneric(changedGenerics));
private readonly IGraphCanvas GraphCanvas;

private Graph Graph => GraphCanvas.Graph;

public GraphManagerService(IGraphCanvas graphCanvas)
{
GraphCanvas = graphCanvas;
}

public void AddNewConnectionBetween(Connection source, Connection destination)
{
Graph.Connect(source, destination, false);

// we're plugging something something with a generic into something without a generic
if (source.Type.IsAssignableTo(destination.Type, out var newTypes) && newTypes.Count != 0)
{
PropagateNewGeneric(source.Parent, newTypes, false);
PropagateNewGeneric(destination.Parent, newTypes, false);
}

GraphCanvas.UpdatePortTypeAndColor(source);
GraphCanvas.UpdatePortTypeAndColor(destination);

// we have to disconnect the previously connected exec, since exec outputs can only have one connection
if (source.Type.IsExec && source.Connections.Count > 1)
DisconnectConnectionBetween(source, source.Connections.First(x => x != destination));
else if (!destination.Type.IsExec && destination.Connections.Count > 1) // non-exec inputs can only have one connection
DisconnectConnectionBetween(destination.Connections.First(x => x != source), destination);

Graph.RaiseGraphChanged(true);
}

public void DisconnectConnectionBetween(Connection source, Connection destination)
{
Graph.Disconnect(source, destination, false);
GraphCanvas.RemoveLinkFromGraphCanvas(source, destination);

GraphCanvas.UpdatePortTypeAndColor(source);
GraphCanvas.UpdatePortTypeAndColor(destination);
}

public void PropagateNewGeneric(Node node, IReadOnlyDictionary<UndefinedGenericType, TypeBase> changedGenerics, bool requireUIRefresh)
{
foreach (var port in node.InputsAndOutputs) // check if any of the ports have the generic we just solved
{
var previousType = port.Type;

if (!previousType.GetUndefinedGenericTypes().Any(changedGenerics.ContainsKey))
continue;

// update port.Type property as well as the textbox visibility if necessary
port.UpdateTypeAndTextboxVisibility(previousType.ReplaceUndefinedGeneric(changedGenerics));
node.GenericConnectionTypeDefined(port);
GraphCanvas.UpdatePortTypeAndColor(port);

var isPortInput = port.IsInput; // cache for performance, IsInput is slow
// check if other connections had their own generics and if we just solved them
foreach (var other in port.Connections.ToList())
{
var source = isPortInput ? other : port;
var ? port : other;
if (source.Type.IsAssignableTo(target.Type, out var changedGenerics2) && changedGenerics2.Count != 0)
PropagateNewGeneric(other.Parent, changedGenerics2, false); // no need to refresh UI since we'll do it ourselves anyway at the end of this call
else if ((changedGenerics2?.Count ?? 0) != 0)// looks like changing the generic made it so we can't link to this connection anymore
DisconnectConnectionBetween(port, other);
}
}

Graph.RaiseGraphChanged(requireUIRefresh);
}

public void SelectNodeOverload(Node popupNode, Node.AlternateOverload overload)
{
popupNode.SelectOverload(overload, out var newConnections, out var removedConnections);

Graph.MergeRemovedConnectionsWithNewConnections(newConnections, removedConnections);
}
var isPortInput = port.IsInput; // cache for performance, IsInput is slow
// check if other connections had their own generics and if we just solved them
foreach (var other in port.Connections.ToList())
{
var source = isPortInput ? other : port;
var ? port : other;
if (source.Type.IsAssignableTo(target.Type, out var changedGenerics2) && changedGenerics2.Count != 0)
PropagateNewGeneric(other.Parent, changedGenerics2, false); // no need to refresh UI since we'll do it ourselves anyway at the end of this call
else if ((changedGenerics2?.Count ?? 0) != 0)// looks like changing the generic made it so we can't link to this connection anymore
DisconnectConnectionBetween(port, other);
}
}

Graph.RaiseGraphChanged(requireUIRefresh);
}

public void SelectNodeOverload(Node popupNode, Node.AlternateOverload overload)
{
popupNode.SelectOverload(overload, out var newConnections, out var removedConnections);

Graph.MergeRemovedConnectionsWithNewConnections(newConnections, removedConnections);
}
}
3 changes: 2 additions & 1 deletion src/NodeDev.Core/Nodes/New.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ public override IEnumerable<AlternateOverload> AlternatesOverloads
return [new(Outputs[1].Type, [])]; // for now, we don't handle custom constructors

else
throw new Exception("Unknowned type in New node: " + Outputs[1].Type.Name);
throw new Exception("Unknown type in New node: " + Outputs[1].Type.Name);
}
}

public override List<Connection> GenericConnectionTypeDefined(Connection connection)
{
if (Outputs[1].Type.IsArray)
Expand Down