From 2bacf055708fbe36a19b03e1df54efde6d7b1985 Mon Sep 17 00:00:00 2001 From: snakex64 <39806655+snakex64@users.noreply.github.com> Date: Thu, 21 Nov 2024 19:00:09 -0500 Subject: [PATCH] Apply code preferences, removed unused namespace and use tabs everywhere (#57) --- .../Platforms/Windows/App.xaml.cs | 4 +- .../Components/DebuggerConsolePanel.razor.cs | 4 +- .../Components/GraphCanvas.razor.cs | 6 +- .../DiagramsModels/GraphNodeModel.cs | 57 +- .../DiagramsModels/GraphPortModel.cs | 38 +- .../SmoothPathGeneratorWithDirectVertices.cs | 4 +- .../NodeDecorations/NodeDecorationPosition.cs | 9 +- .../Services/DebuggedPathService.cs | 5 +- .../GraphManager/GraphManagerService.cs | 186 ++-- .../Services/ServicesExtension.cs | 5 - src/NodeDev.Blazor/Utility.cs | 9 +- src/NodeDev.Core/BuildError.cs | 10 +- src/NodeDev.Core/BuildOptions.cs | 4 +- src/NodeDev.Core/Class/NodeClass.cs | 9 +- src/NodeDev.Core/Class/NodeClassMethod.cs | 5 - .../Class/NodeClassMethodParameter.cs | 276 +++-- src/NodeDev.Core/Class/NodeClassProperty.cs | 4 +- .../Class/NodeClassTypeCreator.cs | 13 +- src/NodeDev.Core/Connections/Connection.cs | 350 ++++--- src/NodeDev.Core/Graph.cs | 953 +++++++++--------- src/NodeDev.Core/GraphExecutor.cs | 8 +- src/NodeDev.Core/Migrations/MigrationBase.cs | 14 +- .../Migration_1_0_1_class_as_raw_json.cs | 136 +-- .../NodeDecorations/NodeDecoration.cs | 9 +- src/NodeDev.Core/NodePaths.cs | 2 +- src/NodeDev.Core/NodeProvider.cs | 246 +++-- src/NodeDev.Core/Nodes/ArrayGet.cs | 8 +- src/NodeDev.Core/Nodes/ArraySet.cs | 11 +- src/NodeDev.Core/Nodes/BuildExpressionInfo.cs | 1 - .../Nodes/BuildExpressionOptions.cs | 4 +- src/NodeDev.Core/Nodes/DeclareVariableNode.cs | 28 +- src/NodeDev.Core/Nodes/Flow/Branch.cs | 6 +- src/NodeDev.Core/Nodes/Flow/EntryNode.cs | 4 +- src/NodeDev.Core/Nodes/Flow/FlowNode.cs | 8 +- src/NodeDev.Core/Nodes/Flow/ForNode.cs | 93 +- src/NodeDev.Core/Nodes/Flow/ForeachNode.cs | 2 +- src/NodeDev.Core/Nodes/Flow/WhileNode.cs | 22 +- src/NodeDev.Core/Nodes/GetPropertyOrField.cs | 2 +- src/NodeDev.Core/Nodes/Math/Add.cs | 3 +- src/NodeDev.Core/Nodes/Math/And.cs | 3 +- src/NodeDev.Core/Nodes/Math/BiggerThan.cs | 3 +- .../Nodes/Math/BiggerThanOrEqual.cs | 3 +- .../Nodes/Math/BinaryOperationMath.cs | 7 +- src/NodeDev.Core/Nodes/Math/Divide.cs | 3 +- src/NodeDev.Core/Nodes/Math/Equals.cs | 3 +- src/NodeDev.Core/Nodes/Math/IsNotNull.cs | 3 +- src/NodeDev.Core/Nodes/Math/IsNull.cs | 3 +- src/NodeDev.Core/Nodes/Math/Modulo.cs | 3 +- src/NodeDev.Core/Nodes/Math/Multiply.cs | 3 +- src/NodeDev.Core/Nodes/Math/Not.cs | 3 +- src/NodeDev.Core/Nodes/Math/NotEquals.cs | 3 +- src/NodeDev.Core/Nodes/Math/Or.cs | 3 +- src/NodeDev.Core/Nodes/Math/SmallerThan.cs | 3 +- .../Nodes/Math/SmallerThanOrEqual.cs | 3 +- src/NodeDev.Core/Nodes/Math/Subtract.cs | 3 +- .../Nodes/Math/TwoOperationMath.cs | 16 +- src/NodeDev.Core/Nodes/Math/Xor.cs | 3 +- src/NodeDev.Core/Nodes/MethodCall.cs | 9 +- src/NodeDev.Core/Nodes/New.cs | 133 ++- src/NodeDev.Core/Nodes/NoFlowNode.cs | 21 +- src/NodeDev.Core/Nodes/Node.cs | 24 +- src/NodeDev.Core/Nodes/NormalFlowNode.cs | 39 +- src/NodeDev.Core/Nodes/Null.cs | 3 +- src/NodeDev.Core/Nodes/Self.cs | 5 +- src/NodeDev.Core/Nodes/SetPropertyOrField.cs | 3 +- .../Nodes/SetVariableValueNode.cs | 22 +- src/NodeDev.Core/Nodes/TypeOf.cs | 2 +- src/NodeDev.Core/Project.cs | 2 +- src/NodeDev.Core/ProjectSettings.cs | 2 +- src/NodeDev.Core/Types/ExecType.cs | 20 +- src/NodeDev.Core/Types/IMethodInfo.cs | 37 +- src/NodeDev.Core/Types/NodeClassArrayType.cs | 160 ++- src/NodeDev.Core/Types/NodeClassType.cs | 119 ++- src/NodeDev.Core/Types/RealMethodInfo.cs | 2 +- .../Types/RealMethodParameterInfo.cs | 8 +- src/NodeDev.Core/Types/RealType.cs | 35 +- src/NodeDev.Core/Types/TypeBase.cs | 7 +- src/NodeDev.Core/Types/TypeFactory.cs | 18 +- .../Types/UndefinedGenericType.cs | 112 +- src/NodeDev.EndToEndTests/HelperExtensions.cs | 8 +- src/NodeDev.EndToEndTests/Hooks/Hooks.cs | 241 +++-- src/NodeDev.EndToEndTests/ImplicitUsings.cs | 2 - src/NodeDev.EndToEndTests/Pages/HomePage.cs | 114 +-- .../MainPageStepDefinitions.cs | 80 +- src/NodeDev.Tests/EventsTests.cs | 1 - src/NodeDev.Tests/GraphAnalysisTests.cs | 150 +-- src/NodeDev.Tests/GraphExecutorTests.cs | 70 +- src/NodeDev.Tests/GraphManagerServiceTests.cs | 445 ++++---- .../NodeClassTypeCreatorTests.cs | 1 - src/NodeDev.Tests/NodeDevTestsBase.cs | 36 +- src/NodeDev.Tests/NodeProviderTests.cs | 8 +- src/NodeDev.Tests/RealTypeTests.cs | 3 +- src/NodeDev.Tests/SerializableBuildOptions.cs | 36 +- src/NodeDev.Tests/SerializationTests.cs | 8 +- src/NodeDev.Tests/TypeBaseTests.cs | 111 +- src/NodeDev.Tests/TypeFactoryTests.cs | 1 - .../UndefinedGenericTypeTests.cs | 4 +- 97 files changed, 2274 insertions(+), 2457 deletions(-) diff --git a/src/NodeDev.Blazor.MAUI/Platforms/Windows/App.xaml.cs b/src/NodeDev.Blazor.MAUI/Platforms/Windows/App.xaml.cs index ef0adf7..dca6d66 100644 --- a/src/NodeDev.Blazor.MAUI/Platforms/Windows/App.xaml.cs +++ b/src/NodeDev.Blazor.MAUI/Platforms/Windows/App.xaml.cs @@ -1,6 +1,4 @@ -using Microsoft.UI.Xaml; - -// To learn more about WinUI, the WinUI project structure, +// To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. namespace NodeDev.Blazor.MAUI.WinUI diff --git a/src/NodeDev.Blazor/Components/DebuggerConsolePanel.razor.cs b/src/NodeDev.Blazor/Components/DebuggerConsolePanel.razor.cs index 652c685..b07f286 100644 --- a/src/NodeDev.Blazor/Components/DebuggerConsolePanel.razor.cs +++ b/src/NodeDev.Blazor/Components/DebuggerConsolePanel.razor.cs @@ -1,8 +1,6 @@ using Microsoft.AspNetCore.Components; using NodeDev.Core; -using System.Diagnostics; using System.Reactive.Subjects; -using System.Text; namespace NodeDev.Blazor.Components; @@ -29,7 +27,7 @@ protected override void OnInitialized() base.OnInitialized(); RefreshRequiredDisposable = RefreshRequiredSubject.AcceptThenSample(TimeSpan.FromMilliseconds(100)).Subscribe(_ => InvokeAsync(StateHasChanged)); - + GraphExecutionChangedDisposable = Project.GraphExecutionChanged.Subscribe(OnGraphExecutionChanged); } diff --git a/src/NodeDev.Blazor/Components/GraphCanvas.razor.cs b/src/NodeDev.Blazor/Components/GraphCanvas.razor.cs index d2e8ee5..26e6210 100644 --- a/src/NodeDev.Blazor/Components/GraphCanvas.razor.cs +++ b/src/NodeDev.Blazor/Components/GraphCanvas.razor.cs @@ -7,15 +7,15 @@ using Microsoft.AspNetCore.Components; using NodeDev.Blazor.DiagramsModels; using NodeDev.Blazor.NodeAttributes; +using NodeDev.Blazor.Services; +using NodeDev.Blazor.Services.GraphManager; using NodeDev.Core; +using NodeDev.Core.Class; using NodeDev.Core.Connections; using NodeDev.Core.Nodes; using NodeDev.Core.Types; using System.Numerics; using System.Reactive.Linq; -using NodeDev.Core.Class; -using NodeDev.Blazor.Services; -using NodeDev.Blazor.Services.GraphManager; namespace NodeDev.Blazor.Components; diff --git a/src/NodeDev.Blazor/DiagramsModels/GraphNodeModel.cs b/src/NodeDev.Blazor/DiagramsModels/GraphNodeModel.cs index 5ccab66..c62e750 100644 --- a/src/NodeDev.Blazor/DiagramsModels/GraphNodeModel.cs +++ b/src/NodeDev.Blazor/DiagramsModels/GraphNodeModel.cs @@ -2,18 +2,13 @@ using NodeDev.Blazor.NodeAttributes; using NodeDev.Core.Connections; using NodeDev.Core.Nodes; -using System; -using System.Collections.Generic; -using System.Linq; using System.Numerics; -using System.Text; -using System.Threading.Tasks; namespace NodeDev.Blazor.DiagramsModels { - public class GraphNodeModel : NodeModel - { - internal readonly Node Node; + public class GraphNodeModel : NodeModel + { + internal readonly Node Node; /// /// Set to true by the canvas when the user hit "f2" to edit the node name. @@ -23,18 +18,18 @@ public class GraphNodeModel : NodeModel public GraphNodeModel(Node node) : base(new(node.GetOrAddDecoration(() => new(Vector2.Zero)).X, node.GetOrAddDecoration(() => new(Vector2.Zero)).Y)) - { - Node = node; - } + { + Node = node; + } - public GraphPortModel GetPort(Connection connection) => Ports.OfType().First( x=> x.Connection == connection); + public GraphPortModel GetPort(Connection connection) => Ports.OfType().First(x => x.Connection == connection); internal void OnNodeExecuted(Connection exec) { } - internal void OnConnectionPathHighlighted(Connection connection) + internal void OnConnectionPathHighlighted(Connection connection) { var port = GetPort(connection); @@ -47,7 +42,7 @@ internal void OnConnectionPathHighlighted(Connection connection) } } - internal void OnConnectionPathUnhighlighted(Connection connection) + internal void OnConnectionPathUnhighlighted(Connection connection) { var port = GetPort(connection); @@ -60,23 +55,23 @@ internal void OnConnectionPathUnhighlighted(Connection connection) internal async Task OnNodeExecuting(Connection exec) { - var port = GetPort(exec); - - foreach (var link in port.Links.OfType()) - { - if(!link.Classes.Contains("executing")) - link.Classes += " executing"; - - link.Refresh(); - } - - var currentCount = ++port.ExecutionCount; - await Task.Delay(100); - if (currentCount == port.ExecutionCount) - { - foreach (var link in port.Links.OfType()) - { - link.Classes = link.Classes.Replace(" executing", ""); + var port = GetPort(exec); + + foreach (var link in port.Links.OfType()) + { + if (!link.Classes.Contains("executing")) + link.Classes += " executing"; + + link.Refresh(); + } + + var currentCount = ++port.ExecutionCount; + await Task.Delay(100); + if (currentCount == port.ExecutionCount) + { + foreach (var link in port.Links.OfType()) + { + link.Classes = link.Classes.Replace(" executing", ""); link.Refresh(); } } diff --git a/src/NodeDev.Blazor/DiagramsModels/GraphPortModel.cs b/src/NodeDev.Blazor/DiagramsModels/GraphPortModel.cs index 57681a4..a1f2d45 100644 --- a/src/NodeDev.Blazor/DiagramsModels/GraphPortModel.cs +++ b/src/NodeDev.Blazor/DiagramsModels/GraphPortModel.cs @@ -5,30 +5,30 @@ namespace NodeDev.Blazor.DiagramsModels; -public class GraphPortModel: PortModel +public class GraphPortModel : PortModel { - internal readonly Connection Connection; + internal readonly Connection Connection; - internal int ExecutionCount = 0; + internal int ExecutionCount = 0; - internal string PortColor => GraphCanvas.GetTypeShapeColor(Connection.Type, Connection.Parent.TypeFactory); + internal string PortColor => GraphCanvas.GetTypeShapeColor(Connection.Type, Connection.Parent.TypeFactory); - public GraphPortModel(GraphNodeModel parent, Connection connection, bool isInput) : base(parent, isInput ? PortAlignment.Left : PortAlignment.Right) - { - Connection = connection; - } + public GraphPortModel(GraphNodeModel parent, Connection connection, bool isInput) : base(parent, isInput ? PortAlignment.Left : PortAlignment.Right) + { + Connection = connection; + } - public override bool CanAttachTo(ILinkable other) - { - if(!base.CanAttachTo(other)) - return false; + public override bool CanAttachTo(ILinkable other) + { + if (!base.CanAttachTo(other)) + return false; - if (other is not GraphPortModel otherPort) - return false; - - if(Alignment == otherPort.Alignment) // can't plug input to input or output to output - return false; + if (other is not GraphPortModel otherPort) + return false; - return Connection.Type.IsAssignableTo(otherPort.Connection.Type, out _, out _); - } + if (Alignment == otherPort.Alignment) // can't plug input to input or output to output + return false; + + return Connection.Type.IsAssignableTo(otherPort.Connection.Type, out _, out _); + } } diff --git a/src/NodeDev.Blazor/DiagramsModels/SmoothPathGeneratorWithDirectVertices.cs b/src/NodeDev.Blazor/DiagramsModels/SmoothPathGeneratorWithDirectVertices.cs index 82a1710..2d406f2 100644 --- a/src/NodeDev.Blazor/DiagramsModels/SmoothPathGeneratorWithDirectVertices.cs +++ b/src/NodeDev.Blazor/DiagramsModels/SmoothPathGeneratorWithDirectVertices.cs @@ -93,9 +93,9 @@ private Point GetCurvePoint(Anchor anchor, double pX, double pY, double cX, doub if (anchor is SinglePortAnchor spa) { PortAlignment portAlignment; - if((isFirstInRoute && first) || (isLastInRoute && !first)) + if ((isFirstInRoute && first) || (isLastInRoute && !first)) portAlignment = spa.Port.Alignment; - else if(pX < cX) + else if (pX < cX) portAlignment = PortAlignment.Right; else portAlignment = PortAlignment.Left; diff --git a/src/NodeDev.Blazor/NodeDecorations/NodeDecorationPosition.cs b/src/NodeDev.Blazor/NodeDecorations/NodeDecorationPosition.cs index cf281b9..b58ac1e 100644 --- a/src/NodeDev.Blazor/NodeDecorations/NodeDecorationPosition.cs +++ b/src/NodeDev.Blazor/NodeDecorations/NodeDecorationPosition.cs @@ -1,12 +1,7 @@ using NodeDev.Core.NodeDecorations; using NodeDev.Core.Types; -using System; -using System.Collections.Generic; -using System.Linq; using System.Numerics; -using System.Text; using System.Text.Json; -using System.Threading.Tasks; namespace NodeDev.Blazor.NodeAttributes { @@ -24,7 +19,7 @@ public NodeDecorationPosition(Vector2 position) private record class SerializedNodeDecoration(float X, float Y); - + public string Serialize() { return JsonSerializer.Serialize(new SerializedNodeDecoration(X, Y)); @@ -36,5 +31,5 @@ public static INodeDecoration Deserialize(TypeFactory typeFactory, string Json) return new NodeDecorationPosition(new(serializedNodeDecoration.X, serializedNodeDecoration.Y)); } - } + } } diff --git a/src/NodeDev.Blazor/Services/DebuggedPathService.cs b/src/NodeDev.Blazor/Services/DebuggedPathService.cs index da4d92d..838d423 100644 --- a/src/NodeDev.Blazor/Services/DebuggedPathService.cs +++ b/src/NodeDev.Blazor/Services/DebuggedPathService.cs @@ -1,9 +1,6 @@ - -using Microsoft.AspNetCore.Components; -using NodeDev.Core; +using NodeDev.Core; using NodeDev.Core.Connections; using NodeDev.Core.Nodes; -using static MudBlazor.Defaults; namespace NodeDev.Blazor.Services; diff --git a/src/NodeDev.Blazor/Services/GraphManager/GraphManagerService.cs b/src/NodeDev.Blazor/Services/GraphManager/GraphManagerService.cs index 9422263..d97b8de 100644 --- a/src/NodeDev.Blazor/Services/GraphManager/GraphManagerService.cs +++ b/src/NodeDev.Blazor/Services/GraphManager/GraphManagerService.cs @@ -10,99 +10,99 @@ namespace NodeDev.Blazor.Services.GraphManager; /// 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.IsAssignableTo(destination, true, true, out var newTypesLeft, out var newTypesRight, out var usedInitialTypes)) - { - if (newTypesLeft.Count != 0) - PropagateNewGeneric(source.Parent, newTypesLeft, usedInitialTypes, destination, false); - if (newTypesRight.Count != 0) - PropagateNewGeneric(destination.Parent, newTypesRight, usedInitialTypes, source, false); - } - - GraphCanvas.UpdatePortColor(source); - GraphCanvas.UpdatePortColor(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.UpdatePortColor(source); - GraphCanvas.UpdatePortColor(destination); - } - - /// - /// Propagate the new generic type to all the connections of the node and recursively to the connected nodes. - /// - /// The connection that initiated the propagation. This is used to avoid reupdating back and forth, sometimes erasing information in the process. - public void PropagateNewGeneric(Node node, IReadOnlyDictionary changedGenerics, bool useInitialTypes, Connection? initiatingConnection, bool overrideInitialTypes) - { + 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.IsAssignableTo(destination, true, true, out var newTypesLeft, out var newTypesRight, out var usedInitialTypes)) + { + if (newTypesLeft.Count != 0) + PropagateNewGeneric(source.Parent, newTypesLeft, usedInitialTypes, destination, false); + if (newTypesRight.Count != 0) + PropagateNewGeneric(destination.Parent, newTypesRight, usedInitialTypes, source, false); + } + + GraphCanvas.UpdatePortColor(source); + GraphCanvas.UpdatePortColor(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.UpdatePortColor(source); + GraphCanvas.UpdatePortColor(destination); + } + + /// + /// Propagate the new generic type to all the connections of the node and recursively to the connected nodes. + /// + /// The connection that initiated the propagation. This is used to avoid reupdating back and forth, sometimes erasing information in the process. + public void PropagateNewGeneric(Node node, IReadOnlyDictionary changedGenerics, bool useInitialTypes, Connection? initiatingConnection, bool overrideInitialTypes) + { node.OnBeforeGenericTypeDefined(changedGenerics); - bool hadAnyChanges = false; - foreach (var port in node.InputsAndOutputs) // check if any of the ports have the generic we just solved - { - var previousType = useInitialTypes ? port.InitialType : 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), overrideInitialType: overrideInitialTypes); - hadAnyChanges |= node.GenericConnectionTypeDefined(port).Count != 0; - GraphCanvas.UpdatePortColor(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()) - { - if (other == initiatingConnection) - continue; - - var source = isPortInput ? other : port; - var target = isPortInput ? port : other; - if (source.IsAssignableTo(target, isPortInput, !isPortInput, out var changedGenericsLeft2, out var changedGenericsRight2, out var usedInitialTypes) && (changedGenericsLeft2.Count != 0 || changedGenericsRight2.Count != 0)) - { - if (changedGenericsLeft2.Count != 0) - PropagateNewGeneric(port.Parent, changedGenericsLeft2, usedInitialTypes, other, false); - if (changedGenericsRight2.Count != 0) - PropagateNewGeneric(other.Parent, changedGenericsRight2, usedInitialTypes, port, false); - } - else if ((changedGenericsLeft2?.Count ?? 0) != 0)// looks like changing the generic made it so we can't link to this connection anymore - DisconnectConnectionBetween(port, other); - } - } - - if (hadAnyChanges) - Graph.RaiseGraphChanged(false); - } - - public void SelectNodeOverload(Node popupNode, Node.AlternateOverload overload) - { - popupNode.SelectOverload(overload, out var newConnections, out var removedConnections); - - Graph.MergeRemovedConnectionsWithNewConnections(newConnections, removedConnections); - } + bool hadAnyChanges = false; + foreach (var port in node.InputsAndOutputs) // check if any of the ports have the generic we just solved + { + var previousType = useInitialTypes ? port.InitialType : 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), overrideInitialType: overrideInitialTypes); + hadAnyChanges |= node.GenericConnectionTypeDefined(port).Count != 0; + GraphCanvas.UpdatePortColor(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()) + { + if (other == initiatingConnection) + continue; + + var source = isPortInput ? other : port; + var target = isPortInput ? port : other; + if (source.IsAssignableTo(target, isPortInput, !isPortInput, out var changedGenericsLeft2, out var changedGenericsRight2, out var usedInitialTypes) && (changedGenericsLeft2.Count != 0 || changedGenericsRight2.Count != 0)) + { + if (changedGenericsLeft2.Count != 0) + PropagateNewGeneric(port.Parent, changedGenericsLeft2, usedInitialTypes, other, false); + if (changedGenericsRight2.Count != 0) + PropagateNewGeneric(other.Parent, changedGenericsRight2, usedInitialTypes, port, false); + } + else if ((changedGenericsLeft2?.Count ?? 0) != 0)// looks like changing the generic made it so we can't link to this connection anymore + DisconnectConnectionBetween(port, other); + } + } + + if (hadAnyChanges) + Graph.RaiseGraphChanged(false); + } + + public void SelectNodeOverload(Node popupNode, Node.AlternateOverload overload) + { + popupNode.SelectOverload(overload, out var newConnections, out var removedConnections); + + Graph.MergeRemovedConnectionsWithNewConnections(newConnections, removedConnections); + } } diff --git a/src/NodeDev.Blazor/Services/ServicesExtension.cs b/src/NodeDev.Blazor/Services/ServicesExtension.cs index ed460ca..407d087 100644 --- a/src/NodeDev.Blazor/Services/ServicesExtension.cs +++ b/src/NodeDev.Blazor/Services/ServicesExtension.cs @@ -1,10 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using MudBlazor.Services; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NodeDev.Blazor.Services { diff --git a/src/NodeDev.Blazor/Utility.cs b/src/NodeDev.Blazor/Utility.cs index 20db2af..d575dcf 100644 --- a/src/NodeDev.Blazor/Utility.cs +++ b/src/NodeDev.Blazor/Utility.cs @@ -1,13 +1,6 @@ -using NodeDev.Core; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reactive; +using System.Diagnostics; using System.Reactive.Disposables; using System.Reactive.Linq; -using System.Text; -using System.Threading.Tasks; namespace NodeDev.Blazor; diff --git a/src/NodeDev.Core/BuildError.cs b/src/NodeDev.Core/BuildError.cs index d1ced45..859e43f 100644 --- a/src/NodeDev.Core/BuildError.cs +++ b/src/NodeDev.Core/BuildError.cs @@ -4,10 +4,10 @@ namespace NodeDev.Core; public class BuildError : Exception { - public readonly Node Node; + public readonly Node Node; - public BuildError(string message, Node node, Exception? inner) : base(message, inner) - { - Node = node; - } + public BuildError(string message, Node node, Exception? inner) : base(message, inner) + { + Node = node; + } } diff --git a/src/NodeDev.Core/BuildOptions.cs b/src/NodeDev.Core/BuildOptions.cs index dffc34c..f0b6941 100644 --- a/src/NodeDev.Core/BuildOptions.cs +++ b/src/NodeDev.Core/BuildOptions.cs @@ -4,7 +4,7 @@ namespace NodeDev.Core; public record class BuildOptions(BuildExpressionOptions BuildExpressionOptions, bool PreBuildOnly, string OutputPath) { - public static readonly BuildOptions Debug = new(BuildExpressionOptions.Debug, false, "bin/Debug"); + public static readonly BuildOptions Debug = new(BuildExpressionOptions.Debug, false, "bin/Debug"); - public static readonly BuildOptions Release = new(BuildExpressionOptions.Release, false, "bin/Release"); + public static readonly BuildOptions Release = new(BuildExpressionOptions.Release, false, "bin/Release"); } diff --git a/src/NodeDev.Core/Class/NodeClass.cs b/src/NodeDev.Core/Class/NodeClass.cs index 280c8a1..61e0ccc 100644 --- a/src/NodeDev.Core/Class/NodeClass.cs +++ b/src/NodeDev.Core/Class/NodeClass.cs @@ -1,9 +1,4 @@ using NodeDev.Core.Types; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NodeDev.Core.Class { @@ -49,13 +44,13 @@ internal void Deserialize_Step2(SerializedNodeClass serializedNodeClass) Methods.Add(NodeClassMethod.Deserialize(this, method)); } - internal void Deserialize_Step3() + internal void Deserialize_Step3() { foreach (var method in Methods) method.Deserialize_Step3(); } - internal SerializedNodeClass Serialize() + internal SerializedNodeClass Serialize() { var serializedNodeClass = new SerializedNodeClass(Name, Namespace, Methods.Select(x => x.Serialize()).ToList(), Properties.Select(x => x.Serialize()).ToList()); diff --git a/src/NodeDev.Core/Class/NodeClassMethod.cs b/src/NodeDev.Core/Class/NodeClassMethod.cs index 8a6b76c..dea43ef 100644 --- a/src/NodeDev.Core/Class/NodeClassMethod.cs +++ b/src/NodeDev.Core/Class/NodeClassMethod.cs @@ -1,12 +1,7 @@ using NodeDev.Core.Nodes; using NodeDev.Core.Nodes.Flow; using NodeDev.Core.Types; -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace NodeDev.Core.Class { diff --git a/src/NodeDev.Core/Class/NodeClassMethodParameter.cs b/src/NodeDev.Core/Class/NodeClassMethodParameter.cs index d0208a4..cb7c6d7 100644 --- a/src/NodeDev.Core/Class/NodeClassMethodParameter.cs +++ b/src/NodeDev.Core/Class/NodeClassMethodParameter.cs @@ -1,147 +1,141 @@ -using NodeDev.Core.Connections; -using NodeDev.Core.Nodes; +using NodeDev.Core.Nodes; using NodeDev.Core.Nodes.Flow; using NodeDev.Core.Types; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NodeDev.Core.Class { - public class NodeClassMethodParameter : IMethodParameterInfo - { - internal record class SerializedNodeClassMethodParameter(string Name, TypeBase.SerializedType ParameterType, bool? IsOut); - - public string Name { get; private set; } - - public TypeBase ParameterType { get; private set; } - - public NodeClassMethod Method { get; } - - public bool IsOut { get; set; } - - public NodeClassMethodParameter(string name, TypeBase parameterType, NodeClassMethod method) - { - Name = name; - ParameterType = parameterType; - Method = method; - } - - internal SerializedNodeClassMethodParameter Serialize() - { - return new SerializedNodeClassMethodParameter(Name, ParameterType.SerializeWithFullTypeName(), IsOut); - } - - internal static NodeClassMethodParameter Deserialize(TypeFactory typeFactory, SerializedNodeClassMethodParameter serializedNodeClassMethodParameter, NodeClassMethod nodeClassMethod) - { - return new NodeClassMethodParameter(serializedNodeClassMethodParameter.Name, TypeBase.Deserialize(typeFactory, serializedNodeClassMethodParameter.ParameterType), nodeClassMethod) - { - IsOut = serializedNodeClassMethodParameter.IsOut ?? false - }; - } - - #region Actions from UI - - public void SetIsOut(bool value) - { - if (value == IsOut) - return; - - IsOut = value; - - RefreshAllMethodCalls(); - RefreshEntryAndReturnNodes(); - } - - private void RefreshEntryAndReturnNodes() - { - var entry = Method.Graph.Nodes.Values.OfType().First(); - var returnNodes = Method.Graph.Nodes.Values.OfType().ToList(); - - entry.Refresh(); - foreach (var returnNode in returnNodes) - returnNode.Refresh(); - } - - private void RefreshAllMethodCalls() - { - foreach (var methodCall in Method.Class.Project.GetNodes()) - { - if (methodCall.TargetMethod == Method) - { - methodCall.SelectOverload(((IMethodInfo)Method).AlternateOverload(), out var newConnections, out var removedConnections); - - methodCall.Graph.MergeRemovedConnectionsWithNewConnections(newConnections, removedConnections); - } - } - } - - public void MoveUp() - { - var index = Method.Parameters.IndexOf(this); - if (index <= 0) - return; - - SwapParameter(index, index - 1); - } - - public void MoveDown() - { - var index = Method.Parameters.IndexOf(this); - if (index == Method.Parameters.Count - 1 || index == -1) - return; - - SwapParameter(index, index + 1); - } - - public void Remove() - { - var index = Method.Parameters.IndexOf(this); - if (index == -1) - return; - - Method.Parameters.RemoveAt(index); - - RefreshAllMethodCalls(); - RefreshEntryAndReturnNodes(); - } - - private void SwapParameter(int index, int newIndex) - { - var previous = Method.Parameters[newIndex]; - Method.Parameters[newIndex] = this; - Method.Parameters[index] = previous; - - RefreshAllMethodCalls(); - RefreshEntryAndReturnNodes(); - } - - public void Rename(string name) - { - var oldName = Name; - Name = name; - - foreach (var methodCall in Method.Graph.Project.GetNodes()) - { - if (methodCall.TargetMethod == Method) - methodCall.OnMethodParameterRenamed(oldName, this); - } - - var entry = Method.Graph.Nodes.Values.OfType().FirstOrDefault(); - if (entry != null) - entry.RenameParameter(this, Method.Parameters.IndexOf(this)); - } - - public void ChangeType(TypeBase type) - { - ParameterType = type; - - RefreshAllMethodCalls(); - RefreshEntryAndReturnNodes(); - } - - #endregion - } + public class NodeClassMethodParameter : IMethodParameterInfo + { + internal record class SerializedNodeClassMethodParameter(string Name, TypeBase.SerializedType ParameterType, bool? IsOut); + + public string Name { get; private set; } + + public TypeBase ParameterType { get; private set; } + + public NodeClassMethod Method { get; } + + public bool IsOut { get; set; } + + public NodeClassMethodParameter(string name, TypeBase parameterType, NodeClassMethod method) + { + Name = name; + ParameterType = parameterType; + Method = method; + } + + internal SerializedNodeClassMethodParameter Serialize() + { + return new SerializedNodeClassMethodParameter(Name, ParameterType.SerializeWithFullTypeName(), IsOut); + } + + internal static NodeClassMethodParameter Deserialize(TypeFactory typeFactory, SerializedNodeClassMethodParameter serializedNodeClassMethodParameter, NodeClassMethod nodeClassMethod) + { + return new NodeClassMethodParameter(serializedNodeClassMethodParameter.Name, TypeBase.Deserialize(typeFactory, serializedNodeClassMethodParameter.ParameterType), nodeClassMethod) + { + IsOut = serializedNodeClassMethodParameter.IsOut ?? false + }; + } + + #region Actions from UI + + public void SetIsOut(bool value) + { + if (value == IsOut) + return; + + IsOut = value; + + RefreshAllMethodCalls(); + RefreshEntryAndReturnNodes(); + } + + private void RefreshEntryAndReturnNodes() + { + var entry = Method.Graph.Nodes.Values.OfType().First(); + var returnNodes = Method.Graph.Nodes.Values.OfType().ToList(); + + entry.Refresh(); + foreach (var returnNode in returnNodes) + returnNode.Refresh(); + } + + private void RefreshAllMethodCalls() + { + foreach (var methodCall in Method.Class.Project.GetNodes()) + { + if (methodCall.TargetMethod == Method) + { + methodCall.SelectOverload(((IMethodInfo)Method).AlternateOverload(), out var newConnections, out var removedConnections); + + methodCall.Graph.MergeRemovedConnectionsWithNewConnections(newConnections, removedConnections); + } + } + } + + public void MoveUp() + { + var index = Method.Parameters.IndexOf(this); + if (index <= 0) + return; + + SwapParameter(index, index - 1); + } + + public void MoveDown() + { + var index = Method.Parameters.IndexOf(this); + if (index == Method.Parameters.Count - 1 || index == -1) + return; + + SwapParameter(index, index + 1); + } + + public void Remove() + { + var index = Method.Parameters.IndexOf(this); + if (index == -1) + return; + + Method.Parameters.RemoveAt(index); + + RefreshAllMethodCalls(); + RefreshEntryAndReturnNodes(); + } + + private void SwapParameter(int index, int newIndex) + { + var previous = Method.Parameters[newIndex]; + Method.Parameters[newIndex] = this; + Method.Parameters[index] = previous; + + RefreshAllMethodCalls(); + RefreshEntryAndReturnNodes(); + } + + public void Rename(string name) + { + var oldName = Name; + Name = name; + + foreach (var methodCall in Method.Graph.Project.GetNodes()) + { + if (methodCall.TargetMethod == Method) + methodCall.OnMethodParameterRenamed(oldName, this); + } + + var entry = Method.Graph.Nodes.Values.OfType().FirstOrDefault(); + if (entry != null) + entry.RenameParameter(this, Method.Parameters.IndexOf(this)); + } + + public void ChangeType(TypeBase type) + { + ParameterType = type; + + RefreshAllMethodCalls(); + RefreshEntryAndReturnNodes(); + } + + #endregion + } } diff --git a/src/NodeDev.Core/Class/NodeClassProperty.cs b/src/NodeDev.Core/Class/NodeClassProperty.cs index e7cc5f1..bf74306 100644 --- a/src/NodeDev.Core/Class/NodeClassProperty.cs +++ b/src/NodeDev.Core/Class/NodeClassProperty.cs @@ -81,8 +81,8 @@ internal SerializedNodeClassProperty Serialize() { var serializedNodeClassProperty = new SerializedNodeClassProperty(Name, PropertyType.SerializeWithFullTypeName()); - return serializedNodeClassProperty; - } + return serializedNodeClassProperty; + } #endregion } diff --git a/src/NodeDev.Core/Class/NodeClassTypeCreator.cs b/src/NodeDev.Core/Class/NodeClassTypeCreator.cs index 2a847bf..fe354b4 100644 --- a/src/NodeDev.Core/Class/NodeClassTypeCreator.cs +++ b/src/NodeDev.Core/Class/NodeClassTypeCreator.cs @@ -1,11 +1,10 @@ -using System.Reflection.Emit; -using System.Reflection; -using NodeDev.Core.Types; +using Dis2Msil; using FastExpressionCompiler; -using Dis2Msil; -using System.Text; +using NodeDev.Core.Types; +using System.Reflection; +using System.Reflection.Emit; using System.Runtime.Loader; -using System.Linq.Expressions; +using System.Text; namespace NodeDev.Core.Class; @@ -150,7 +149,7 @@ public void GetBodyAsCsAndMsilCode(string assemblyPath, NodeClassMethod method, var assembly = context.LoadFromAssemblyPath(assemblyPath); var type = assembly.GetType(HiddenName(method.Class.Name)); - if(type == null) + if (type == null) { msil = "type not found"; return; diff --git a/src/NodeDev.Core/Connections/Connection.cs b/src/NodeDev.Core/Connections/Connection.cs index 692b918..d9bf44d 100644 --- a/src/NodeDev.Core/Connections/Connection.cs +++ b/src/NodeDev.Core/Connections/Connection.cs @@ -1,188 +1,182 @@ using NodeDev.Core.Nodes; using NodeDev.Core.Types; -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Numerics; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; namespace NodeDev.Core.Connections { - [System.Diagnostics.DebuggerDisplay("{Parent.Name}:{Name} - {Type}, {Connections.Count}")] - public class Connection - { - public string Id { get; } + [System.Diagnostics.DebuggerDisplay("{Parent.Name}:{Name} - {Type}, {Connections.Count}")] + public class Connection + { + public string Id { get; } - public string Name { get; set; } + public string Name { get; set; } - public Node Parent { get; } - - public TypeBase Type { get; private set; } - - /// - /// Initial type of the connection. Used to remember the type before generics were resolved in case we need to re-resolve them differently. - /// - public TypeBase InitialType { get; private set; } - - internal readonly List _Connections = []; - - public IReadOnlyList Connections => _Connections; - - /// - /// Vertices of the connection. Used for drawing connections with multiple segments. - /// This is defined either if the connection is an input AND not exec, or if the connection is an output AND exec. - /// This is because for every possible types of inputs there is always only one output connected to it. Except for execs since multiple output paths can be connected to it. - /// - public readonly List Vertices = []; - - public string? TextboxValue { get; private set; } - - public object? ParsedTextboxValue { get; set; } - - /// - /// Global index of this connection in the graph. Each connection of each node in a graph has a unique index. - /// - public int GraphIndex { get; set; } = -1; - - /// - /// LinkedExec can be used to make sure a connection can only ever be used while on a path inside the linked exec connection. - /// Ex. during a 'foreach' loop, the 'Item' connection can only be used inside the 'Loop Exec' connection. - /// - public Connection? LinkedExec { get; } - - public bool IsInput => Parent.Inputs.Contains(this); - public bool IsOutput => Parent.Outputs.Contains(this); - - public Connection(string name, Node parent, TypeBase type, string? id = null, Connection? linkedExec = null) - { - Id = id ?? Guid.NewGuid().ToString(); - Name = name; - Parent = parent; - Type = type; - InitialType = type; - LinkedExec = linkedExec; - } - - #region Serialization - - public record class SerializedConnectionVertex(float X, float Y); - public record SerializedConnection(string Id, string Name, TypeBase.SerializedType SerializedType, List Connections, string? TextboxValue, List? Vertices, string? LinkedExec); - internal SerializedConnection Serialize() - { - var connections = Connections.ToList(); - - var serializedConnection = new SerializedConnection(Id, Name, Type.SerializeWithFullTypeName(), Connections.Select(x => x.Id).ToList(), TextboxValue, Vertices.Select(x => new SerializedConnectionVertex(x.X, x.Y)).ToList(), LinkedExec?.Id); - - return serializedConnection; - } - - internal static Connection Deserialize(Node parent, SerializedConnection serializedConnectionObj, bool isInput) - { - // Find the LinkedExec connection, if any - Connection? linkedExec = null; - if (linkedExec != null) - linkedExec = parent.Graph.Nodes.SelectMany(x => x.Value.InputsAndOutputs).FirstOrDefault(x => x.Id == serializedConnectionObj.LinkedExec); - - var type = TypeBase.Deserialize(parent.TypeFactory, serializedConnectionObj.SerializedType); - var connection = new Connection(serializedConnectionObj.Name, parent, type, serializedConnectionObj.Id, linkedExec); - - connection.TextboxValue = serializedConnectionObj.TextboxValue; - if (connection.TextboxValue != null && isInput) - connection.ParsedTextboxValue = connection.Type.ParseTextboxEdit(connection.TextboxValue); - - if (serializedConnectionObj.Vertices != null) - connection.Vertices.AddRange(serializedConnectionObj.Vertices.Select(x => new Vector2(x.X, x.Y))); - - foreach (var connectionId in serializedConnectionObj.Connections) - { - var otherConnection = parent.Graph.Nodes.SelectMany(x => x.Value.InputsAndOutputs).FirstOrDefault(x => x.Id == connectionId); - if (otherConnection == null) - continue; - - if (!connection.Connections.Contains(otherConnection)) - connection._Connections.Add(otherConnection); - if (!otherConnection.Connections.Contains(connection)) - otherConnection._Connections.Add(connection); - } - - return connection; - } - - #endregion - - public bool IsAssignableTo(Connection other, bool alsoValidateInitialTypeSource, bool alsoValidateInitialTypeDestination, [MaybeNullWhen(false)] out Dictionary changedGenericsLeft, [MaybeNullWhen(false)] out Dictionary changedGenericsRight, out bool usedInitialTypes) - { - if (Type.IsAssignableTo(other.Type, out var changedGenericsLeft1, out var changedGenericsRight1, out var depth1)) - { - if (alsoValidateInitialTypeSource || alsoValidateInitialTypeDestination) - { - var initialType = alsoValidateInitialTypeSource ? InitialType : Type; - var otherInitialType = alsoValidateInitialTypeDestination ? other.InitialType : other.Type; - if ((initialType != Type || otherInitialType != other.Type) && initialType.IsAssignableTo(otherInitialType, out var changedGenericsLeft2, out var changedGenericsRight2, out var depth2)) - { - if ((changedGenericsLeft2.Count != 0 || changedGenericsRight2.Count != 0) && depth2 < depth1) - { - changedGenericsLeft = changedGenericsLeft2; - changedGenericsRight = changedGenericsRight2; - usedInitialTypes = true; - return true; - } - } - } - - changedGenericsLeft = changedGenericsLeft1; - changedGenericsRight = changedGenericsRight1; - usedInitialTypes = false; - return true; - } - - changedGenericsLeft = changedGenericsRight = null; - usedInitialTypes = false; - return false; - } - - public void UpdateVertices(IEnumerable vertices) - { - Vertices.Clear(); - Vertices.AddRange(vertices); - } - - - public void UpdateTypeAndTextboxVisibility(TypeBase newType, bool overrideInitialType) - { - Type = newType; - - if (overrideInitialType) - InitialType = newType; - - if (Type.AllowTextboxEdit) - TextboxValue = Type.DefaultTextboxValue; - else - TextboxValue = null; - } - - public void UpdateTextboxText(string? text) - { - if (Type.AllowTextboxEdit) - { - TextboxValue = text; - if (text == null) - ParsedTextboxValue = null; - else - { - try - { - ParsedTextboxValue = Type.ParseTextboxEdit(text); - } - catch (Exception) - { } - } - } - } - - - } + public Node Parent { get; } + + public TypeBase Type { get; private set; } + + /// + /// Initial type of the connection. Used to remember the type before generics were resolved in case we need to re-resolve them differently. + /// + public TypeBase InitialType { get; private set; } + + internal readonly List _Connections = []; + + public IReadOnlyList Connections => _Connections; + + /// + /// Vertices of the connection. Used for drawing connections with multiple segments. + /// This is defined either if the connection is an input AND not exec, or if the connection is an output AND exec. + /// This is because for every possible types of inputs there is always only one output connected to it. Except for execs since multiple output paths can be connected to it. + /// + public readonly List Vertices = []; + + public string? TextboxValue { get; private set; } + + public object? ParsedTextboxValue { get; set; } + + /// + /// Global index of this connection in the graph. Each connection of each node in a graph has a unique index. + /// + public int GraphIndex { get; set; } = -1; + + /// + /// LinkedExec can be used to make sure a connection can only ever be used while on a path inside the linked exec connection. + /// Ex. during a 'foreach' loop, the 'Item' connection can only be used inside the 'Loop Exec' connection. + /// + public Connection? LinkedExec { get; } + + public bool IsInput => Parent.Inputs.Contains(this); + public bool IsOutput => Parent.Outputs.Contains(this); + + public Connection(string name, Node parent, TypeBase type, string? id = null, Connection? linkedExec = null) + { + Id = id ?? Guid.NewGuid().ToString(); + Name = name; + Parent = parent; + Type = type; + InitialType = type; + LinkedExec = linkedExec; + } + + #region Serialization + + public record class SerializedConnectionVertex(float X, float Y); + public record SerializedConnection(string Id, string Name, TypeBase.SerializedType SerializedType, List Connections, string? TextboxValue, List? Vertices, string? LinkedExec); + internal SerializedConnection Serialize() + { + var connections = Connections.ToList(); + + var serializedConnection = new SerializedConnection(Id, Name, Type.SerializeWithFullTypeName(), Connections.Select(x => x.Id).ToList(), TextboxValue, Vertices.Select(x => new SerializedConnectionVertex(x.X, x.Y)).ToList(), LinkedExec?.Id); + + return serializedConnection; + } + + internal static Connection Deserialize(Node parent, SerializedConnection serializedConnectionObj, bool isInput) + { + // Find the LinkedExec connection, if any + Connection? linkedExec = null; + if (linkedExec != null) + linkedExec = parent.Graph.Nodes.SelectMany(x => x.Value.InputsAndOutputs).FirstOrDefault(x => x.Id == serializedConnectionObj.LinkedExec); + + var type = TypeBase.Deserialize(parent.TypeFactory, serializedConnectionObj.SerializedType); + var connection = new Connection(serializedConnectionObj.Name, parent, type, serializedConnectionObj.Id, linkedExec); + + connection.TextboxValue = serializedConnectionObj.TextboxValue; + if (connection.TextboxValue != null && isInput) + connection.ParsedTextboxValue = connection.Type.ParseTextboxEdit(connection.TextboxValue); + + if (serializedConnectionObj.Vertices != null) + connection.Vertices.AddRange(serializedConnectionObj.Vertices.Select(x => new Vector2(x.X, x.Y))); + + foreach (var connectionId in serializedConnectionObj.Connections) + { + var otherConnection = parent.Graph.Nodes.SelectMany(x => x.Value.InputsAndOutputs).FirstOrDefault(x => x.Id == connectionId); + if (otherConnection == null) + continue; + + if (!connection.Connections.Contains(otherConnection)) + connection._Connections.Add(otherConnection); + if (!otherConnection.Connections.Contains(connection)) + otherConnection._Connections.Add(connection); + } + + return connection; + } + + #endregion + + public bool IsAssignableTo(Connection other, bool alsoValidateInitialTypeSource, bool alsoValidateInitialTypeDestination, [MaybeNullWhen(false)] out Dictionary changedGenericsLeft, [MaybeNullWhen(false)] out Dictionary changedGenericsRight, out bool usedInitialTypes) + { + if (Type.IsAssignableTo(other.Type, out var changedGenericsLeft1, out var changedGenericsRight1, out var depth1)) + { + if (alsoValidateInitialTypeSource || alsoValidateInitialTypeDestination) + { + var initialType = alsoValidateInitialTypeSource ? InitialType : Type; + var otherInitialType = alsoValidateInitialTypeDestination ? other.InitialType : other.Type; + if ((initialType != Type || otherInitialType != other.Type) && initialType.IsAssignableTo(otherInitialType, out var changedGenericsLeft2, out var changedGenericsRight2, out var depth2)) + { + if ((changedGenericsLeft2.Count != 0 || changedGenericsRight2.Count != 0) && depth2 < depth1) + { + changedGenericsLeft = changedGenericsLeft2; + changedGenericsRight = changedGenericsRight2; + usedInitialTypes = true; + return true; + } + } + } + + changedGenericsLeft = changedGenericsLeft1; + changedGenericsRight = changedGenericsRight1; + usedInitialTypes = false; + return true; + } + + changedGenericsLeft = changedGenericsRight = null; + usedInitialTypes = false; + return false; + } + + public void UpdateVertices(IEnumerable vertices) + { + Vertices.Clear(); + Vertices.AddRange(vertices); + } + + + public void UpdateTypeAndTextboxVisibility(TypeBase newType, bool overrideInitialType) + { + Type = newType; + + if (overrideInitialType) + InitialType = newType; + + if (Type.AllowTextboxEdit) + TextboxValue = Type.DefaultTextboxValue; + else + TextboxValue = null; + } + + public void UpdateTextboxText(string? text) + { + if (Type.AllowTextboxEdit) + { + TextboxValue = text; + if (text == null) + ParsedTextboxValue = null; + else + { + try + { + ParsedTextboxValue = Type.ParseTextboxEdit(text); + } + catch (Exception) + { } + } + } + } + + + } } diff --git a/src/NodeDev.Core/Graph.cs b/src/NodeDev.Core/Graph.cs index c958061..6c64205 100644 --- a/src/NodeDev.Core/Graph.cs +++ b/src/NodeDev.Core/Graph.cs @@ -2,114 +2,111 @@ using NodeDev.Core.Connections; using NodeDev.Core.Nodes; using NodeDev.Core.Nodes.Flow; -using System.Collections.Concurrent; using System.Linq.Expressions; -using System.Reflection; -using System.Text.Json; namespace NodeDev.Core; public class Graph { - public IReadOnlyDictionary Nodes { get; } = new Dictionary(); - - public NodeClass SelfClass => SelfMethod.Class; - public NodeClassMethod SelfMethod { get; set; } - - public Project Project => SelfMethod.Class.Project; - - static Graph() - { - NodeProvider.Initialize(); - } - - public void RaiseGraphChanged(bool requireUIRefresh) => Project.GraphChangedSubject.OnNext((this, requireUIRefresh)); - - #region GetChunks - - public class BadMergeException(Connection input) : Exception($"Error merging path to {input.Name} of tool {input.Parent.Name}") { } - public class DeadEndNotAllowed(List inputs) : Exception(inputs.Count == 1 ? $"Dead end not allowed in {inputs[0].Name} of tool {inputs[0].Parent.Name}" : $"Dead end not allowed in {inputs.Count} tools") { } - - - /// - /// Contains either the straight path between two lines of connections (such as a simple a -> b -> c) or a subchunk of paths. - /// The subchunk is used to group an entire chunk such as. It would contain "c" and "d" in the following example: - /// -> c - /// a -> b | -> e - /// -> d - /// "e" would be added in the next chunk, along with anything else after it - /// Only one of the two values is set at once, either or - /// Both and can be null, in that case it means that chunk part is a dead end. - /// - internal record class NodePathChunkPart(Connection Input, Connection? Output, Dictionary? SubChunk) - { - internal bool ContainOutput(Connection output) - { - if (Output == output) - return true; - - if (SubChunk?.Count > 0) - { - foreach (var subChunk in SubChunk) - { - if (subChunk.Value.ContainOutput(output)) - return true; - } - } - - return false; - } - } - - /// - /// Contains the starting point of the path, the chunks of the path and the merging point of the path. - /// If both InputMergePoint and DeadEndInput are null, the path simply led nowhere at all. Should rarely be the case in a valid graph. - /// - /// Start point of the path. - /// List of all the chunks inside the path. - /// Merging point at the end of the path. Null if the path was a dead end or a breaking such as "Return". - /// If the path was a dead end, this is the inputs that led to the dead end. Ex if both side of a branch ends in dead end, they will both indicate their last input. - internal record class NodePathChunks(Connection OutputStartPoint, List Chunks, Connection? InputMergePoint, List? DeadEndInputs) - { - internal bool ContainOutput(Connection output) - { + public IReadOnlyDictionary Nodes { get; } = new Dictionary(); + + public NodeClass SelfClass => SelfMethod.Class; + public NodeClassMethod SelfMethod { get; set; } + + public Project Project => SelfMethod.Class.Project; + + static Graph() + { + NodeProvider.Initialize(); + } + + public void RaiseGraphChanged(bool requireUIRefresh) => Project.GraphChangedSubject.OnNext((this, requireUIRefresh)); + + #region GetChunks + + public class BadMergeException(Connection input) : Exception($"Error merging path to {input.Name} of tool {input.Parent.Name}") { } + public class DeadEndNotAllowed(List inputs) : Exception(inputs.Count == 1 ? $"Dead end not allowed in {inputs[0].Name} of tool {inputs[0].Parent.Name}" : $"Dead end not allowed in {inputs.Count} tools") { } + + + /// + /// Contains either the straight path between two lines of connections (such as a simple a -> b -> c) or a subchunk of paths. + /// The subchunk is used to group an entire chunk such as. It would contain "c" and "d" in the following example: + /// -> c + /// a -> b | -> e + /// -> d + /// "e" would be added in the next chunk, along with anything else after it + /// Only one of the two values is set at once, either or + /// Both and can be null, in that case it means that chunk part is a dead end. + /// + internal record class NodePathChunkPart(Connection Input, Connection? Output, Dictionary? SubChunk) + { + internal bool ContainOutput(Connection output) + { + if (Output == output) + return true; + + if (SubChunk?.Count > 0) + { + foreach (var subChunk in SubChunk) + { + if (subChunk.Value.ContainOutput(output)) + return true; + } + } + + return false; + } + } + + /// + /// Contains the starting point of the path, the chunks of the path and the merging point of the path. + /// If both InputMergePoint and DeadEndInput are null, the path simply led nowhere at all. Should rarely be the case in a valid graph. + /// + /// Start point of the path. + /// List of all the chunks inside the path. + /// Merging point at the end of the path. Null if the path was a dead end or a breaking such as "Return". + /// If the path was a dead end, this is the inputs that led to the dead end. Ex if both side of a branch ends in dead end, they will both indicate their last input. + internal record class NodePathChunks(Connection OutputStartPoint, List Chunks, Connection? InputMergePoint, List? DeadEndInputs) + { + internal bool ContainOutput(Connection output) + { if (OutputStartPoint == output) return true; - foreach (var part in Chunks) - { - if (part.ContainOutput(output)) - return true; - } - - return false; - } - } - - /// - /// Get the chunk of a path until the next merging point. In the following example, we would chunk something like "a, (b.c, b.d), e": - /// -> c - /// a -> b | -> e - /// -> d - /// if "e" was to be merging with another path, we'd stop at "e" and return it as a merging point. - /// - internal NodePathChunks GetChunks(Connection execOutput, bool allowDeadEnd) - { - var chunks = new List(); - - var currentInput = execOutput.Connections.FirstOrDefault(); - if (currentInput == null) - { - if (!allowDeadEnd) - throw new DeadEndNotAllowed([]); - - return new NodePathChunks(execOutput, chunks, null, null); // the path led nowhere - } - - while (true) - { - if (currentInput.Parent.Outputs.Count(x => x.Type.IsExec) <= 1) // we can keep adding to the straight path. It's either a dead end or the path keeps going - { + foreach (var part in Chunks) + { + if (part.ContainOutput(output)) + return true; + } + + return false; + } + } + + /// + /// Get the chunk of a path until the next merging point. In the following example, we would chunk something like "a, (b.c, b.d), e": + /// -> c + /// a -> b | -> e + /// -> d + /// if "e" was to be merging with another path, we'd stop at "e" and return it as a merging point. + /// + internal NodePathChunks GetChunks(Connection execOutput, bool allowDeadEnd) + { + var chunks = new List(); + + var currentInput = execOutput.Connections.FirstOrDefault(); + if (currentInput == null) + { + if (!allowDeadEnd) + throw new DeadEndNotAllowed([]); + + return new NodePathChunks(execOutput, chunks, null, null); // the path led nowhere + } + + while (true) + { + if (currentInput.Parent.Outputs.Count(x => x.Type.IsExec) <= 1) // we can keep adding to the straight path. It's either a dead end or the path keeps going + { if (currentInput.Connections.Count != 1) { // We reached a merging point. We need to validate if this merging point was already validated by the last chunk. It is possible that we are in a junction like so : @@ -131,396 +128,396 @@ internal NodePathChunks GetChunks(Connection execOutput, bool allowDeadEnd) // ... <- we are here // If we are indeed in this scenario, everything from the first "if" should be in the "chunks" // We can check any merge point of the last chunk. They should all either be the same of null - if(chunks.LastOrDefault()?.SubChunk?.Values?.FirstOrDefault(x => x.InputMergePoint != null)?.InputMergePoint != currentInput) + if (chunks.LastOrDefault()?.SubChunk?.Values?.FirstOrDefault(x => x.InputMergePoint != null)?.InputMergePoint != currentInput) return new NodePathChunks(execOutput, chunks, currentInput, null); // we reached a merging point } // find the next output node to follow var output = currentInput.Parent.Outputs.FirstOrDefault(x => x.Type.IsExec && x != execOutput); - var nextInput = output?.Connections.FirstOrDefault(); // get the next input, there's either 1 or none - - if (nextInput == null) - { - // We've reached a dead end, let's check if we were allowed to in the first place - // Some nodes like "Return" are allowed to be dead ends, so we check the parent of that last node see if it's allowed - if (!allowDeadEnd && !currentInput.Parent.BreaksDeadEnd) - throw new DeadEndNotAllowed([currentInput]); - - chunks.Add(new NodePathChunkPart(currentInput, null, null)); - return new NodePathChunks(execOutput, chunks, null, [currentInput]); // we reached a dead end - } - - // add the current node to the chunks, after we know we can keep going - chunks.Add(new NodePathChunkPart(currentInput, output, null)); - - currentInput = nextInput; // we can keep going - - if (currentInput.Connections.Count != 1) - return new NodePathChunks(execOutput, chunks, currentInput, null); // we reached a merging point - } - else // we have a subchunk - { - // Get all the chunks of the node (example, both "c" and "d" in the example above) - var subChunk = GetChunks(currentInput, currentInput.Parent, allowDeadEnd); - - if (subChunk.Count == 0) - return new NodePathChunks(execOutput, chunks, null, [currentInput]); // we reached a dead end - - // We had some actual path, add it to the chunks - var part = new NodePathChunkPart(currentInput, null, subChunk); - chunks.Add(part); - - // get the merge point, it should be either null if it's all dead end, or all the same merge point. No need to validate, as GetChunks already did - var mergePoint = subChunk.Values.FirstOrDefault(x => x.InputMergePoint != null)?.InputMergePoint; - - if (mergePoint != null) - { - // we are not the only one merging here, we need to check if all the other paths are sub chunks of ours. - // That would mean we can keep going, otherwise we need to stop here and let our parent handle the merge. - foreach (var mergeOutput in mergePoint.Connections) - { - if (!part.ContainOutput(mergeOutput)) - { - // we can't keep going, we need to stop here and let our parent handle the merge - return new NodePathChunks(execOutput, chunks, mergePoint, null); - } - } - - // It's all our stuff, we can keep going - - // we merged back, we can keep going with the next nodes along the path since it's still our path - currentInput = mergePoint; - } - else - { - // we reached a dead end, we can stop the path here - var deadEndInputs = subChunk.Values.Where(x => x.DeadEndInputs != null).ToList(); - if (deadEndInputs.Count != 0) // we have dead ends, not just "nothing connected to the outputs" - return new NodePathChunks(execOutput, chunks, null, deadEndInputs.SelectMany(x => x.DeadEndInputs!).ToList()); - else - return new NodePathChunks(execOutput, chunks, null, [currentInput]); // we reached a dead end - } - } - } - } - - /// - /// Get all the chunks of a node. This will recursively get all the chunks of the outputs of the node. - /// - /// Exec input that was used to enter that node. - /// Node to get all the chunks from. - /// Does a parent allow dead end here. - /// - /// - private Dictionary GetChunks(Connection input, Node node, bool allowDeadEnd) - { - var chunks = new Dictionary(); - - foreach (var output in node.Outputs.Where(x => x.Type.IsExec)) - { - // allowDeadEnd is prioritized over the node's own setting, since we can have a dead end if the parent allows it. - // Cases like a "branch" inside a loop can be a dead end, even though branch doesn't allow it, because the loop does. - var chunk = GetChunks(output, allowDeadEnd || node.DoesOutputPathAllowDeadEnd(output)); - chunks[output] = chunk; - - // Validate if the chunk is a merge and if it is allowed - if (chunk.InputMergePoint != null && !node.DoesOutputPathAllowMerge(output)) - throw new BadMergeException(output); - } - - if (chunks.Count == 0) // it's a dead end because the node doesn't even have an exec output - return chunks; - - // Validate that the dead ends are allowed. They are either allowed if the parent path allows it or if they are breaking nodes like "Return" - if (!allowDeadEnd) - { - var hasInvalidDeadEnd = chunks - .Where(x => !node.DoesOutputPathAllowDeadEnd(x.Key) && (x.Value.DeadEndInputs != null || x.Value.InputMergePoint == null)) // x.Value.InputMergePoint == null is a dead end because the output connection was not connected to anything - .Where(x => x.Value.DeadEndInputs?.All(y => !y!.Parent.BreaksDeadEnd) ?? true) // ?? true because any dead end by "no connection" is automatically an invalid dead end - .ToList(); - - if (hasInvalidDeadEnd.Count != 0) - { - var deadEnds = new List(); - foreach (var invalidDeadEnd in hasInvalidDeadEnd) - { - if (invalidDeadEnd.Value.DeadEndInputs != null) - deadEnds.AddRange(invalidDeadEnd.Value.DeadEndInputs); - else // it's a dead end because the output connection was not connected to anything - deadEnds.Add(input); - } - - throw new DeadEndNotAllowed(deadEnds); - } - } - - // validate that all the chunks have the same merging point. If not, the path that don't merge at the same place must be dead ends - var nbDifferentMergePoint = chunks.Values.Where(x => x.InputMergePoint != null).Select(x => x.InputMergePoint).Distinct().Count(); - if (nbDifferentMergePoint > 1) // all the same or none is fine, but more than one is not - throw new BadMergeException(chunks.Values.First(x => x.InputMergePoint != null).InputMergePoint!); // we can throw any of the inputs, they all have different merging points - - return chunks; - } - - #endregion - - #region BuildExpression - - public LambdaExpression BuildExpression(BuildExpressionOptions options) - { - var entry = (Nodes.Values.FirstOrDefault(x => x is EntryNode)?.Outputs.FirstOrDefault()) ?? throw new Exception($"No entry node found in graph {SelfMethod.Name}"); - var returnLabelTarget = !SelfMethod.HasReturnValue ? Expression.Label("ReturnLabel") : Expression.Label(SelfMethod.ReturnType.MakeRealType(), "ReturnLabel"); - - var info = new BuildExpressionInfo(returnLabelTarget, options, SelfMethod.IsStatic ? null : Expression.Parameter(SelfClass.ClassTypeBase.MakeRealType(), "this")); - - // Create a variable for each output parameter - foreach (var parameter in SelfMethod.Parameters) - { - if (parameter.ParameterType.IsExec) - continue; - - var type = parameter.ParameterType.MakeRealType(); - var variable = Expression.Parameter(type, parameter.Name); - info.MethodParametersExpression[parameter.Name] = variable; - } - - // Create a variable for each node's output - foreach (var node in Nodes.Values) - { - if (node.CanBeInlined) - continue; // this can be inlined, no need to create local variables for stuff such as "a + b" - - // normal execution nodes each have their own local variable for every output - // Their input connections will be inlined or point to another local variable of someone else's output - foreach ((var connection, var variable) in node.CreateOutputsLocalVariableExpressions(info)) - info.LocalVariables[connection] = variable; - } - - var chunks = GetChunks(entry, false); - - var expressions = BuildExpression(chunks, info); - - // Create the return label with its default return value (if needed) - var returnLabel = SelfMethod.HasReturnValue ? Expression.Label(returnLabelTarget, Expression.Default(returnLabelTarget.Type)) : Expression.Label(returnLabelTarget); - // create a list of all the local variables that were used in the entire method - var localVariables = info.LocalVariables.Values - .OfType() - .Distinct() // lots of inputs use the same variable as another node's output, make sure we only declare them once - .Except(info.MethodParametersExpression.Values); // Remove the method parameters as they are declared later and not here - - var expressionBlock = Expression.Block(localVariables, expressions.Append(returnLabel)); - - var parameters = SelfMethod.IsStatic ? info.MethodParametersExpression.Values : info.MethodParametersExpression.Values.Prepend(info.ThisExpression!); - var lambdaExpression = Expression.Lambda(expressionBlock, parameters); - - return lambdaExpression; - } - - internal static Expression[] BuildExpression(NodePathChunks chunks, BuildExpressionInfo info) - { - var expressions = new Expression[chunks.Chunks.Count]; - - for (int i = 0; i < chunks.Chunks.Count; ++i) - { - var chunk = chunks.Chunks[i]; - - // connect all the inputs to it's inputs - foreach (var input in chunk.Input.Parent.Inputs) - ConnectInputExpression(input, info); - - try - { - var expression = chunk.Input.Parent.BuildExpression(chunk.SubChunk, info); - expressions[i] = expression; - } - catch (Exception ex) when (ex is not BuildError) - { - throw new BuildError(ex.Message, chunk.Input.Parent, ex); - } - } - - return expressions; - } - - private static void BuildInlineExpression(Node node, BuildExpressionInfo info) - { - if (info.InlinedNodes.Contains(node)) - return; - - if (!node.CanBeInlined) - throw new Exception($"{nameof(BuildInlineExpression)} can only be called on nodes that can be inlined: {node.Name}"); - - foreach (var input in node.Inputs) - { - ConnectInputExpression(input, info); - } - - // now that all our dependencies are built, we can build the node itself - try - { - node.BuildInlineExpression(info); - } - catch (Exception ex) when (ex is not BuildError) - { - throw new BuildError(ex.Message, node, ex); - } - - info.InlinedNodes.Add(node); - } - - private static void ConnectInputExpression(Connection input, BuildExpressionInfo info) - { - if (input.Type.IsExec) - return; - - if (input.Connections.Count == 0) - { - if (!input.Type.AllowTextboxEdit || input.ParsedTextboxValue == null) - info.LocalVariables[input] = Expression.Default(input.Type.MakeRealType()); - else - info.LocalVariables[input] = Expression.Constant(input.ParsedTextboxValue, input.Type.MakeRealType()); - } - else - { - var otherNode = input.Connections[0].Parent; - if (otherNode.CanBeInlined) - BuildInlineExpression(otherNode, info); - - // Get the local variable or expression associated with that input and use it as that input's expression - info.LocalVariables[input] = info.LocalVariables[input.Connections[0]]; - } - } - - #endregion - - #region Invoke - - public Task Invoke(Action action) - { - return Invoke(() => - { - action(); - return Task.CompletedTask; - }); - } - - public async Task Invoke(Func action) - { - await action(); // temporary - } - - #endregion - - #region Connect / Disconnect / MergedRemovedConnectionsWithNewConnections - - public void MergeRemovedConnectionsWithNewConnections(List newConnections, List removedConnections) - { - foreach (var removedConnection in removedConnections) - { - var newConnection = newConnections.FirstOrDefault(x => x.Parent == removedConnection.Parent && x.Name == removedConnection.Name && x.Type == removedConnection.Type); - - // if we found a new connection, connect them together and remove the old connection - foreach (var oldLink in removedConnection.Connections) - { - oldLink._Connections.Remove(removedConnection); // cleanup the old connection - - if (newConnection != null) - { - // Before we re-connect them let's make sure both are inputs or both outputs - if (oldLink.Parent.Outputs.Contains(oldLink) == newConnection.Parent.Inputs.Contains(newConnection)) - { - // we can safely reconnect the new connection to the old link - // Either newConnection is an input and removedConnection is an output or vice versa - newConnection._Connections.Add(oldLink); - oldLink._Connections.Add(newConnection); - } - } - } - } - - RaiseGraphChanged(true); // this always require refreshing the UI as this is custom behavior that needs to be replicated by the UI - } - - public void Connect(Connection connection1, Connection connection2, bool requireUIRefresh) - { - if (!connection1._Connections.Contains(connection2)) - connection1._Connections.Add(connection2); - if (!connection2._Connections.Contains(connection1)) - connection2._Connections.Add(connection1); - - RaiseGraphChanged(requireUIRefresh); - } - - public void Disconnect(Connection connection1, Connection connection2, bool requireUIRefresh) - { - connection1._Connections.Remove(connection2); - connection2._Connections.Remove(connection1); - - RaiseGraphChanged(requireUIRefresh); - } - - #endregion - - #region AddNode - - public void AddNode(Node node, bool requireUIRefresh) - { - ((IDictionary)Nodes)[node.Id] = node; - - RaiseGraphChanged(requireUIRefresh); - } - - public Node AddNode(NodeProvider.NodeSearchResult searchResult, bool requireUIRefresh) - { - var node = (Node)Activator.CreateInstance(searchResult.Type, this, null)!; - AddNode(node, requireUIRefresh); - - if (searchResult is NodeProvider.MethodCallNode methodCall && node is MethodCall methodCallNode) - methodCallNode.SetMethodTarget(methodCall.MethodInfo); - else if (searchResult is NodeProvider.GetPropertyOrFieldNode getPropertyOrField && node is GetPropertyOrField getPropertyOrFieldNode) - getPropertyOrFieldNode.SetMemberTarget(getPropertyOrField.MemberInfo); - else if (searchResult is NodeProvider.SetPropertyOrFieldNode setPropertyOrField && node is SetPropertyOrField setPropertyOrFieldNode) - setPropertyOrFieldNode.SetMemberTarget(setPropertyOrField.MemberInfo); - - return node; - } + var nextInput = output?.Connections.FirstOrDefault(); // get the next input, there's either 1 or none + + if (nextInput == null) + { + // We've reached a dead end, let's check if we were allowed to in the first place + // Some nodes like "Return" are allowed to be dead ends, so we check the parent of that last node see if it's allowed + if (!allowDeadEnd && !currentInput.Parent.BreaksDeadEnd) + throw new DeadEndNotAllowed([currentInput]); + + chunks.Add(new NodePathChunkPart(currentInput, null, null)); + return new NodePathChunks(execOutput, chunks, null, [currentInput]); // we reached a dead end + } + + // add the current node to the chunks, after we know we can keep going + chunks.Add(new NodePathChunkPart(currentInput, output, null)); + + currentInput = nextInput; // we can keep going + + if (currentInput.Connections.Count != 1) + return new NodePathChunks(execOutput, chunks, currentInput, null); // we reached a merging point + } + else // we have a subchunk + { + // Get all the chunks of the node (example, both "c" and "d" in the example above) + var subChunk = GetChunks(currentInput, currentInput.Parent, allowDeadEnd); + + if (subChunk.Count == 0) + return new NodePathChunks(execOutput, chunks, null, [currentInput]); // we reached a dead end + + // We had some actual path, add it to the chunks + var part = new NodePathChunkPart(currentInput, null, subChunk); + chunks.Add(part); + + // get the merge point, it should be either null if it's all dead end, or all the same merge point. No need to validate, as GetChunks already did + var mergePoint = subChunk.Values.FirstOrDefault(x => x.InputMergePoint != null)?.InputMergePoint; + + if (mergePoint != null) + { + // we are not the only one merging here, we need to check if all the other paths are sub chunks of ours. + // That would mean we can keep going, otherwise we need to stop here and let our parent handle the merge. + foreach (var mergeOutput in mergePoint.Connections) + { + if (!part.ContainOutput(mergeOutput)) + { + // we can't keep going, we need to stop here and let our parent handle the merge + return new NodePathChunks(execOutput, chunks, mergePoint, null); + } + } + + // It's all our stuff, we can keep going + + // we merged back, we can keep going with the next nodes along the path since it's still our path + currentInput = mergePoint; + } + else + { + // we reached a dead end, we can stop the path here + var deadEndInputs = subChunk.Values.Where(x => x.DeadEndInputs != null).ToList(); + if (deadEndInputs.Count != 0) // we have dead ends, not just "nothing connected to the outputs" + return new NodePathChunks(execOutput, chunks, null, deadEndInputs.SelectMany(x => x.DeadEndInputs!).ToList()); + else + return new NodePathChunks(execOutput, chunks, null, [currentInput]); // we reached a dead end + } + } + } + } + + /// + /// Get all the chunks of a node. This will recursively get all the chunks of the outputs of the node. + /// + /// Exec input that was used to enter that node. + /// Node to get all the chunks from. + /// Does a parent allow dead end here. + /// + /// + private Dictionary GetChunks(Connection input, Node node, bool allowDeadEnd) + { + var chunks = new Dictionary(); + + foreach (var output in node.Outputs.Where(x => x.Type.IsExec)) + { + // allowDeadEnd is prioritized over the node's own setting, since we can have a dead end if the parent allows it. + // Cases like a "branch" inside a loop can be a dead end, even though branch doesn't allow it, because the loop does. + var chunk = GetChunks(output, allowDeadEnd || node.DoesOutputPathAllowDeadEnd(output)); + chunks[output] = chunk; + + // Validate if the chunk is a merge and if it is allowed + if (chunk.InputMergePoint != null && !node.DoesOutputPathAllowMerge(output)) + throw new BadMergeException(output); + } + + if (chunks.Count == 0) // it's a dead end because the node doesn't even have an exec output + return chunks; + + // Validate that the dead ends are allowed. They are either allowed if the parent path allows it or if they are breaking nodes like "Return" + if (!allowDeadEnd) + { + var hasInvalidDeadEnd = chunks + .Where(x => !node.DoesOutputPathAllowDeadEnd(x.Key) && (x.Value.DeadEndInputs != null || x.Value.InputMergePoint == null)) // x.Value.InputMergePoint == null is a dead end because the output connection was not connected to anything + .Where(x => x.Value.DeadEndInputs?.All(y => !y!.Parent.BreaksDeadEnd) ?? true) // ?? true because any dead end by "no connection" is automatically an invalid dead end + .ToList(); + + if (hasInvalidDeadEnd.Count != 0) + { + var deadEnds = new List(); + foreach (var invalidDeadEnd in hasInvalidDeadEnd) + { + if (invalidDeadEnd.Value.DeadEndInputs != null) + deadEnds.AddRange(invalidDeadEnd.Value.DeadEndInputs); + else // it's a dead end because the output connection was not connected to anything + deadEnds.Add(input); + } + + throw new DeadEndNotAllowed(deadEnds); + } + } + + // validate that all the chunks have the same merging point. If not, the path that don't merge at the same place must be dead ends + var nbDifferentMergePoint = chunks.Values.Where(x => x.InputMergePoint != null).Select(x => x.InputMergePoint).Distinct().Count(); + if (nbDifferentMergePoint > 1) // all the same or none is fine, but more than one is not + throw new BadMergeException(chunks.Values.First(x => x.InputMergePoint != null).InputMergePoint!); // we can throw any of the inputs, they all have different merging points + + return chunks; + } + + #endregion + + #region BuildExpression + + public LambdaExpression BuildExpression(BuildExpressionOptions options) + { + var entry = (Nodes.Values.FirstOrDefault(x => x is EntryNode)?.Outputs.FirstOrDefault()) ?? throw new Exception($"No entry node found in graph {SelfMethod.Name}"); + var returnLabelTarget = !SelfMethod.HasReturnValue ? Expression.Label("ReturnLabel") : Expression.Label(SelfMethod.ReturnType.MakeRealType(), "ReturnLabel"); + + var info = new BuildExpressionInfo(returnLabelTarget, options, SelfMethod.IsStatic ? null : Expression.Parameter(SelfClass.ClassTypeBase.MakeRealType(), "this")); + + // Create a variable for each output parameter + foreach (var parameter in SelfMethod.Parameters) + { + if (parameter.ParameterType.IsExec) + continue; + + var type = parameter.ParameterType.MakeRealType(); + var variable = Expression.Parameter(type, parameter.Name); + info.MethodParametersExpression[parameter.Name] = variable; + } + + // Create a variable for each node's output + foreach (var node in Nodes.Values) + { + if (node.CanBeInlined) + continue; // this can be inlined, no need to create local variables for stuff such as "a + b" + + // normal execution nodes each have their own local variable for every output + // Their input connections will be inlined or point to another local variable of someone else's output + foreach ((var connection, var variable) in node.CreateOutputsLocalVariableExpressions(info)) + info.LocalVariables[connection] = variable; + } + + var chunks = GetChunks(entry, false); + + var expressions = BuildExpression(chunks, info); + + // Create the return label with its default return value (if needed) + var returnLabel = SelfMethod.HasReturnValue ? Expression.Label(returnLabelTarget, Expression.Default(returnLabelTarget.Type)) : Expression.Label(returnLabelTarget); + // create a list of all the local variables that were used in the entire method + var localVariables = info.LocalVariables.Values + .OfType() + .Distinct() // lots of inputs use the same variable as another node's output, make sure we only declare them once + .Except(info.MethodParametersExpression.Values); // Remove the method parameters as they are declared later and not here + + var expressionBlock = Expression.Block(localVariables, expressions.Append(returnLabel)); + + var parameters = SelfMethod.IsStatic ? info.MethodParametersExpression.Values : info.MethodParametersExpression.Values.Prepend(info.ThisExpression!); + var lambdaExpression = Expression.Lambda(expressionBlock, parameters); + + return lambdaExpression; + } + + internal static Expression[] BuildExpression(NodePathChunks chunks, BuildExpressionInfo info) + { + var expressions = new Expression[chunks.Chunks.Count]; + + for (int i = 0; i < chunks.Chunks.Count; ++i) + { + var chunk = chunks.Chunks[i]; + + // connect all the inputs to it's inputs + foreach (var input in chunk.Input.Parent.Inputs) + ConnectInputExpression(input, info); + + try + { + var expression = chunk.Input.Parent.BuildExpression(chunk.SubChunk, info); + expressions[i] = expression; + } + catch (Exception ex) when (ex is not BuildError) + { + throw new BuildError(ex.Message, chunk.Input.Parent, ex); + } + } + + return expressions; + } + + private static void BuildInlineExpression(Node node, BuildExpressionInfo info) + { + if (info.InlinedNodes.Contains(node)) + return; + + if (!node.CanBeInlined) + throw new Exception($"{nameof(BuildInlineExpression)} can only be called on nodes that can be inlined: {node.Name}"); + + foreach (var input in node.Inputs) + { + ConnectInputExpression(input, info); + } + + // now that all our dependencies are built, we can build the node itself + try + { + node.BuildInlineExpression(info); + } + catch (Exception ex) when (ex is not BuildError) + { + throw new BuildError(ex.Message, node, ex); + } + + info.InlinedNodes.Add(node); + } + + private static void ConnectInputExpression(Connection input, BuildExpressionInfo info) + { + if (input.Type.IsExec) + return; + + if (input.Connections.Count == 0) + { + if (!input.Type.AllowTextboxEdit || input.ParsedTextboxValue == null) + info.LocalVariables[input] = Expression.Default(input.Type.MakeRealType()); + else + info.LocalVariables[input] = Expression.Constant(input.ParsedTextboxValue, input.Type.MakeRealType()); + } + else + { + var otherNode = input.Connections[0].Parent; + if (otherNode.CanBeInlined) + BuildInlineExpression(otherNode, info); + + // Get the local variable or expression associated with that input and use it as that input's expression + info.LocalVariables[input] = info.LocalVariables[input.Connections[0]]; + } + } + + #endregion + + #region Invoke + + public Task Invoke(Action action) + { + return Invoke(() => + { + action(); + return Task.CompletedTask; + }); + } + + public async Task Invoke(Func action) + { + await action(); // temporary + } + + #endregion + + #region Connect / Disconnect / MergedRemovedConnectionsWithNewConnections + + public void MergeRemovedConnectionsWithNewConnections(List newConnections, List removedConnections) + { + foreach (var removedConnection in removedConnections) + { + var newConnection = newConnections.FirstOrDefault(x => x.Parent == removedConnection.Parent && x.Name == removedConnection.Name && x.Type == removedConnection.Type); + + // if we found a new connection, connect them together and remove the old connection + foreach (var oldLink in removedConnection.Connections) + { + oldLink._Connections.Remove(removedConnection); // cleanup the old connection + + if (newConnection != null) + { + // Before we re-connect them let's make sure both are inputs or both outputs + if (oldLink.Parent.Outputs.Contains(oldLink) == newConnection.Parent.Inputs.Contains(newConnection)) + { + // we can safely reconnect the new connection to the old link + // Either newConnection is an input and removedConnection is an output or vice versa + newConnection._Connections.Add(oldLink); + oldLink._Connections.Add(newConnection); + } + } + } + } + + RaiseGraphChanged(true); // this always require refreshing the UI as this is custom behavior that needs to be replicated by the UI + } + + public void Connect(Connection connection1, Connection connection2, bool requireUIRefresh) + { + if (!connection1._Connections.Contains(connection2)) + connection1._Connections.Add(connection2); + if (!connection2._Connections.Contains(connection1)) + connection2._Connections.Add(connection1); + + RaiseGraphChanged(requireUIRefresh); + } + + public void Disconnect(Connection connection1, Connection connection2, bool requireUIRefresh) + { + connection1._Connections.Remove(connection2); + connection2._Connections.Remove(connection1); + + RaiseGraphChanged(requireUIRefresh); + } + + #endregion + + #region AddNode + + public void AddNode(Node node, bool requireUIRefresh) + { + ((IDictionary)Nodes)[node.Id] = node; + + RaiseGraphChanged(requireUIRefresh); + } + + public Node AddNode(NodeProvider.NodeSearchResult searchResult, bool requireUIRefresh) + { + var node = (Node)Activator.CreateInstance(searchResult.Type, this, null)!; + AddNode(node, requireUIRefresh); + + if (searchResult is NodeProvider.MethodCallNode methodCall && node is MethodCall methodCallNode) + methodCallNode.SetMethodTarget(methodCall.MethodInfo); + else if (searchResult is NodeProvider.GetPropertyOrFieldNode getPropertyOrField && node is GetPropertyOrField getPropertyOrFieldNode) + getPropertyOrFieldNode.SetMemberTarget(getPropertyOrField.MemberInfo); + else if (searchResult is NodeProvider.SetPropertyOrFieldNode setPropertyOrField && node is SetPropertyOrField setPropertyOrFieldNode) + setPropertyOrFieldNode.SetMemberTarget(setPropertyOrField.MemberInfo); + + return node; + } + + #endregion + + #region RemoveNode + + public void RemoveNode(Node node, bool requireUIRefresh) + { + ((IDictionary)Nodes).Remove(node.Id); + + RaiseGraphChanged(requireUIRefresh); + } - #endregion - - #region RemoveNode + #endregion - public void RemoveNode(Node node, bool requireUIRefresh) - { - ((IDictionary)Nodes).Remove(node.Id); + #region Serialization - RaiseGraphChanged(requireUIRefresh); - } - - #endregion - - #region Serialization - - internal record class SerializedGraph(List Nodes); - internal SerializedGraph Serialize() - { - var nodes = new List(); + internal record class SerializedGraph(List Nodes); + internal SerializedGraph Serialize() + { + var nodes = new List(); - foreach (var node in Nodes.Values) - nodes.Add(node.Serialize()); + foreach (var node in Nodes.Values) + nodes.Add(node.Serialize()); - var serializedGraph = new SerializedGraph(nodes); + var serializedGraph = new SerializedGraph(nodes); - return serializedGraph; - } + return serializedGraph; + } - internal static void Deserialize(SerializedGraph serializedGraphObj, Graph graph) - { - foreach (var serializedNode in serializedGraphObj.Nodes) - { - var node = Node.Deserialize(graph, serializedNode); - graph.AddNode(node, false); - } - } + internal static void Deserialize(SerializedGraph serializedGraphObj, Graph graph) + { + foreach (var serializedNode in serializedGraphObj.Nodes) + { + var node = Node.Deserialize(graph, serializedNode); + graph.AddNode(node, false); + } + } - #endregion + #endregion } \ No newline at end of file diff --git a/src/NodeDev.Core/GraphExecutor.cs b/src/NodeDev.Core/GraphExecutor.cs index daf57c8..12c400a 100644 --- a/src/NodeDev.Core/GraphExecutor.cs +++ b/src/NodeDev.Core/GraphExecutor.cs @@ -1,12 +1,6 @@ using NodeDev.Core.Connections; using NodeDev.Core.Nodes; using NodeDev.Core.Nodes.Flow; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NodeDev.Core { @@ -141,7 +135,7 @@ public void Execute(object? self, Span inputs, Span outputs) private Span GetNodeInputs(object? self, Node node) { - if(node.Inputs.Count == 0) + if (node.Inputs.Count == 0) return Array.Empty(); var inputs = Connections.AsSpan(node.Inputs[0].GraphIndex, node.Inputs.Count); diff --git a/src/NodeDev.Core/Migrations/MigrationBase.cs b/src/NodeDev.Core/Migrations/MigrationBase.cs index daf97ac..d3d1ed4 100644 --- a/src/NodeDev.Core/Migrations/MigrationBase.cs +++ b/src/NodeDev.Core/Migrations/MigrationBase.cs @@ -4,13 +4,13 @@ namespace NodeDev.Core.Migrations; internal abstract class MigrationBase { - internal abstract string Version { get; } + internal abstract string Version { get; } - internal virtual void PerformMigrationBeforeDeserialization(JsonObject document) - { - } + internal virtual void PerformMigrationBeforeDeserialization(JsonObject document) + { + } - internal virtual void PerformMigrationAfterClassesDeserialization(Project project) - { - } + internal virtual void PerformMigrationAfterClassesDeserialization(Project project) + { + } } diff --git a/src/NodeDev.Core/Migrations/Migration_1_0_1_class_as_raw_json.cs b/src/NodeDev.Core/Migrations/Migration_1_0_1_class_as_raw_json.cs index 6046914..a141d1b 100644 --- a/src/NodeDev.Core/Migrations/Migration_1_0_1_class_as_raw_json.cs +++ b/src/NodeDev.Core/Migrations/Migration_1_0_1_class_as_raw_json.cs @@ -6,72 +6,72 @@ namespace NodeDev.Core.Migrations; // The new ones are cleaner and have less sub strings that are already serialized internal class Migration_1_0_1_class_as_raw_json : MigrationBase { - internal override string Version => "1.0.1"; - - internal override void PerformMigrationBeforeDeserialization(JsonObject document) - { - var documentClasses = document["Classes"]!; - foreach (var nodeClass in documentClasses.AsArray().ToList()) - { - var nodeClassSerializedObject = JsonNode.Parse(nodeClass!.ToString()) ?? throw new Exception("Unable to deserialize node class during migration 1.0.1"); - - var classProperties = nodeClassSerializedObject["Properties"]!; - foreach (var property in classProperties.AsArray().ToList()) - { - var serializedProperty = JsonNode.Parse(property!.ToString()) ?? throw new Exception("Unable to deserialize node class property during migration 1.0.1"); - classProperties[property!.GetElementIndex()] = serializedProperty; - } - - var classMethods = nodeClassSerializedObject["Methods"]!; - foreach (var method in classMethods.AsArray().ToList()) - { - var serializedMethod = JsonNode.Parse(method!.ToString()) ?? throw new Exception("Unable to deserialize node class method during migration 1.0.1"); - - serializedMethod["ReturnType"] = JsonNode.Parse(serializedMethod["ReturnType"]!.ToString()) ?? throw new Exception("Unable to parse return type during migration 1.0.1"); - - var methodParameters = serializedMethod["Parameters"]!; - foreach (var parameter in methodParameters.AsArray().ToList()) - { - var serializedParameter = JsonNode.Parse(parameter!.ToString()) ?? throw new Exception("Unable to deserialize node class method parameter during migration 1.0.1"); - - serializedParameter["ParameterType"] = JsonNode.Parse(serializedParameter["ParameterType"]!.ToString()) ?? throw new Exception("Unable to parse parameter type during migration 1.0.1"); - methodParameters[parameter!.GetElementIndex()] = serializedParameter; - } - - var graphMethod = JsonNode.Parse(serializedMethod["Graph"]!.ToString()) ?? throw new Exception("Unable to parse graph during migration 1.0.1"); - - var graphNodes = graphMethod["Nodes"]!; - foreach (var node in graphNodes.AsArray().ToList()) - { - var serializedNode = JsonNode.Parse(node!.ToString()) ?? throw new Exception("Unable to deserialize node during migration 1.0.1"); - - var nodeInputs = serializedNode["Inputs"]!; - foreach (var input in nodeInputs.AsArray().ToList()) - { - var serializedInput = JsonNode.Parse(input!.ToString()) ?? throw new Exception("Unable to deserialize node input during migration 1.0.1"); - - serializedInput["SerializedType"] = JsonNode.Parse(serializedInput["SerializedType"]!.ToString()) ?? throw new Exception("Unable to parse input type during migration 1.0.1"); - nodeInputs[input!.GetElementIndex()] = serializedInput; - } - - var nodeOutputs = serializedNode["Outputs"]!; - foreach (var output in nodeOutputs.AsArray().ToList()) - { - var serializedOutput = JsonNode.Parse(output!.ToString()) ?? throw new Exception("Unable to deserialize node output during migration 1.0.1"); - - serializedOutput["SerializedType"] = JsonNode.Parse(serializedOutput["SerializedType"]!.ToString()) ?? throw new Exception("Unable to parse output type during migration 1.0.1"); - nodeOutputs[output!.GetElementIndex()] = serializedOutput; - } - - graphNodes[node!.GetElementIndex()] = serializedNode; - } - - serializedMethod["Graph"] = graphMethod; - classMethods[method!.GetElementIndex()] = serializedMethod; - } - - - documentClasses[nodeClass!.GetElementIndex()] = nodeClassSerializedObject; - } - } + internal override string Version => "1.0.1"; + + internal override void PerformMigrationBeforeDeserialization(JsonObject document) + { + var documentClasses = document["Classes"]!; + foreach (var nodeClass in documentClasses.AsArray().ToList()) + { + var nodeClassSerializedObject = JsonNode.Parse(nodeClass!.ToString()) ?? throw new Exception("Unable to deserialize node class during migration 1.0.1"); + + var classProperties = nodeClassSerializedObject["Properties"]!; + foreach (var property in classProperties.AsArray().ToList()) + { + var serializedProperty = JsonNode.Parse(property!.ToString()) ?? throw new Exception("Unable to deserialize node class property during migration 1.0.1"); + classProperties[property!.GetElementIndex()] = serializedProperty; + } + + var classMethods = nodeClassSerializedObject["Methods"]!; + foreach (var method in classMethods.AsArray().ToList()) + { + var serializedMethod = JsonNode.Parse(method!.ToString()) ?? throw new Exception("Unable to deserialize node class method during migration 1.0.1"); + + serializedMethod["ReturnType"] = JsonNode.Parse(serializedMethod["ReturnType"]!.ToString()) ?? throw new Exception("Unable to parse return type during migration 1.0.1"); + + var methodParameters = serializedMethod["Parameters"]!; + foreach (var parameter in methodParameters.AsArray().ToList()) + { + var serializedParameter = JsonNode.Parse(parameter!.ToString()) ?? throw new Exception("Unable to deserialize node class method parameter during migration 1.0.1"); + + serializedParameter["ParameterType"] = JsonNode.Parse(serializedParameter["ParameterType"]!.ToString()) ?? throw new Exception("Unable to parse parameter type during migration 1.0.1"); + methodParameters[parameter!.GetElementIndex()] = serializedParameter; + } + + var graphMethod = JsonNode.Parse(serializedMethod["Graph"]!.ToString()) ?? throw new Exception("Unable to parse graph during migration 1.0.1"); + + var graphNodes = graphMethod["Nodes"]!; + foreach (var node in graphNodes.AsArray().ToList()) + { + var serializedNode = JsonNode.Parse(node!.ToString()) ?? throw new Exception("Unable to deserialize node during migration 1.0.1"); + + var nodeInputs = serializedNode["Inputs"]!; + foreach (var input in nodeInputs.AsArray().ToList()) + { + var serializedInput = JsonNode.Parse(input!.ToString()) ?? throw new Exception("Unable to deserialize node input during migration 1.0.1"); + + serializedInput["SerializedType"] = JsonNode.Parse(serializedInput["SerializedType"]!.ToString()) ?? throw new Exception("Unable to parse input type during migration 1.0.1"); + nodeInputs[input!.GetElementIndex()] = serializedInput; + } + + var nodeOutputs = serializedNode["Outputs"]!; + foreach (var output in nodeOutputs.AsArray().ToList()) + { + var serializedOutput = JsonNode.Parse(output!.ToString()) ?? throw new Exception("Unable to deserialize node output during migration 1.0.1"); + + serializedOutput["SerializedType"] = JsonNode.Parse(serializedOutput["SerializedType"]!.ToString()) ?? throw new Exception("Unable to parse output type during migration 1.0.1"); + nodeOutputs[output!.GetElementIndex()] = serializedOutput; + } + + graphNodes[node!.GetElementIndex()] = serializedNode; + } + + serializedMethod["Graph"] = graphMethod; + classMethods[method!.GetElementIndex()] = serializedMethod; + } + + + documentClasses[nodeClass!.GetElementIndex()] = nodeClassSerializedObject; + } + } } diff --git a/src/NodeDev.Core/NodeDecorations/NodeDecoration.cs b/src/NodeDev.Core/NodeDecorations/NodeDecoration.cs index bb15ef3..9aaf33f 100644 --- a/src/NodeDev.Core/NodeDecorations/NodeDecoration.cs +++ b/src/NodeDev.Core/NodeDecorations/NodeDecoration.cs @@ -1,15 +1,10 @@ using NodeDev.Core.Types; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NodeDev.Core.NodeDecorations; public interface INodeDecoration { - public static INodeDecoration Deserialize(TypeFactory typeFactory, string serialized) => throw new NotImplementedException(); + public static INodeDecoration Deserialize(TypeFactory typeFactory, string serialized) => throw new NotImplementedException(); - public abstract string Serialize(); + public abstract string Serialize(); } diff --git a/src/NodeDev.Core/NodePaths.cs b/src/NodeDev.Core/NodePaths.cs index 15869a8..3faab07 100644 --- a/src/NodeDev.Core/NodePaths.cs +++ b/src/NodeDev.Core/NodePaths.cs @@ -58,7 +58,7 @@ public NodePaths GetPathsLeadingToOutput(Connection connection) } return newPaths; - + } /// diff --git a/src/NodeDev.Core/NodeProvider.cs b/src/NodeDev.Core/NodeProvider.cs index a5575a4..320e52a 100644 --- a/src/NodeDev.Core/NodeProvider.cs +++ b/src/NodeDev.Core/NodeProvider.cs @@ -1,139 +1,133 @@ -using NodeDev.Core.Class; -using NodeDev.Core.Connections; +using NodeDev.Core.Connections; using NodeDev.Core.Nodes; using NodeDev.Core.Types; -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; namespace NodeDev.Core { - // this whole way of searching nodes is disgusting. - // you're welcome - public static class NodeProvider - { - private static List NodeTypes = new(); - public static void Initialize() - { - AddNodesFromAssembly(typeof(NodeProvider).Assembly); - } - - - // function load a list of all class that inherit from Node - public static void AddNodesFromAssembly(Assembly assembly) - { - var types = assembly.GetTypes().Where(p => typeof(Node).IsAssignableFrom(p) && !p.IsAbstract); - - NodeTypes.AddRange(types); - } - - public record class NodeSearchResult(Type Type); - public record class MethodCallNode(Type Type, IMethodInfo MethodInfo) : NodeSearchResult(Type); - public record class GetPropertyOrFieldNode(Type Type, IMemberInfo MemberInfo) : NodeSearchResult(Type); - public record class SetPropertyOrFieldNode(Type Type, IMemberInfo MemberInfo) : NodeSearchResult(Type); - public static IEnumerable Search(Project project, string text, Connection? startConnection) - { - if (startConnection?.Type is UndefinedGenericType) - startConnection = null; // we want to list every possible choices - - var nodes = NodeTypes.Where(x => x != typeof(MethodCall)).Where(p => p.Name.Contains(text, StringComparison.OrdinalIgnoreCase)); - - var results = nodes.Select(x => new NodeSearchResult(x)); - - IEnumerable GetPropertiesAndFields(TypeBase type, string text) - { - IEnumerable members = type.GetMembers(); - members = members.Where(x => (x.IsProperty || x.IsField) && x.Name.Contains(text, StringComparison.OrdinalIgnoreCase)); // filter with the name - - IEnumerable results = members.Where(x => x.CanGet).Select(x => new GetPropertyOrFieldNode(typeof(GetPropertyOrField), x)); - results = results.Concat(members.Where(x => x.CanSet).Select(x => new SetPropertyOrFieldNode(typeof(SetPropertyOrField), x))); - - return results; - } - - // check if the text is a method call like 'ClassName.MethodName' - var methodCallSplit = text.Split('.'); - if (methodCallSplit.Length >= 2) - { - // try to find the class specified - project.TypeFactory.CreateBaseFromUserInput(string.Join('.', methodCallSplit[0..^1]), out var type); - if (type != null) - { - // find if the method exists - var methods = type.GetMethods().Where(x => x.Name.Contains(methodCallSplit[^1], StringComparison.OrdinalIgnoreCase)); - - // only keep the methods that are using the startConnection type, if provided - if (startConnection?.Type?.IsExec == false) - methods = methods.Where(x => x.GetParameters().Any(y => startConnection.Type.IsAssignableTo(y.ParameterType, out _, out _))); - - results = results.Concat(methods.Select(x => new MethodCallNode(typeof(MethodCall), x))); - - if (startConnection == null) - results = results.Concat(GetPropertiesAndFields(type, methodCallSplit[1])); - } - } - else if (startConnection?.Type.IsExec == false) - { - // find if the method exists - var methods = startConnection.Type + // this whole way of searching nodes is disgusting. + // you're welcome + public static class NodeProvider + { + private static List NodeTypes = new(); + public static void Initialize() + { + AddNodesFromAssembly(typeof(NodeProvider).Assembly); + } + + + // function load a list of all class that inherit from Node + public static void AddNodesFromAssembly(Assembly assembly) + { + var types = assembly.GetTypes().Where(p => typeof(Node).IsAssignableFrom(p) && !p.IsAbstract); + + NodeTypes.AddRange(types); + } + + public record class NodeSearchResult(Type Type); + public record class MethodCallNode(Type Type, IMethodInfo MethodInfo) : NodeSearchResult(Type); + public record class GetPropertyOrFieldNode(Type Type, IMemberInfo MemberInfo) : NodeSearchResult(Type); + public record class SetPropertyOrFieldNode(Type Type, IMemberInfo MemberInfo) : NodeSearchResult(Type); + public static IEnumerable Search(Project project, string text, Connection? startConnection) + { + if (startConnection?.Type is UndefinedGenericType) + startConnection = null; // we want to list every possible choices + + var nodes = NodeTypes.Where(x => x != typeof(MethodCall)).Where(p => p.Name.Contains(text, StringComparison.OrdinalIgnoreCase)); + + var results = nodes.Select(x => new NodeSearchResult(x)); + + IEnumerable GetPropertiesAndFields(TypeBase type, string text) + { + IEnumerable members = type.GetMembers(); + members = members.Where(x => (x.IsProperty || x.IsField) && x.Name.Contains(text, StringComparison.OrdinalIgnoreCase)); // filter with the name + + IEnumerable results = members.Where(x => x.CanGet).Select(x => new GetPropertyOrFieldNode(typeof(GetPropertyOrField), x)); + results = results.Concat(members.Where(x => x.CanSet).Select(x => new SetPropertyOrFieldNode(typeof(SetPropertyOrField), x))); + + return results; + } + + // check if the text is a method call like 'ClassName.MethodName' + var methodCallSplit = text.Split('.'); + if (methodCallSplit.Length >= 2) + { + // try to find the class specified + project.TypeFactory.CreateBaseFromUserInput(string.Join('.', methodCallSplit[0..^1]), out var type); + if (type != null) + { + // find if the method exists + var methods = type.GetMethods().Where(x => x.Name.Contains(methodCallSplit[^1], StringComparison.OrdinalIgnoreCase)); + + // only keep the methods that are using the startConnection type, if provided + if (startConnection?.Type?.IsExec == false) + methods = methods.Where(x => x.GetParameters().Any(y => startConnection.Type.IsAssignableTo(y.ParameterType, out _, out _))); + + results = results.Concat(methods.Select(x => new MethodCallNode(typeof(MethodCall), x))); + + if (startConnection == null) + results = results.Concat(GetPropertiesAndFields(type, methodCallSplit[1])); + } + } + else if (startConnection?.Type.IsExec == false) + { + // find if the method exists + var methods = startConnection.Type .GetMethods() - .Where(x => + .Where(x => !x.Attributes.HasFlag(MethodAttributes.HideBySig) && // hide methods such as get/set of properties. Not sure if there's a better way to efficiently do this? x.Name.Contains(text, StringComparison.OrdinalIgnoreCase) && // search with the text ignoring case !x.IsStatic); // Since we're dragging out of a connection, we're expected to only want to execute instance methods - // get extensions methods for the realType.BackendType - methods = methods.Concat(GetExtensionMethods(startConnection.Type, project.TypeFactory)).Where(x => string.IsNullOrWhiteSpace(text) || x.Name.Contains(text, StringComparison.OrdinalIgnoreCase)); - - results = results.Concat(methods.Select(x => new MethodCallNode(typeof(MethodCall), x))); - - results = results.Concat(GetPropertiesAndFields(startConnection.Type, text)); - } - - // add methods, get properties and set properties - results = results.Concat(project.Classes.SelectMany(nodeClass => nodeClass.Methods.Where(x => string.IsNullOrWhiteSpace(text) || x.Name.Contains(text, StringComparison.OrdinalIgnoreCase)).Select(x => new MethodCallNode(typeof(MethodCall), x)))); - results = results.Concat(project.Classes.SelectMany(nodeClass => nodeClass.Properties.Select(x => new GetPropertyOrFieldNode(typeof(GetPropertyOrField), x)))); - results = results.Concat(project.Classes.SelectMany(nodeClass => nodeClass.Properties.Select(x => new SetPropertyOrFieldNode(typeof(SetPropertyOrField), x)))); - - // remove any duplicates that may have introduced itself - results = results.DistinctBy(result => - { - if (result is MethodCallNode methodCallNode) - return (object)methodCallNode.MethodInfo; - //if (result is GetPropertyOrFieldNode propertyOrFieldNode) - // return (object)propertyOrFieldNode.MemberInfo; - //if (result is SetPropertyOrFieldNode getPropertyOrFieldNode) - // return (object)getPropertyOrFieldNode.MemberInfo; - return (object)result; - }); - - return results; - } - - private static readonly Dictionary> ExtensionMethodsMethodsPerType = []; - private static IEnumerable GetExtensionMethods(TypeBase t, TypeFactory typeFactory) - { - var query = AppDomain.CurrentDomain.GetAssemblies() - .Where(x => !x.IsDynamic) // dirty patch to prevent loading types from the generated assemblies - .SelectMany(assembly => - { - if (ExtensionMethodsMethodsPerType.TryGetValue(assembly, out var methods)) - return methods; - - return ExtensionMethodsMethodsPerType[assembly] = assembly - .GetTypes() - .Where(type => !type.IsGenericType) - .SelectMany(x => x.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) - .Where(method => method.IsDefined(typeof(ExtensionAttribute), false) && t.IsAssignableTo(typeFactory.Get(method.GetParameters()[0].ParameterType, null), out _, out _)) - .Select(x => new RealMethodInfo(typeFactory, x, typeFactory.Get(x.DeclaringType!, null))) - .ToList(); - }); - - return query; - } - } + // get extensions methods for the realType.BackendType + methods = methods.Concat(GetExtensionMethods(startConnection.Type, project.TypeFactory)).Where(x => string.IsNullOrWhiteSpace(text) || x.Name.Contains(text, StringComparison.OrdinalIgnoreCase)); + + results = results.Concat(methods.Select(x => new MethodCallNode(typeof(MethodCall), x))); + + results = results.Concat(GetPropertiesAndFields(startConnection.Type, text)); + } + + // add methods, get properties and set properties + results = results.Concat(project.Classes.SelectMany(nodeClass => nodeClass.Methods.Where(x => string.IsNullOrWhiteSpace(text) || x.Name.Contains(text, StringComparison.OrdinalIgnoreCase)).Select(x => new MethodCallNode(typeof(MethodCall), x)))); + results = results.Concat(project.Classes.SelectMany(nodeClass => nodeClass.Properties.Select(x => new GetPropertyOrFieldNode(typeof(GetPropertyOrField), x)))); + results = results.Concat(project.Classes.SelectMany(nodeClass => nodeClass.Properties.Select(x => new SetPropertyOrFieldNode(typeof(SetPropertyOrField), x)))); + + // remove any duplicates that may have introduced itself + results = results.DistinctBy(result => + { + if (result is MethodCallNode methodCallNode) + return (object)methodCallNode.MethodInfo; + //if (result is GetPropertyOrFieldNode propertyOrFieldNode) + // return (object)propertyOrFieldNode.MemberInfo; + //if (result is SetPropertyOrFieldNode getPropertyOrFieldNode) + // return (object)getPropertyOrFieldNode.MemberInfo; + return (object)result; + }); + + return results; + } + + private static readonly Dictionary> ExtensionMethodsMethodsPerType = []; + private static IEnumerable GetExtensionMethods(TypeBase t, TypeFactory typeFactory) + { + var query = AppDomain.CurrentDomain.GetAssemblies() + .Where(x => !x.IsDynamic) // dirty patch to prevent loading types from the generated assemblies + .SelectMany(assembly => + { + if (ExtensionMethodsMethodsPerType.TryGetValue(assembly, out var methods)) + return methods; + + return ExtensionMethodsMethodsPerType[assembly] = assembly + .GetTypes() + .Where(type => !type.IsGenericType) + .SelectMany(x => x.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) + .Where(method => method.IsDefined(typeof(ExtensionAttribute), false) && t.IsAssignableTo(typeFactory.Get(method.GetParameters()[0].ParameterType, null), out _, out _)) + .Select(x => new RealMethodInfo(typeFactory, x, typeFactory.Get(x.DeclaringType!, null))) + .ToList(); + }); + + return query; + } + } } diff --git a/src/NodeDev.Core/Nodes/ArrayGet.cs b/src/NodeDev.Core/Nodes/ArrayGet.cs index f34d335..a1bde1e 100644 --- a/src/NodeDev.Core/Nodes/ArrayGet.cs +++ b/src/NodeDev.Core/Nodes/ArrayGet.cs @@ -1,6 +1,4 @@ -using NodeDev.Core.Class; -using NodeDev.Core.Connections; -using NodeDev.Core.Types; +using NodeDev.Core.Types; using System.Linq.Expressions; namespace NodeDev.Core.Nodes; @@ -10,14 +8,14 @@ public class ArrayGet : NoFlowNode public override string Name { get => $"{Outputs[0].Type.Name} Get"; - set { } + set { } } public ArrayGet(Graph graph, string? id = null) : base(graph, id) { var undefinedT = new UndefinedGenericType("T"); - Inputs.Add(new("Array", this, undefinedT.ArrayType)); + Inputs.Add(new("Array", this, undefinedT.ArrayType)); Inputs.Add(new("Index", this, TypeFactory.Get())); Outputs.Add(new("Obj", this, undefinedT)); diff --git a/src/NodeDev.Core/Nodes/ArraySet.cs b/src/NodeDev.Core/Nodes/ArraySet.cs index 849fa3c..f9edf78 100644 --- a/src/NodeDev.Core/Nodes/ArraySet.cs +++ b/src/NodeDev.Core/Nodes/ArraySet.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Class; -using NodeDev.Core.Connections; +using NodeDev.Core.Connections; using NodeDev.Core.Types; using System.Linq.Expressions; @@ -10,16 +9,16 @@ public class ArraySet : NormalFlowNode public override string Name { get => $"{Inputs[0].Type.Name} Set"; - set { } + set { } } public ArraySet(Graph graph, string? id = null) : base(graph, id) { var undefinedT = new UndefinedGenericType("T"); - Inputs.Add(new("Array", this, undefinedT.ArrayType)); + Inputs.Add(new("Array", this, undefinedT.ArrayType)); Inputs.Add(new("Index", this, TypeFactory.Get())); - Inputs.Add(new("Obj", this, undefinedT)); + Inputs.Add(new("Obj", this, undefinedT)); } internal override Expression BuildExpression(Dictionary? subChunks, BuildExpressionInfo info) @@ -28,6 +27,6 @@ internal override Expression BuildExpression(Dictionary base.Name; set { base.Name = value; - if(Outputs.Count > 1) // If not, we're probably still in the constructor + if (Outputs.Count > 1) // If not, we're probably still in the constructor Outputs[1].Name = value; } } @@ -32,12 +32,12 @@ public override string Name public override bool AllowEditingName => true; internal override Expression BuildExpression(Dictionary? subChunks, BuildExpressionInfo info) - { - return Expression.Assign(info.LocalVariables[Outputs[1]], info.LocalVariables[Inputs[1]]); - } - - internal override void BuildInlineExpression(BuildExpressionInfo info) - { - throw new NotImplementedException(); - } + { + return Expression.Assign(info.LocalVariables[Outputs[1]], info.LocalVariables[Inputs[1]]); + } + + internal override void BuildInlineExpression(BuildExpressionInfo info) + { + throw new NotImplementedException(); + } } diff --git a/src/NodeDev.Core/Nodes/Flow/Branch.cs b/src/NodeDev.Core/Nodes/Flow/Branch.cs index 4fd217e..60c20eb 100644 --- a/src/NodeDev.Core/Nodes/Flow/Branch.cs +++ b/src/NodeDev.Core/Nodes/Flow/Branch.cs @@ -35,11 +35,11 @@ internal override Expression BuildExpression(Dictionary true; + public override bool IsFlowNode => true; - public override bool FetchState => true; + public override bool FetchState => true; - public override bool ReOrderExecInputsAndOutputs => false; + public override bool ReOrderExecInputsAndOutputs => false; - public override bool AllowRemergingExecConnections => false; + public override bool AllowRemergingExecConnections => false; - public ForNode(Graph graph, string? id = null) : base(graph, id) - { - Name = "For"; + public ForNode(Graph graph, string? id = null) : base(graph, id) + { + Name = "For"; - Inputs.Add(new("Exec", this, TypeFactory.ExecType)); - Inputs.Add(new("Start", this, TypeFactory.Get())); - Inputs.Add(new("End (Exclude)", this, TypeFactory.Get())); + Inputs.Add(new("Exec", this, TypeFactory.ExecType)); + Inputs.Add(new("Start", this, TypeFactory.Get())); + Inputs.Add(new("End (Exclude)", this, TypeFactory.Get())); - Outputs.Add(new("ExecLoop", this, TypeFactory.ExecType)); - Outputs.Add(new("Index", this, TypeFactory.Get(), linkedExec: Outputs[0])); - Outputs.Add(new("ExecOut", this, TypeFactory.ExecType)); - } + Outputs.Add(new("ExecLoop", this, TypeFactory.ExecType)); + Outputs.Add(new("Index", this, TypeFactory.Get(), linkedExec: Outputs[0])); + Outputs.Add(new("ExecOut", this, TypeFactory.ExecType)); + } - public override string GetExecOutputPathId(string pathId, Connection execOutput) - { - if (execOutput == Outputs[0]) - return pathId + "-" + execOutput.Id; - else if (execOutput == Outputs[2]) - return pathId; - else - throw new Exception("Invalid exec output"); - } + public override string GetExecOutputPathId(string pathId, Connection execOutput) + { + if (execOutput == Outputs[0]) + return pathId + "-" + execOutput.Id; + else if (execOutput == Outputs[2]) + return pathId; + else + throw new Exception("Invalid exec output"); + } - public override bool DoesOutputPathAllowDeadEnd(Connection execOutput) => execOutput == Outputs[0]; // The loop exec path must be a dead end (or a breaking node, such as return, continue, break) + public override bool DoesOutputPathAllowDeadEnd(Connection execOutput) => execOutput == Outputs[0]; // The loop exec path must be a dead end (or a breaking node, such as return, continue, break) - public override bool DoesOutputPathAllowMerge(Connection execOutput) => execOutput == Outputs[2]; // the ExecOut path allows merging, but not the loop. The loop is always a dead end. + public override bool DoesOutputPathAllowMerge(Connection execOutput) => execOutput == Outputs[2]; // the ExecOut path allows merging, but not the loop. The loop is always a dead end. - private readonly string LabelName = $"break_{Random.Shared.Next(0, 100000)}"; + private readonly string LabelName = $"break_{Random.Shared.Next(0, 100000)}"; - internal override Expression BuildExpression(Dictionary? subChunks, BuildExpressionInfo info) - { - ArgumentNullException.ThrowIfNull(subChunks); + internal override Expression BuildExpression(Dictionary? subChunks, BuildExpressionInfo info) + { + ArgumentNullException.ThrowIfNull(subChunks); - var count = info.LocalVariables[Outputs[1]]; - var assignCount = Expression.Assign(count, info.LocalVariables[Inputs[1]]); // assign the start value to the count variable - var incrementCount = Expression.PreIncrementAssign(count); // increment the count variable + var count = info.LocalVariables[Outputs[1]]; + var assignCount = Expression.Assign(count, info.LocalVariables[Inputs[1]]); // assign the start value to the count variable + var incrementCount = Expression.PreIncrementAssign(count); // increment the count variable - var loopBody = Expression.Block(Graph.BuildExpression(subChunks[Outputs[0]], info).Append(incrementCount)); // Build the loop body and the counter increment - var afterLoop = Expression.Block(Graph.BuildExpression(subChunks[Outputs[2]], info)); // Build the after loop body + var loopBody = Expression.Block(Graph.BuildExpression(subChunks[Outputs[0]], info).Append(incrementCount)); // Build the loop body and the counter increment + var afterLoop = Expression.Block(Graph.BuildExpression(subChunks[Outputs[2]], info)); // Build the after loop body - var breakLabel = Expression.Label(LabelName); - var loop = Expression.Loop( - Expression.IfThenElse( - Expression.LessThan(count, info.LocalVariables[Inputs[2]]), // i < end - loopBody, // does the assign for enumerator.Current, as well as the loop body - Expression.Break(breakLabel) // break the loop - ), - breakLabel - ); + var breakLabel = Expression.Label(LabelName); + var loop = Expression.Loop( + Expression.IfThenElse( + Expression.LessThan(count, info.LocalVariables[Inputs[2]]), // i < end + loopBody, // does the assign for enumerator.Current, as well as the loop body + Expression.Break(breakLabel) // break the loop + ), + breakLabel + ); - return Expression.Block(assignCount, loop, afterLoop); - } + return Expression.Block(assignCount, loop, afterLoop); + } } diff --git a/src/NodeDev.Core/Nodes/Flow/ForeachNode.cs b/src/NodeDev.Core/Nodes/Flow/ForeachNode.cs index 1e00370..777e34e 100644 --- a/src/NodeDev.Core/Nodes/Flow/ForeachNode.cs +++ b/src/NodeDev.Core/Nodes/Flow/ForeachNode.cs @@ -44,7 +44,7 @@ public override string GetExecOutputPathId(string pathId, Connection execOutput) public override bool DoesOutputPathAllowMerge(Connection execOutput) => execOutput == Outputs[2]; // The ExecOut path allows merging but not the loop. The loop is always a dead end. - private readonly string LabelName = $"break_{Random.Shared.Next(0, 100000)}"; + private readonly string LabelName = $"break_{Random.Shared.Next(0, 100000)}"; internal override Expression BuildExpression(Dictionary? subChunks, BuildExpressionInfo info) { ArgumentNullException.ThrowIfNull(subChunks); diff --git a/src/NodeDev.Core/Nodes/Flow/WhileNode.cs b/src/NodeDev.Core/Nodes/Flow/WhileNode.cs index 4af3b90..7d201c5 100644 --- a/src/NodeDev.Core/Nodes/Flow/WhileNode.cs +++ b/src/NodeDev.Core/Nodes/Flow/WhileNode.cs @@ -34,24 +34,24 @@ public override string GetExecOutputPathId(string pathId, Connection execOutput) public override bool DoesOutputPathAllowMerge(Connection execOutput) => execOutput == Outputs[1]; // Only the ExecOut path can merge. The loop path can never merge and always ends in a dead end. - private readonly string LabelName = $"break_{Random.Shared.Next(0, 100000)}"; + private readonly string LabelName = $"break_{Random.Shared.Next(0, 100000)}"; internal override Expression BuildExpression(Dictionary? subChunks, BuildExpressionInfo info) - { + { ArgumentNullException.ThrowIfNull(subChunks); var loopBody = Expression.Block(Graph.BuildExpression(subChunks[Outputs[0]], info)); // Build the loop body var afterLoop = Expression.Block(Graph.BuildExpression(subChunks[Outputs[1]], info)); // Build the after loop body - var breakLabel = Expression.Label(LabelName); - var loop = Expression.Loop( - Expression.IfThenElse( + var breakLabel = Expression.Label(LabelName); + var loop = Expression.Loop( + Expression.IfThenElse( info.LocalVariables[Inputs[1]], // while condition - loopBody, // does the assign for enumerator.Current, as well as the loop body - Expression.Break(breakLabel) // break the loop - ), - breakLabel - ); + loopBody, // does the assign for enumerator.Current, as well as the loop body + Expression.Break(breakLabel) // break the loop + ), + breakLabel + ); return Expression.Block(loop, afterLoop); - } + } } diff --git a/src/NodeDev.Core/Nodes/GetPropertyOrField.cs b/src/NodeDev.Core/Nodes/GetPropertyOrField.cs index 3cde148..bb1d9f1 100644 --- a/src/NodeDev.Core/Nodes/GetPropertyOrField.cs +++ b/src/NodeDev.Core/Nodes/GetPropertyOrField.cs @@ -87,7 +87,7 @@ internal override void BuildInlineExpression(BuildExpressionInfo info) var binding = BindingFlags.Public | BindingFlags.NonPublic | (TargetMember.IsStatic ? BindingFlags.Static : BindingFlags.Instance); - if(TargetMember.IsField) + if (TargetMember.IsField) { var field = type.GetField(TargetMember.Name, binding | BindingFlags.GetField) ?? throw new Exception($"Unable to find field: {TargetMember.Name}"); diff --git a/src/NodeDev.Core/Nodes/Math/Add.cs b/src/NodeDev.Core/Nodes/Math/Add.cs index bcf9ad3..9466fb9 100644 --- a/src/NodeDev.Core/Nodes/Math/Add.cs +++ b/src/NodeDev.Core/Nodes/Math/Add.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using System.Linq.Expressions; +using System.Linq.Expressions; namespace NodeDev.Core.Nodes.Math; diff --git a/src/NodeDev.Core/Nodes/Math/And.cs b/src/NodeDev.Core/Nodes/Math/And.cs index c9b3b79..43eab00 100644 --- a/src/NodeDev.Core/Nodes/Math/And.cs +++ b/src/NodeDev.Core/Nodes/Math/And.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using NodeDev.Core.Types; +using NodeDev.Core.Types; using System.Linq.Expressions; namespace NodeDev.Core.Nodes.Math; diff --git a/src/NodeDev.Core/Nodes/Math/BiggerThan.cs b/src/NodeDev.Core/Nodes/Math/BiggerThan.cs index ccfd444..6228e66 100644 --- a/src/NodeDev.Core/Nodes/Math/BiggerThan.cs +++ b/src/NodeDev.Core/Nodes/Math/BiggerThan.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using NodeDev.Core.Types; +using NodeDev.Core.Types; using System.Linq.Expressions; namespace NodeDev.Core.Nodes.Math; diff --git a/src/NodeDev.Core/Nodes/Math/BiggerThanOrEqual.cs b/src/NodeDev.Core/Nodes/Math/BiggerThanOrEqual.cs index b8db6eb..836ed9f 100644 --- a/src/NodeDev.Core/Nodes/Math/BiggerThanOrEqual.cs +++ b/src/NodeDev.Core/Nodes/Math/BiggerThanOrEqual.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using NodeDev.Core.Types; +using NodeDev.Core.Types; using System.Linq.Expressions; namespace NodeDev.Core.Nodes.Math; diff --git a/src/NodeDev.Core/Nodes/Math/BinaryOperationMath.cs b/src/NodeDev.Core/Nodes/Math/BinaryOperationMath.cs index c183751..6201459 100644 --- a/src/NodeDev.Core/Nodes/Math/BinaryOperationMath.cs +++ b/src/NodeDev.Core/Nodes/Math/BinaryOperationMath.cs @@ -1,13 +1,8 @@ using NodeDev.Core.Types; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NodeDev.Core.Nodes.Math { - public abstract class BinaryOperationMath: NoFlowNode + public abstract class BinaryOperationMath : NoFlowNode { public BinaryOperationMath(Graph graph, string? id = null) : base(graph, id) diff --git a/src/NodeDev.Core/Nodes/Math/Divide.cs b/src/NodeDev.Core/Nodes/Math/Divide.cs index e16f2f0..fbed0c0 100644 --- a/src/NodeDev.Core/Nodes/Math/Divide.cs +++ b/src/NodeDev.Core/Nodes/Math/Divide.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using System.Linq.Expressions; +using System.Linq.Expressions; namespace NodeDev.Core.Nodes.Math; diff --git a/src/NodeDev.Core/Nodes/Math/Equals.cs b/src/NodeDev.Core/Nodes/Math/Equals.cs index 377e508..0f4b062 100644 --- a/src/NodeDev.Core/Nodes/Math/Equals.cs +++ b/src/NodeDev.Core/Nodes/Math/Equals.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using NodeDev.Core.Types; +using NodeDev.Core.Types; using System.Linq.Expressions; namespace NodeDev.Core.Nodes.Math; diff --git a/src/NodeDev.Core/Nodes/Math/IsNotNull.cs b/src/NodeDev.Core/Nodes/Math/IsNotNull.cs index 27aabde..914c615 100644 --- a/src/NodeDev.Core/Nodes/Math/IsNotNull.cs +++ b/src/NodeDev.Core/Nodes/Math/IsNotNull.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using System.Linq.Expressions; +using System.Linq.Expressions; namespace NodeDev.Core.Nodes.Math; diff --git a/src/NodeDev.Core/Nodes/Math/IsNull.cs b/src/NodeDev.Core/Nodes/Math/IsNull.cs index 73f22a8..ad3b65e 100644 --- a/src/NodeDev.Core/Nodes/Math/IsNull.cs +++ b/src/NodeDev.Core/Nodes/Math/IsNull.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using System.Linq.Expressions; +using System.Linq.Expressions; namespace NodeDev.Core.Nodes.Math; diff --git a/src/NodeDev.Core/Nodes/Math/Modulo.cs b/src/NodeDev.Core/Nodes/Math/Modulo.cs index a900f8c..ad62f10 100644 --- a/src/NodeDev.Core/Nodes/Math/Modulo.cs +++ b/src/NodeDev.Core/Nodes/Math/Modulo.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using System.Linq.Expressions; +using System.Linq.Expressions; namespace NodeDev.Core.Nodes.Math; diff --git a/src/NodeDev.Core/Nodes/Math/Multiply.cs b/src/NodeDev.Core/Nodes/Math/Multiply.cs index 6cf5ccd..9efc122 100644 --- a/src/NodeDev.Core/Nodes/Math/Multiply.cs +++ b/src/NodeDev.Core/Nodes/Math/Multiply.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using System.Linq.Expressions; +using System.Linq.Expressions; namespace NodeDev.Core.Nodes.Math; diff --git a/src/NodeDev.Core/Nodes/Math/Not.cs b/src/NodeDev.Core/Nodes/Math/Not.cs index 24894b4..37e3737 100644 --- a/src/NodeDev.Core/Nodes/Math/Not.cs +++ b/src/NodeDev.Core/Nodes/Math/Not.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using System.Linq.Expressions; +using System.Linq.Expressions; namespace NodeDev.Core.Nodes.Math; diff --git a/src/NodeDev.Core/Nodes/Math/NotEquals.cs b/src/NodeDev.Core/Nodes/Math/NotEquals.cs index 908de81..4962e47 100644 --- a/src/NodeDev.Core/Nodes/Math/NotEquals.cs +++ b/src/NodeDev.Core/Nodes/Math/NotEquals.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using NodeDev.Core.Types; +using NodeDev.Core.Types; using System.Linq.Expressions; namespace NodeDev.Core.Nodes.Math; diff --git a/src/NodeDev.Core/Nodes/Math/Or.cs b/src/NodeDev.Core/Nodes/Math/Or.cs index e7202e4..18bd42a 100644 --- a/src/NodeDev.Core/Nodes/Math/Or.cs +++ b/src/NodeDev.Core/Nodes/Math/Or.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using NodeDev.Core.Types; +using NodeDev.Core.Types; using System.Linq.Expressions; namespace NodeDev.Core.Nodes.Math; diff --git a/src/NodeDev.Core/Nodes/Math/SmallerThan.cs b/src/NodeDev.Core/Nodes/Math/SmallerThan.cs index 03f241c..43135a7 100644 --- a/src/NodeDev.Core/Nodes/Math/SmallerThan.cs +++ b/src/NodeDev.Core/Nodes/Math/SmallerThan.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using NodeDev.Core.Types; +using NodeDev.Core.Types; using System.Linq.Expressions; namespace NodeDev.Core.Nodes.Math; diff --git a/src/NodeDev.Core/Nodes/Math/SmallerThanOrEqual.cs b/src/NodeDev.Core/Nodes/Math/SmallerThanOrEqual.cs index 1601018..45002c1 100644 --- a/src/NodeDev.Core/Nodes/Math/SmallerThanOrEqual.cs +++ b/src/NodeDev.Core/Nodes/Math/SmallerThanOrEqual.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using NodeDev.Core.Types; +using NodeDev.Core.Types; using System.Linq.Expressions; namespace NodeDev.Core.Nodes.Math; diff --git a/src/NodeDev.Core/Nodes/Math/Subtract.cs b/src/NodeDev.Core/Nodes/Math/Subtract.cs index 77c7fb4..c581558 100644 --- a/src/NodeDev.Core/Nodes/Math/Subtract.cs +++ b/src/NodeDev.Core/Nodes/Math/Subtract.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using System.Linq.Expressions; +using System.Linq.Expressions; namespace NodeDev.Core.Nodes.Math; diff --git a/src/NodeDev.Core/Nodes/Math/TwoOperationMath.cs b/src/NodeDev.Core/Nodes/Math/TwoOperationMath.cs index 8e09243..212fdd2 100644 --- a/src/NodeDev.Core/Nodes/Math/TwoOperationMath.cs +++ b/src/NodeDev.Core/Nodes/Math/TwoOperationMath.cs @@ -1,11 +1,5 @@ using NodeDev.Core.Connections; using NodeDev.Core.Types; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; namespace NodeDev.Core.Nodes.Math { @@ -22,7 +16,7 @@ public TwoOperationMath(Graph graph, string? id = null) : base(graph, id) } public override List GenericConnectionTypeDefined(Connection connection) - { + { if (Inputs.Count(x => x.Type is RealType t && (t.BackendType.IsPrimitive || t.BackendType == typeof(string))) == 2) { if (!Outputs[0].Type.HasUndefinedGenerics) @@ -34,7 +28,7 @@ public override List GenericConnectionTypeDefined(Connection connect Type resultingType; // both inputs are basic types like int or float // find the type with the highest precision - if(type1 == typeof(string) || type2 == typeof(string)) + if (type1 == typeof(string) || type2 == typeof(string)) resultingType = typeof(string); else if (type1 == typeof(decimal) || type2 == typeof(decimal)) resultingType = typeof(decimal); @@ -44,9 +38,9 @@ public override List GenericConnectionTypeDefined(Connection connect resultingType = typeof(float); else if (type1 == typeof(long) || type2 == typeof(long)) resultingType = typeof(long); - else if(type1 == typeof(uint) && type2 == typeof(uint)) + else if (type1 == typeof(uint) && type2 == typeof(uint)) resultingType = typeof(uint); - else if((type1 == typeof(uint) && type2 == typeof(int)) || (type2 == typeof(uint) && type1 == typeof(int))) + else if ((type1 == typeof(uint) && type2 == typeof(int)) || (type2 == typeof(uint) && type1 == typeof(int))) resultingType = typeof(long); else resultingType = typeof(int); @@ -62,7 +56,7 @@ public override List GenericConnectionTypeDefined(Connection connect var correctOne = operations.FirstOrDefault(x => x.GetParameters().Length == 2 && x.GetParameters()[1].ParameterType == type2.BackendType); - if(correctOne != null) + if (correctOne != null) { Outputs[0].UpdateTypeAndTextboxVisibility(TypeFactory.Get(correctOne.ReturnType, null), overrideInitialType: true); return new() { Outputs[0] }; diff --git a/src/NodeDev.Core/Nodes/Math/Xor.cs b/src/NodeDev.Core/Nodes/Math/Xor.cs index 24457fe..eede431 100644 --- a/src/NodeDev.Core/Nodes/Math/Xor.cs +++ b/src/NodeDev.Core/Nodes/Math/Xor.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using System.Linq.Expressions; +using System.Linq.Expressions; namespace NodeDev.Core.Nodes.Math; diff --git a/src/NodeDev.Core/Nodes/MethodCall.cs b/src/NodeDev.Core/Nodes/MethodCall.cs index 0e00504..3beb46c 100644 --- a/src/NodeDev.Core/Nodes/MethodCall.cs +++ b/src/NodeDev.Core/Nodes/MethodCall.cs @@ -4,7 +4,6 @@ using NodeDev.Core.Types; using System.Buffers; using System.Linq.Expressions; -using System.Reflection; using System.Text.Json; namespace NodeDev.Core.Nodes; @@ -161,12 +160,12 @@ internal void OnNewMethodParameter(NodeClassMethodParameter newParameter) { Inputs.Add(new Connection(newParameter.Name, this, newParameter.ParameterType)); - Graph.RaiseGraphChanged(true); - } + Graph.RaiseGraphChanged(true); + } - #endregion + #endregion - internal override Expression BuildExpression(Dictionary? subChunks, BuildExpressionInfo info) + internal override Expression BuildExpression(Dictionary? subChunks, BuildExpressionInfo info) { if (subChunks != null) throw new Exception("MethodCall.BuildExpression: subChunks should be null as MethodCall never has multiple output paths"); diff --git a/src/NodeDev.Core/Nodes/New.cs b/src/NodeDev.Core/Nodes/New.cs index 16edf45..4374e64 100644 --- a/src/NodeDev.Core/Nodes/New.cs +++ b/src/NodeDev.Core/Nodes/New.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Class; -using NodeDev.Core.Connections; +using NodeDev.Core.Connections; using NodeDev.Core.Types; using System.Linq.Expressions; @@ -7,81 +6,81 @@ namespace NodeDev.Core.Nodes; public class New : NormalFlowNode { - public override string Name - { - get => Outputs[1].Type.HasUndefinedGenerics ? "New ?" : $"New {Outputs[1].Type.FriendlyName}"; - set { } - } + public override string Name + { + get => Outputs[1].Type.HasUndefinedGenerics ? "New ?" : $"New {Outputs[1].Type.FriendlyName}"; + set { } + } - public New(Graph graph, string? id = null) : base(graph, id) - { - Outputs.Add(new("Obj", this, new UndefinedGenericType("T"))); - } + public New(Graph graph, string? id = null) : base(graph, id) + { + Outputs.Add(new("Obj", this, new UndefinedGenericType("T"))); + } - public override IEnumerable AlternatesOverloads - { - get - { - // If we don't know the type yet we are constructing an array, there are no overloads to show - if (Outputs[1].Type is UndefinedGenericType || Outputs[1].Type.IsArray) - return []; + public override IEnumerable AlternatesOverloads + { + get + { + // If we don't know the type yet we are constructing an array, there are no overloads to show + if (Outputs[1].Type is UndefinedGenericType || Outputs[1].Type.IsArray) + return []; - if (Outputs[1].Type is RealType realType) - { - var constructors = realType.BackendType.GetConstructors(); - return constructors.Select(x => new AlternateOverload(Outputs[1].Type, x.GetParameters().Select(y => new RealMethodParameterInfo(y, TypeFactory, realType)).OfType().ToList())).ToList(); - } - else if (Outputs[1].Type is NodeClassType nodeClassType) - return [new(Outputs[1].Type, [])]; // for now, we don't handle custom constructors + if (Outputs[1].Type is RealType realType) + { + var constructors = realType.BackendType.GetConstructors(); + return constructors.Select(x => new AlternateOverload(Outputs[1].Type, x.GetParameters().Select(y => new RealMethodParameterInfo(y, TypeFactory, realType)).OfType().ToList())).ToList(); + } + else if (Outputs[1].Type is NodeClassType nodeClassType) + return [new(Outputs[1].Type, [])]; // for now, we don't handle custom constructors - else - throw new Exception("Unknown type in New node: " + Outputs[1].Type.Name); - } - } + else + throw new Exception("Unknown type in New node: " + Outputs[1].Type.Name); + } + } - public override List GenericConnectionTypeDefined(Connection connection) - { - if (Outputs[1].Type.IsArray) - { - Inputs.Add(new("Length", this, TypeFactory.Get())); - } - else - { - var constructor = AlternatesOverloads.First(); + public override List GenericConnectionTypeDefined(Connection connection) + { + if (Outputs[1].Type.IsArray) + { + Inputs.Add(new("Length", this, TypeFactory.Get())); + } + else + { + var constructor = AlternatesOverloads.First(); - Inputs.AddRange(constructor.Parameters.Select(x => new Connection(x.Name ?? "??", this, x.ParameterType))); - } + Inputs.AddRange(constructor.Parameters.Select(x => new Connection(x.Name ?? "??", this, x.ParameterType))); + } - return []; - } + return []; + } - public override void SelectOverload(AlternateOverload overload, out List newConnections, out List removedConnections) - { - removedConnections = Inputs.Skip(1).ToList(); - Inputs.RemoveRange(1, Inputs.Count - 1); + public override void SelectOverload(AlternateOverload overload, out List newConnections, out List removedConnections) + { + removedConnections = Inputs.Skip(1).ToList(); + Inputs.RemoveRange(1, Inputs.Count - 1); - newConnections = overload.Parameters.Select(x => new Connection(x.Name, this, x.ParameterType)).ToList(); - Inputs.AddRange(newConnections); - } + newConnections = overload.Parameters.Select(x => new Connection(x.Name, this, x.ParameterType)).ToList(); + Inputs.AddRange(newConnections); + } - internal override Expression BuildExpression(Dictionary? subChunks, BuildExpressionInfo info) - { - var type = Outputs[1].Type.MakeRealType(); - if (type.IsArray) - { - var length = info.LocalVariables[Inputs[1]]; - return Expression.Assign(info.LocalVariables[Outputs[1]], Expression.NewArrayBounds(type.GetElementType()!, length)); - } - else - { - var argumentTypes = Inputs.Skip(1).Select(x => x.Type.MakeRealType()).ToArray(); - var constructor = type.GetConstructor(argumentTypes); + internal override Expression BuildExpression(Dictionary? subChunks, BuildExpressionInfo info) + { + var type = Outputs[1].Type.MakeRealType(); + if (type.IsArray) + { + var length = info.LocalVariables[Inputs[1]]; + return Expression.Assign(info.LocalVariables[Outputs[1]], Expression.NewArrayBounds(type.GetElementType()!, length)); + } + else + { + var argumentTypes = Inputs.Skip(1).Select(x => x.Type.MakeRealType()).ToArray(); + var constructor = type.GetConstructor(argumentTypes); - if (constructor == null) - throw new Exception($"Constructor not found: {Outputs[1].Type.FriendlyName}"); + if (constructor == null) + throw new Exception($"Constructor not found: {Outputs[1].Type.FriendlyName}"); - var arguments = Inputs.Skip(1).Select(x => info.LocalVariables[x]).ToArray(); - return Expression.Assign(info.LocalVariables[Outputs[1]], Expression.New(constructor, arguments)); - } - } + var arguments = Inputs.Skip(1).Select(x => info.LocalVariables[x]).ToArray(); + return Expression.Assign(info.LocalVariables[Outputs[1]], Expression.New(constructor, arguments)); + } + } } diff --git a/src/NodeDev.Core/Nodes/NoFlowNode.cs b/src/NodeDev.Core/Nodes/NoFlowNode.cs index c84b7b1..0fc7289 100644 --- a/src/NodeDev.Core/Nodes/NoFlowNode.cs +++ b/src/NodeDev.Core/Nodes/NoFlowNode.cs @@ -1,14 +1,9 @@ using NodeDev.Core.Connections; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NodeDev.Core.Nodes { - public abstract class NoFlowNode : Node - { + public abstract class NoFlowNode : Node + { public override string TitleColor => "lightgreen"; public override string GetExecOutputPathId(string pathId, Connection execOutput) @@ -18,12 +13,12 @@ public override string GetExecOutputPathId(string pathId, Connection execOutput) public override bool DoesOutputPathAllowDeadEnd(Connection execOutput) => throw new NotImplementedException(); - public override bool DoesOutputPathAllowMerge(Connection execOutput) => throw new NotImplementedException(); + public override bool DoesOutputPathAllowMerge(Connection execOutput) => throw new NotImplementedException(); - public NoFlowNode(Graph graph, string? id = null) : base(graph, id) - { - } + public NoFlowNode(Graph graph, string? id = null) : base(graph, id) + { + } - public override bool IsFlowNode => false; - } + public override bool IsFlowNode => false; + } } diff --git a/src/NodeDev.Core/Nodes/Node.cs b/src/NodeDev.Core/Nodes/Node.cs index 2d8abf4..aac2c35 100644 --- a/src/NodeDev.Core/Nodes/Node.cs +++ b/src/NodeDev.Core/Nodes/Node.cs @@ -1,15 +1,7 @@ -using NodeDev.Core.Class; -using NodeDev.Core.Connections; +using NodeDev.Core.Connections; using NodeDev.Core.NodeDecorations; using NodeDev.Core.Types; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; -using System.Xml.Linq; namespace NodeDev.Core.Nodes { @@ -68,7 +60,7 @@ public Node(Graph graph, string? id = null) /// /// Called before the generic type are set on every connections /// - public virtual void OnBeforeGenericTypeDefined(IReadOnlyDictionary changedGenerics) {} + public virtual void OnBeforeGenericTypeDefined(IReadOnlyDictionary changedGenerics) { } public record class AlternateOverload(TypeBase ReturnType, List Parameters); @@ -79,7 +71,7 @@ public record class AlternateOverload(TypeBase ReturnType, List /// The connection that was generic, it is not generic anymore public virtual List GenericConnectionTypeDefined(Connection connection) - { + { return []; } @@ -125,11 +117,11 @@ public virtual void SelectOverload(AlternateOverload overload, out List - /// Returns true if this node breaks a dead end. These are usually "Return", "Break", "Continue", etc. - /// This will allow a dead end in places where it shouldn't be allowed, such as a "Branch" node. - /// - public virtual bool BreaksDeadEnd => false; + /// + /// Returns true if this node breaks a dead end. These are usually "Return", "Break", "Continue", etc. + /// This will allow a dead end in places where it shouldn't be allowed, such as a "Branch" node. + /// + public virtual bool BreaksDeadEnd => false; public class InfiniteLoopException(Node node) : Exception { diff --git a/src/NodeDev.Core/Nodes/NormalFlowNode.cs b/src/NodeDev.Core/Nodes/NormalFlowNode.cs index 82a6a69..e5bca89 100644 --- a/src/NodeDev.Core/Nodes/NormalFlowNode.cs +++ b/src/NodeDev.Core/Nodes/NormalFlowNode.cs @@ -1,39 +1,34 @@ using NodeDev.Core.Connections; using NodeDev.Core.Types; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NodeDev.Core.Nodes { - /// - /// Nodes that want to implement a normal flow should inherit from this class. - /// It'll automatically add a input exec and output exec. - /// - public abstract class NormalFlowNode : Node - { + /// + /// Nodes that want to implement a normal flow should inherit from this class. + /// It'll automatically add a input exec and output exec. + /// + public abstract class NormalFlowNode : Node + { public override string TitleColor => "lightblue"; - public override bool IsFlowNode => true; + public override bool IsFlowNode => true; public override string GetExecOutputPathId(string pathId, Connection execOutput) { - if(execOutput != Outputs[0]) + if (execOutput != Outputs[0]) throw new InvalidOperationException("Invalid exec output connection."); - return pathId; // no need to change the pathId, we're just flowing through like a->b->c + return pathId; // no need to change the pathId, we're just flowing through like a->b->c } - public override bool DoesOutputPathAllowDeadEnd(Connection execOutput) => false; // A normal flow node should not allow dead ends, unless a parent allowed it. + public override bool DoesOutputPathAllowDeadEnd(Connection execOutput) => false; // A normal flow node should not allow dead ends, unless a parent allowed it. - public override bool DoesOutputPathAllowMerge(Connection execOutput) => throw new NotImplementedException(); // Since there is only one exec, it doesn't make sense to talk about merging, nothing to merge after all. + public override bool DoesOutputPathAllowMerge(Connection execOutput) => throw new NotImplementedException(); // Since there is only one exec, it doesn't make sense to talk about merging, nothing to merge after all. - protected NormalFlowNode(Graph graph, string? id = null) : base(graph, id) - { - Inputs.Add(new("Exec", this, TypeFactory.ExecType)); - Outputs.Add(new("Exec", this, TypeFactory.ExecType)); - } - } + protected NormalFlowNode(Graph graph, string? id = null) : base(graph, id) + { + Inputs.Add(new("Exec", this, TypeFactory.ExecType)); + Outputs.Add(new("Exec", this, TypeFactory.ExecType)); + } + } } diff --git a/src/NodeDev.Core/Nodes/Null.cs b/src/NodeDev.Core/Nodes/Null.cs index 7b85407..1a6a3a3 100644 --- a/src/NodeDev.Core/Nodes/Null.cs +++ b/src/NodeDev.Core/Nodes/Null.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Connections; -using NodeDev.Core.Types; +using NodeDev.Core.Types; using System.Linq.Expressions; namespace NodeDev.Core.Nodes; diff --git a/src/NodeDev.Core/Nodes/Self.cs b/src/NodeDev.Core/Nodes/Self.cs index 9d819fb..fb85510 100644 --- a/src/NodeDev.Core/Nodes/Self.cs +++ b/src/NodeDev.Core/Nodes/Self.cs @@ -1,7 +1,4 @@ -using NodeDev.Core.Connections; -using System.Linq.Expressions; - -namespace NodeDev.Core.Nodes; +namespace NodeDev.Core.Nodes; public class Self : NoFlowNode { diff --git a/src/NodeDev.Core/Nodes/SetPropertyOrField.cs b/src/NodeDev.Core/Nodes/SetPropertyOrField.cs index 6fc718d..307828e 100644 --- a/src/NodeDev.Core/Nodes/SetPropertyOrField.cs +++ b/src/NodeDev.Core/Nodes/SetPropertyOrField.cs @@ -1,5 +1,4 @@ -using NodeDev.Core.Class; -using NodeDev.Core.Connections; +using NodeDev.Core.Connections; using NodeDev.Core.Types; using System.Linq.Expressions; using System.Reflection; diff --git a/src/NodeDev.Core/Nodes/SetVariableValueNode.cs b/src/NodeDev.Core/Nodes/SetVariableValueNode.cs index c208cdb..8224cdc 100644 --- a/src/NodeDev.Core/Nodes/SetVariableValueNode.cs +++ b/src/NodeDev.Core/Nodes/SetVariableValueNode.cs @@ -6,16 +6,16 @@ namespace NodeDev.Core.Nodes; public class SetVariableValueNode : NormalFlowNode { - public SetVariableValueNode(Graph graph, string? id = null) : base(graph, id) - { - Name = "Set Variable"; + public SetVariableValueNode(Graph graph, string? id = null) : base(graph, id) + { + Name = "Set Variable"; - var type = new UndefinedGenericType("T"); - Inputs.Add(new Connection("Variable", this, type)); - Inputs.Add(new Connection("Value", this, type)); - } + var type = new UndefinedGenericType("T"); + Inputs.Add(new Connection("Variable", this, type)); + Inputs.Add(new Connection("Value", this, type)); + } - public override string Name + public override string Name { get { @@ -33,7 +33,7 @@ public override string Name } internal override Expression BuildExpression(Dictionary? subChunks, BuildExpressionInfo info) - { - return Expression.Assign(info.LocalVariables[Inputs[1]], info.LocalVariables[Inputs[2]]); - } + { + return Expression.Assign(info.LocalVariables[Inputs[1]], info.LocalVariables[Inputs[2]]); + } } diff --git a/src/NodeDev.Core/Nodes/TypeOf.cs b/src/NodeDev.Core/Nodes/TypeOf.cs index 3756cb9..78a2046 100644 --- a/src/NodeDev.Core/Nodes/TypeOf.cs +++ b/src/NodeDev.Core/Nodes/TypeOf.cs @@ -51,7 +51,7 @@ public override IEnumerable GetUndefinedGenericTypes() public override void OnBeforeGenericTypeDefined(IReadOnlyDictionary changedGenerics) { - if(Type is not UndefinedGenericType undefined) + if (Type is not UndefinedGenericType undefined) return; if (changedGenerics.TryGetValue(undefined.Name, out var newType)) diff --git a/src/NodeDev.Core/Project.cs b/src/NodeDev.Core/Project.cs index 865486a..eb0f886 100644 --- a/src/NodeDev.Core/Project.cs +++ b/src/NodeDev.Core/Project.cs @@ -8,8 +8,8 @@ using System.Reactive.Subjects; using System.Reflection; using System.Reflection.Emit; -using System.Reflection.Metadata.Ecma335; using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; using System.Text.Json; using System.Text.Json.Nodes; diff --git a/src/NodeDev.Core/ProjectSettings.cs b/src/NodeDev.Core/ProjectSettings.cs index 2f7dcc3..a5e25a0 100644 --- a/src/NodeDev.Core/ProjectSettings.cs +++ b/src/NodeDev.Core/ProjectSettings.cs @@ -2,5 +2,5 @@ public record class ProjectSettings() { - public static ProjectSettings Default { get; } = new(); + public static ProjectSettings Default { get; } = new(); } diff --git a/src/NodeDev.Core/Types/ExecType.cs b/src/NodeDev.Core/Types/ExecType.cs index 0793eba..4eca3dd 100644 --- a/src/NodeDev.Core/Types/ExecType.cs +++ b/src/NodeDev.Core/Types/ExecType.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace NodeDev.Core.Types +namespace NodeDev.Core.Types { public class ExecType : TypeBase { @@ -14,8 +8,8 @@ public class ExecType : TypeBase public override bool IsClass => false; - public override bool IsExec => true; - + public override bool IsExec => true; + public override TypeBase[] Generics => Array.Empty(); public override string FriendlyName => "Exec"; @@ -24,13 +18,13 @@ public class ExecType : TypeBase public override TypeBase[] Interfaces => throw new NotImplementedException(); - public override bool IsArray => false; + public override bool IsArray => false; - public override TypeBase ArrayInnerType => throw new Exception("Can't call ArrayInnerType on ExecType"); + public override TypeBase ArrayInnerType => throw new Exception("Can't call ArrayInnerType on ExecType"); - public override TypeBase ArrayType => throw new Exception("Can't call ArrayType on ExecType"); + public override TypeBase ArrayType => throw new Exception("Can't call ArrayType on ExecType"); - public override IEnumerable GetMembers() => throw new NotImplementedException(); + public override IEnumerable GetMembers() => throw new NotImplementedException(); public override IEnumerable GetMethods() => []; diff --git a/src/NodeDev.Core/Types/IMethodInfo.cs b/src/NodeDev.Core/Types/IMethodInfo.cs index cf832d4..7ea1698 100644 --- a/src/NodeDev.Core/Types/IMethodInfo.cs +++ b/src/NodeDev.Core/Types/IMethodInfo.cs @@ -1,28 +1,27 @@ using NodeDev.Core.Nodes; -using NodeDev.Core.Types; using System.Reflection; namespace NodeDev.Core.Types; public interface IMethodInfo { - public string Name { get; } + public string Name { get; } - public bool IsStatic { get; } + public bool IsStatic { get; } - public TypeBase DeclaringType { get; } + public TypeBase DeclaringType { get; } - public TypeBase ReturnType { get; } + public TypeBase ReturnType { get; } - public IEnumerable GetParameters(); + public IEnumerable GetParameters(); - public Node.AlternateOverload AlternateOverload() => new(ReturnType, GetParameters().ToList()); + public Node.AlternateOverload AlternateOverload() => new(ReturnType, GetParameters().ToList()); - /// - /// Create MethodInfo for the current method. - /// If this is called on a NodeClassMethod, this assumes the project has already generated the classes types. - /// - /// + /// + /// Create MethodInfo for the current method. + /// If this is called on a NodeClassMethod, this assumes the project has already generated the classes types. + /// + /// public MethodInfo CreateMethodInfo(); public MethodAttributes Attributes { get; } @@ -30,14 +29,14 @@ public interface IMethodInfo public interface IMethodParameterInfo { - public string Name { get; } + public string Name { get; } - public TypeBase ParameterType { get; } + public TypeBase ParameterType { get; } - public bool IsOut { get; } + public bool IsOut { get; } - public string FriendlyFormat() - { - return $"{(IsOut ? "out " : "")}{ParameterType.FriendlyName} {Name}"; - } + public string FriendlyFormat() + { + return $"{(IsOut ? "out " : "")}{ParameterType.FriendlyName} {Name}"; + } } diff --git a/src/NodeDev.Core/Types/NodeClassArrayType.cs b/src/NodeDev.Core/Types/NodeClassArrayType.cs index f9ab0b6..34afffa 100644 --- a/src/NodeDev.Core/Types/NodeClassArrayType.cs +++ b/src/NodeDev.Core/Types/NodeClassArrayType.cs @@ -1,113 +1,107 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace NodeDev.Core.Types +namespace NodeDev.Core.Types { - public class NodeClassArrayType : TypeBase - { - public readonly NodeClassType InnerNodeClassType; + public class NodeClassArrayType : TypeBase + { + public readonly NodeClassType InnerNodeClassType; - public readonly int NbArrayLevels; + public readonly int NbArrayLevels; - public NodeClassArrayType(NodeClassType innerClassType, int nbArrayLevels) - { - if (nbArrayLevels == 0) - throw new ArgumentException("NodeClassArrayType cannot have 0 array level. That imples 'not array', in which case NodeClassType should be used instead", nameof(nbArrayLevels)); + public NodeClassArrayType(NodeClassType innerClassType, int nbArrayLevels) + { + if (nbArrayLevels == 0) + throw new ArgumentException("NodeClassArrayType cannot have 0 array level. That imples 'not array', in which case NodeClassType should be used instead", nameof(nbArrayLevels)); - InnerNodeClassType = innerClassType; - NbArrayLevels = nbArrayLevels; - } + InnerNodeClassType = innerClassType; + NbArrayLevels = nbArrayLevels; + } - public override string Name => InnerNodeClassType.Name + GetArrayString(NbArrayLevels); + public override string Name => InnerNodeClassType.Name + GetArrayString(NbArrayLevels); - public override string FullName => InnerNodeClassType.FullName + GetArrayString(NbArrayLevels); + public override string FullName => InnerNodeClassType.FullName + GetArrayString(NbArrayLevels); - public override TypeBase[] Generics => InnerNodeClassType.Generics; + public override TypeBase[] Generics => InnerNodeClassType.Generics; - public override TypeBase? BaseType => InnerNodeClassType.NodeClass.TypeFactory.Get(); + public override TypeBase? BaseType => InnerNodeClassType.NodeClass.TypeFactory.Get(); - public override TypeBase[] Interfaces => [InnerNodeClassType.NodeClass.TypeFactory.Get(typeof(IReadOnlyList<>), [ArrayInnerType])]; + public override TypeBase[] Interfaces => [InnerNodeClassType.NodeClass.TypeFactory.Get(typeof(IReadOnlyList<>), [ArrayInnerType])]; - public override bool IsArray => true; + public override bool IsArray => true; - public override string FriendlyName => InnerNodeClassType.FriendlyName + GetArrayString(NbArrayLevels); + public override string FriendlyName => InnerNodeClassType.FriendlyName + GetArrayString(NbArrayLevels); - public override TypeBase ArrayInnerType => NbArrayLevels == 1 ? InnerNodeClassType : new NodeClassArrayType(InnerNodeClassType, NbArrayLevels - 1); + public override TypeBase ArrayInnerType => NbArrayLevels == 1 ? InnerNodeClassType : new NodeClassArrayType(InnerNodeClassType, NbArrayLevels - 1); - public override TypeBase ArrayType => new NodeClassArrayType(InnerNodeClassType, NbArrayLevels + 1); + public override TypeBase ArrayType => new NodeClassArrayType(InnerNodeClassType, NbArrayLevels + 1); - public override TypeBase CloneWithGenerics(TypeBase[] newGenerics) - { - return new NodeClassArrayType((NodeClassType)InnerNodeClassType.CloneWithGenerics(newGenerics), NbArrayLevels); - } + public override TypeBase CloneWithGenerics(TypeBase[] newGenerics) + { + return new NodeClassArrayType((NodeClassType)InnerNodeClassType.CloneWithGenerics(newGenerics), NbArrayLevels); + } - public override IEnumerable GetMembers() - { - return []; - } + public override IEnumerable GetMembers() + { + return []; + } - public override IEnumerable GetMethods() - { - return []; - } + public override IEnumerable GetMethods() + { + return []; + } - public override IEnumerable GetMethods(string name) - { - return []; - } + public override IEnumerable GetMethods(string name) + { + return []; + } - public override bool IsSameBackend(TypeBase typeBase) - { - if(typeBase is not NodeClassArrayType nodeClassArrayType) - return false; + public override bool IsSameBackend(TypeBase typeBase) + { + if (typeBase is not NodeClassArrayType nodeClassArrayType) + return false; - return NbArrayLevels == nodeClassArrayType.NbArrayLevels && InnerNodeClassType.IsSameBackend(nodeClassArrayType.InnerNodeClassType); - } + return NbArrayLevels == nodeClassArrayType.NbArrayLevels && InnerNodeClassType.IsSameBackend(nodeClassArrayType.InnerNodeClassType); + } - public override Type MakeRealType() - { - var realBaseType = InnerNodeClassType.MakeRealType(); + public override Type MakeRealType() + { + var realBaseType = InnerNodeClassType.MakeRealType(); - for(int i = 0; i < NbArrayLevels; i++) - realBaseType = realBaseType.MakeArrayType(); + for (int i = 0; i < NbArrayLevels; i++) + realBaseType = realBaseType.MakeArrayType(); - return realBaseType; - } + return realBaseType; + } - private record class SerializedNodeClassArrayType(string InnerNodeClassType, int NbArrayLevels); - protected internal override string Serialize() - { - return System.Text.Json.JsonSerializer.Serialize(new SerializedNodeClassArrayType(InnerNodeClassType.Serialize(), NbArrayLevels)); - } + private record class SerializedNodeClassArrayType(string InnerNodeClassType, int NbArrayLevels); + protected internal override string Serialize() + { + return System.Text.Json.JsonSerializer.Serialize(new SerializedNodeClassArrayType(InnerNodeClassType.Serialize(), NbArrayLevels)); + } - public new static NodeClassArrayType Deserialize(TypeFactory typeFactory, string serializedString) - { - var deserialized = System.Text.Json.JsonSerializer.Deserialize(serializedString); - if(deserialized == null) - throw new ArgumentException("Failed to deserialize NodeClassArrayType"); + public new static NodeClassArrayType Deserialize(TypeFactory typeFactory, string serializedString) + { + var deserialized = System.Text.Json.JsonSerializer.Deserialize(serializedString); + if (deserialized == null) + throw new ArgumentException("Failed to deserialize NodeClassArrayType"); - return new NodeClassArrayType(NodeClassType.Deserialize(typeFactory, deserialized.InnerNodeClassType), deserialized.NbArrayLevels); - } + return new NodeClassArrayType(NodeClassType.Deserialize(typeFactory, deserialized.InnerNodeClassType), deserialized.NbArrayLevels); + } - public static string GetArrayString(int nbArrayLevels) - { - if (nbArrayLevels == 0) - return string.Empty; + public static string GetArrayString(int nbArrayLevels) + { + if (nbArrayLevels == 0) + return string.Empty; - var str = string.Create(nbArrayLevels * 2, nbArrayLevels, static (span, nbLevels) => - { - for (int i = 0; i < span.Length; i += 2) - { - span[i] = '['; - span[i + 1] = ']'; - } - }); + var str = string.Create(nbArrayLevels * 2, nbArrayLevels, static (span, nbLevels) => + { + for (int i = 0; i < span.Length; i += 2) + { + span[i] = '['; + span[i + 1] = ']'; + } + }); - return str; - } - } + return str; + } + } } diff --git a/src/NodeDev.Core/Types/NodeClassType.cs b/src/NodeDev.Core/Types/NodeClassType.cs index 2c1ad83..79c023e 100644 --- a/src/NodeDev.Core/Types/NodeClassType.cs +++ b/src/NodeDev.Core/Types/NodeClassType.cs @@ -1,89 +1,84 @@ using NodeDev.Core.Class; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NodeDev.Core.Types { - public class NodeClassType : TypeBase - { - public readonly NodeClass NodeClass; + public class NodeClassType : TypeBase + { + public readonly NodeClass NodeClass; - public NodeClassType(NodeClass nodeClass, TypeBase[] generics) - { - if (generics.Length != 0) - throw new NotImplementedException("Generics are not supported yet for NodeClass"); + public NodeClassType(NodeClass nodeClass, TypeBase[] generics) + { + if (generics.Length != 0) + throw new NotImplementedException("Generics are not supported yet for NodeClass"); - NodeClass = nodeClass; - Generics = generics; - } + NodeClass = nodeClass; + Generics = generics; + } - public override string Name => NodeClass.Name; + public override string Name => NodeClass.Name; - public override string FullName => NodeClass.Namespace + "." + NodeClass.Name; + public override string FullName => NodeClass.Namespace + "." + NodeClass.Name; - public override TypeBase[] Generics { get; } + public override TypeBase[] Generics { get; } - override public TypeBase? BaseType => null; + override public TypeBase? BaseType => null; - public override string FriendlyName => Name; + public override string FriendlyName => Name; - public override bool IsArray => false; + public override bool IsArray => false; - public override TypeBase ArrayInnerType => throw new NotImplementedException(); + public override TypeBase ArrayInnerType => throw new NotImplementedException(); - public override TypeBase ArrayType => new NodeClassArrayType(this, 1); + public override TypeBase ArrayType => new NodeClassArrayType(this, 1); - public override TypeBase[] Interfaces => []; + public override TypeBase[] Interfaces => []; - public override IEnumerable GetMembers() => NodeClass.Properties; + public override IEnumerable GetMembers() => NodeClass.Properties; - public NodeClassType GetNonArray() - { - if(!IsArray) - return this; + public NodeClassType GetNonArray() + { + if (!IsArray) + return this; - return NodeClass.Project.GetNodeClassType(NodeClass, Generics); - } + return NodeClass.Project.GetNodeClassType(NodeClass, Generics); + } - internal protected override string Serialize() - { - return FullName; - } + internal protected override string Serialize() + { + return FullName; + } - public override TypeBase CloneWithGenerics(TypeBase[] newGenerics) - { - if (Generics.Length != newGenerics.Length) - throw new ArgumentException("Generics count mismatch"); + public override TypeBase CloneWithGenerics(TypeBase[] newGenerics) + { + if (Generics.Length != newGenerics.Length) + throw new ArgumentException("Generics count mismatch"); - return new NodeClassType(NodeClass, newGenerics); - } + return new NodeClassType(NodeClass, newGenerics); + } - public new static NodeClassType Deserialize(TypeFactory typeFactory, string typeName) - { - return typeFactory.Project.GetNodeClassType(typeFactory.Project.Classes.First(x => x.Namespace + "." + x.Name == typeName)); - } + public new static NodeClassType Deserialize(TypeFactory typeFactory, string typeName) + { + return typeFactory.Project.GetNodeClassType(typeFactory.Project.Classes.First(x => x.Namespace + "." + x.Name == typeName)); + } - public override IEnumerable GetMethods() - { - return NodeClass.Methods; - } + public override IEnumerable GetMethods() + { + return NodeClass.Methods; + } - public override IEnumerable GetMethods(string name) - { - return NodeClass.Methods.Where(x => x.Name == name); - } + public override IEnumerable GetMethods(string name) + { + return NodeClass.Methods.Where(x => x.Name == name); + } - public override Type MakeRealType() - { - return NodeClass.Project.GetCreatedClassType(NodeClass); - } + public override Type MakeRealType() + { + return NodeClass.Project.GetCreatedClassType(NodeClass); + } - public override bool IsSameBackend(TypeBase typeBase) - { - return typeBase is NodeClassType nodeClassType && nodeClassType.NodeClass == NodeClass; - } - } + public override bool IsSameBackend(TypeBase typeBase) + { + return typeBase is NodeClassType nodeClassType && nodeClassType.NodeClass == NodeClass; + } + } } diff --git a/src/NodeDev.Core/Types/RealMethodInfo.cs b/src/NodeDev.Core/Types/RealMethodInfo.cs index 62f1d07..e27f6f1 100644 --- a/src/NodeDev.Core/Types/RealMethodInfo.cs +++ b/src/NodeDev.Core/Types/RealMethodInfo.cs @@ -20,7 +20,7 @@ public TypeBase ReturnType { get { - if(Method.ReturnType.IsGenericParameter) + if (Method.ReturnType.IsGenericParameter) return DeclaringRealType.Generics[Method.ReturnType.GenericParameterPosition]; return TypeFactory.Get(Method.ReturnType, null); } diff --git a/src/NodeDev.Core/Types/RealMethodParameterInfo.cs b/src/NodeDev.Core/Types/RealMethodParameterInfo.cs index 9a30b70..415fb6e 100644 --- a/src/NodeDev.Core/Types/RealMethodParameterInfo.cs +++ b/src/NodeDev.Core/Types/RealMethodParameterInfo.cs @@ -26,7 +26,7 @@ private IEnumerable ReplaceGenericsRecursively(Type type) { if (generic.IsGenericMethodParameter) yield return TypeFactory.Get(generic, null); - else if (generic.IsGenericParameter) + else if (generic.IsGenericParameter) yield return DeclaringRealType.Generics[generic.GenericParameterPosition]; else if (!generic.IsGenericType) // we've reached the end, that one is good, we can simply return it yield return TypeFactory.Get(generic, null); @@ -42,9 +42,9 @@ public TypeBase ParameterType { get { - if (ParameterInfo.ParameterType.IsGenericMethodParameter) + if (ParameterInfo.ParameterType.IsGenericMethodParameter) return TypeFactory.Get(ParameterInfo.ParameterType, null); - else if (ParameterInfo.ParameterType.IsGenericParameter) + else if (ParameterInfo.ParameterType.IsGenericParameter) return DeclaringRealType.Generics[ParameterInfo.ParameterType.GenericParameterPosition]; else if (ParameterInfo.ParameterType.IsGenericType) { @@ -56,5 +56,5 @@ public TypeBase ParameterType } } - public bool IsOut => ParameterInfo.IsOut; + public bool IsOut => ParameterInfo.IsOut; } diff --git a/src/NodeDev.Core/Types/RealType.cs b/src/NodeDev.Core/Types/RealType.cs index acc6bf8..52a84d2 100644 --- a/src/NodeDev.Core/Types/RealType.cs +++ b/src/NodeDev.Core/Types/RealType.cs @@ -1,10 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; +using System.Diagnostics; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace NodeDev.Core.Types; @@ -28,13 +23,13 @@ public class RealType : TypeBase private TypeBase[]? _Interfaces; public override TypeBase[] Interfaces => _Interfaces ?? InitializeInterfaces(); - public override bool IsArray => BackendType.IsArray; + public override bool IsArray => BackendType.IsArray; - public override TypeBase ArrayType => TypeFactory.Get(BackendType.MakeArrayType(), Generics); + public override TypeBase ArrayType => TypeFactory.Get(BackendType.MakeArrayType(), Generics); - public override TypeBase ArrayInnerType => IsArray ? TypeFactory.Get(BackendType.GetElementType()!, Generics) : throw new Exception("Can't call ArrayInnerType on non-array type"); + public override TypeBase ArrayInnerType => IsArray ? TypeFactory.Get(BackendType.GetElementType()!, Generics) : throw new Exception("Can't call ArrayInnerType on non-array type"); - public override bool IsIn(int genericIndex) => BackendType.GetGenericArguments()[genericIndex].IsGenericParameter && (BackendType.GetGenericArguments()[genericIndex].GenericParameterAttributes & System.Reflection.GenericParameterAttributes.Contravariant) != System.Reflection.GenericParameterAttributes.None; + public override bool IsIn(int genericIndex) => BackendType.GetGenericArguments()[genericIndex].IsGenericParameter && (BackendType.GetGenericArguments()[genericIndex].GenericParameterAttributes & System.Reflection.GenericParameterAttributes.Contravariant) != System.Reflection.GenericParameterAttributes.None; public override bool IsOut(int genericIndex) => BackendType.GetGenericArguments()[genericIndex].IsGenericParameter && (BackendType.GetGenericArguments()[genericIndex].GenericParameterAttributes & System.Reflection.GenericParameterAttributes.Covariant) != System.Reflection.GenericParameterAttributes.None; @@ -94,11 +89,11 @@ public override string? DefaultTextboxValue private List GetMembers_() { - var properties = BackendType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); - var fields = BackendType.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); + var properties = BackendType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); + var fields = BackendType.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); - return properties.Select(x => new RealMemberInfo(x, this)).Concat(fields.Select(x => new RealMemberInfo(x, this))).ToList(); - } + return properties.Select(x => new RealMemberInfo(x, this)).Concat(fields.Select(x => new RealMemberInfo(x, this))).ToList(); + } public override TypeBase CloneWithGenerics(TypeBase[] newGenerics) { @@ -174,11 +169,11 @@ private string GetFriendlyName(Type t) public override string FriendlyName => GetFriendlyName(BackendType); private List GetMethods_() - { - return BackendType.GetMethods().Select(x => new RealMethodInfo(TypeFactory, x, this)).ToList(); - } + { + return BackendType.GetMethods().Select(x => new RealMethodInfo(TypeFactory, x, this)).ToList(); + } - public override IEnumerable GetMethods() + public override IEnumerable GetMethods() { return Methods.Value; } @@ -191,10 +186,10 @@ public override IEnumerable GetMethods(string name) internal RealType(TypeFactory typeFactory, Type backendType, TypeBase[]? generics) { TypeFactory = typeFactory; - Members = new Lazy>(GetMembers_); + Members = new Lazy>(GetMembers_); Methods = new Lazy>(GetMethods_); - if (generics == null) + if (generics == null) { if (backendType.IsGenericType && !backendType.IsConstructedGenericType) throw new Exception("Unable to create real type with undefined generics. To do so you must manually specify the generics through the 'generics' parameter in the RealType constructor"); diff --git a/src/NodeDev.Core/Types/TypeBase.cs b/src/NodeDev.Core/Types/TypeBase.cs index 1db7ee0..4510d5e 100644 --- a/src/NodeDev.Core/Types/TypeBase.cs +++ b/src/NodeDev.Core/Types/TypeBase.cs @@ -1,11 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Text; using System.Text.Json; -using System.Threading.Tasks; namespace NodeDev.Core.Types; diff --git a/src/NodeDev.Core/Types/TypeFactory.cs b/src/NodeDev.Core/Types/TypeFactory.cs index f4c4228..d519917 100644 --- a/src/NodeDev.Core/Types/TypeFactory.cs +++ b/src/NodeDev.Core/Types/TypeFactory.cs @@ -13,8 +13,8 @@ public class TypeFactory "System.Text", "System.Threading", "System.Threading.Tasks", - "System.Diagnostics", - }; + "System.Diagnostics", + }; private Dictionary> TypeCorrespondances = new() { ["System.Int32"] = new() { "int" }, @@ -97,7 +97,7 @@ public RealType Get(Type type, TypeBase[]? generics) public string? CreateBaseFromUserInput(string typeName, out TypeBase? type) { int nbArray = 0; - while(typeName.EndsWith("[]")) + while (typeName.EndsWith("[]")) { ++nbArray; typeName = typeName[..^2]; @@ -137,10 +137,10 @@ public RealType Get(Type type, TypeBase[]? generics) if (nodeClass != null) { type = nodeClass.ClassTypeBase; - for (int i = 0; i < nbArray; ++i) - type = type.ArrayType; + for (int i = 0; i < nbArray; ++i) + type = type.ArrayType; - return null; + return null; } } @@ -179,10 +179,10 @@ public RealType Get(Type type, TypeBase[]? generics) // create the generic type type = Get(baseType, genericArgsTypes); - for (int i = 0; i < nbArray; ++i) - type = type.ArrayType; + for (int i = 0; i < nbArray; ++i) + type = type.ArrayType; - return null; + return null; } #endregion diff --git a/src/NodeDev.Core/Types/UndefinedGenericType.cs b/src/NodeDev.Core/Types/UndefinedGenericType.cs index ef1f829..e1153c2 100644 --- a/src/NodeDev.Core/Types/UndefinedGenericType.cs +++ b/src/NodeDev.Core/Types/UndefinedGenericType.cs @@ -6,87 +6,87 @@ namespace NodeDev.Core.Types; public class UndefinedGenericType : TypeBase { - private record class SerializedUndefinedGenericType(string Name, int NbArrayLevels = 0); + private record class SerializedUndefinedGenericType(string Name, int NbArrayLevels = 0); - public int NbArrayLevels { get; } + public int NbArrayLevels { get; } - public override string Name { get; } + public override string Name { get; } - public override string FullName { get; } + public override string FullName { get; } - public override TypeBase[] Generics => []; + public override TypeBase[] Generics => []; - public override string FriendlyName => Name; + public override string FriendlyName => Name; - public override TypeBase? BaseType => throw new NotImplementedException(); + public override TypeBase? BaseType => throw new NotImplementedException(); - public override TypeBase[] Interfaces => throw new NotImplementedException(); + public override TypeBase[] Interfaces => throw new NotImplementedException(); - public override TypeBase CloneWithGenerics(TypeBase[] newGenerics) => throw new NotImplementedException(); + public override TypeBase CloneWithGenerics(TypeBase[] newGenerics) => throw new NotImplementedException(); - public override IEnumerable GetMembers() => throw new NotImplementedException(); + public override IEnumerable GetMembers() => throw new NotImplementedException(); - public override bool IsArray => NbArrayLevels != 0; + public override bool IsArray => NbArrayLevels != 0; - public override UndefinedGenericType ArrayType => new(UndefinedGenericTypeName, NbArrayLevels + 1); + public override UndefinedGenericType ArrayType => new(UndefinedGenericTypeName, NbArrayLevels + 1); - public override UndefinedGenericType ArrayInnerType => IsArray ? new UndefinedGenericType(UndefinedGenericTypeName, NbArrayLevels - 1) : throw new Exception("Can't call ArrayInnerType on non-array type"); + public override UndefinedGenericType ArrayInnerType => IsArray ? new UndefinedGenericType(UndefinedGenericTypeName, NbArrayLevels - 1) : throw new Exception("Can't call ArrayInnerType on non-array type"); - public readonly string UndefinedGenericTypeName; + public readonly string UndefinedGenericTypeName; - public UndefinedGenericType(string name, int nbArrayLevels = 0) - { - UndefinedGenericTypeName = name; - FullName = Name = name + NodeClassArrayType.GetArrayString(nbArrayLevels); - NbArrayLevels = nbArrayLevels; - } + public UndefinedGenericType(string name, int nbArrayLevels = 0) + { + UndefinedGenericTypeName = name; + FullName = Name = name + NodeClassArrayType.GetArrayString(nbArrayLevels); + NbArrayLevels = nbArrayLevels; + } - /// - /// Simplifies the current undefined generic to match as easily as possible with the other type. - /// T[] to string[] will return string. T to string[] will return string[]. - /// - public TypeBase SimplifyToMatchWith(TypeBase otherType) - { - var thisUndefined = this; - while(thisUndefined.IsArray) - { - if(!otherType.IsArray) - throw new Exception("Can't simplify array to non-array type"); + /// + /// Simplifies the current undefined generic to match as easily as possible with the other type. + /// T[] to string[] will return string. T to string[] will return string[]. + /// + public TypeBase SimplifyToMatchWith(TypeBase otherType) + { + var thisUndefined = this; + while (thisUndefined.IsArray) + { + if (!otherType.IsArray) + throw new Exception("Can't simplify array to non-array type"); - thisUndefined = thisUndefined.ArrayInnerType; - otherType = otherType.ArrayInnerType; - } + thisUndefined = thisUndefined.ArrayInnerType; + otherType = otherType.ArrayInnerType; + } - return otherType; - } + return otherType; + } - public override IEnumerable GetMethods() => []; + public override IEnumerable GetMethods() => []; - public override IEnumerable GetMethods(string name) => []; + public override IEnumerable GetMethods(string name) => []; - #region Serialize / Deserialize + #region Serialize / Deserialize - internal protected override string Serialize() => JsonSerializer.Serialize(new SerializedUndefinedGenericType(UndefinedGenericTypeName, NbArrayLevels)); - public static UndefinedGenericType Deserialize(TypeFactory typeFactory, string serialized) - { - var deserialized = JsonSerializer.Deserialize(serialized) ?? throw new Exception("Unable to deserialize UndefinedGenericType"); + internal protected override string Serialize() => JsonSerializer.Serialize(new SerializedUndefinedGenericType(UndefinedGenericTypeName, NbArrayLevels)); + public static UndefinedGenericType Deserialize(TypeFactory typeFactory, string serialized) + { + var deserialized = JsonSerializer.Deserialize(serialized) ?? throw new Exception("Unable to deserialize UndefinedGenericType"); - return new UndefinedGenericType(deserialized.Name, deserialized.NbArrayLevels); - } + return new UndefinedGenericType(deserialized.Name, deserialized.NbArrayLevels); + } - #endregion + #endregion - public override Type MakeRealType() - { - throw new Exception("Unable to make real type with undefined generics"); - } + public override Type MakeRealType() + { + throw new Exception("Unable to make real type with undefined generics"); + } - public override bool IsSameBackend(TypeBase typeBase) - { - if (typeBase is not UndefinedGenericType undefinedGenericType) - return false; + public override bool IsSameBackend(TypeBase typeBase) + { + if (typeBase is not UndefinedGenericType undefinedGenericType) + return false; - return Name == undefinedGenericType.Name; - } + return Name == undefinedGenericType.Name; + } } diff --git a/src/NodeDev.EndToEndTests/HelperExtensions.cs b/src/NodeDev.EndToEndTests/HelperExtensions.cs index 70e4f02..6600556 100644 --- a/src/NodeDev.EndToEndTests/HelperExtensions.cs +++ b/src/NodeDev.EndToEndTests/HelperExtensions.cs @@ -4,8 +4,8 @@ namespace NodeDev.EndToEndTests; internal static class HelperExtensions { - public static Task WaitForVisible(this ILocator locator, WaitForSelectorState state = WaitForSelectorState.Visible) - { - return locator.WaitForAsync(new() { State = state }); - } + public static Task WaitForVisible(this ILocator locator, WaitForSelectorState state = WaitForSelectorState.Visible) + { + return locator.WaitForAsync(new() { State = state }); + } } diff --git a/src/NodeDev.EndToEndTests/Hooks/Hooks.cs b/src/NodeDev.EndToEndTests/Hooks/Hooks.cs index 0864ed7..a297809 100644 --- a/src/NodeDev.EndToEndTests/Hooks/Hooks.cs +++ b/src/NodeDev.EndToEndTests/Hooks/Hooks.cs @@ -1,5 +1,4 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Playwright; +using Microsoft.Playwright; using System.Diagnostics; namespace NodeDev.EndToEndTests.Hooks; @@ -9,86 +8,86 @@ namespace NodeDev.EndToEndTests.Hooks; [Binding] public class Hooks { - public IPage User { get; private set; } = null!; //-> We'll call this property in the tests - - private static Process App = null!; - private static StreamWriter? StdOutput; - private static StreamWriter? StdError; - - private const int Port = 5166; - private const bool EnableLoggingOfServerOutput = false; - - [BeforeFeature] - public static async Task StartServer() - { - if (EnableLoggingOfServerOutput) - { - var path = "../../../../NodeDev.Blazor.Server/logs"; - Directory.CreateDirectory(path); - - StdOutput = new StreamWriter(File.Open(Path.Combine(path, "logs_std.txt"), FileMode.Create)); - StdError = new StreamWriter(File.Open(Path.Combine(path, "logs_err.txt"), FileMode.Create)); - } - - // start the server using either a environment variable set by the CI, or a default path. - // The default path will work if you're running the tests from Visual Studio. - App = new Process(); - App.StartInfo = new ProcessStartInfo() - { - FileName = "dotnet", - Arguments = $"run --no-build -- --urls http://localhost:{Port}", - WorkingDirectory = "../../../../NodeDev.Blazor.Server", - UseShellExecute = false, - RedirectStandardOutput = EnableLoggingOfServerOutput, - RedirectStandardError = EnableLoggingOfServerOutput - }; - - App.OutputDataReceived += App_OutputDataReceived; - App.ErrorDataReceived += App_ErrorDataReceived; - - App.Start(); - - if (EnableLoggingOfServerOutput) - { - App.BeginOutputReadLine(); - App.BeginErrorReadLine(); - } - - await Task.Delay(1000); - - if(App.HasExited) - { - StdOutput?.Flush(); - StdError?.Flush(); - throw new Exception("Failed to start the server: " + App.ExitCode); - } - } - - private static void App_ErrorDataReceived(object sender, DataReceivedEventArgs e) - { - if (e.Data != null) - StdError?.WriteLine(e.Data); - } - - private static void App_OutputDataReceived(object sender, DataReceivedEventArgs e) - { - if(e.Data != null) - StdOutput?.WriteLine(e.Data); - } - - [BeforeScenario] // -> Notice how we're doing these steps before each scenario - public async Task RegisterSingleInstancePractitioner() - { - //Initialize Playwright - var playwright = await Playwright.CreateAsync(); - - //Initialize a browser - 'Chromium' can be changed to 'Firefox' or 'Web kit - var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions - { - Headless = Environment.GetEnvironmentVariable("HEADLESS") == "true" // -> Use this option to be able to see your test running - }); - //Setup a browser context - var context1 = await browser.NewContextAsync(new() + public IPage User { get; private set; } = null!; //-> We'll call this property in the tests + + private static Process App = null!; + private static StreamWriter? StdOutput; + private static StreamWriter? StdError; + + private const int Port = 5166; + private const bool EnableLoggingOfServerOutput = false; + + [BeforeFeature] + public static async Task StartServer() + { + if (EnableLoggingOfServerOutput) + { + var path = "../../../../NodeDev.Blazor.Server/logs"; + Directory.CreateDirectory(path); + + StdOutput = new StreamWriter(File.Open(Path.Combine(path, "logs_std.txt"), FileMode.Create)); + StdError = new StreamWriter(File.Open(Path.Combine(path, "logs_err.txt"), FileMode.Create)); + } + + // start the server using either a environment variable set by the CI, or a default path. + // The default path will work if you're running the tests from Visual Studio. + App = new Process(); + App.StartInfo = new ProcessStartInfo() + { + FileName = "dotnet", + Arguments = $"run --no-build -- --urls http://localhost:{Port}", + WorkingDirectory = "../../../../NodeDev.Blazor.Server", + UseShellExecute = false, + RedirectStandardOutput = EnableLoggingOfServerOutput, + RedirectStandardError = EnableLoggingOfServerOutput + }; + + App.OutputDataReceived += App_OutputDataReceived; + App.ErrorDataReceived += App_ErrorDataReceived; + + App.Start(); + + if (EnableLoggingOfServerOutput) + { + App.BeginOutputReadLine(); + App.BeginErrorReadLine(); + } + + await Task.Delay(1000); + + if (App.HasExited) + { + StdOutput?.Flush(); + StdError?.Flush(); + throw new Exception("Failed to start the server: " + App.ExitCode); + } + } + + private static void App_ErrorDataReceived(object sender, DataReceivedEventArgs e) + { + if (e.Data != null) + StdError?.WriteLine(e.Data); + } + + private static void App_OutputDataReceived(object sender, DataReceivedEventArgs e) + { + if (e.Data != null) + StdOutput?.WriteLine(e.Data); + } + + [BeforeScenario] // -> Notice how we're doing these steps before each scenario + public async Task RegisterSingleInstancePractitioner() + { + //Initialize Playwright + var playwright = await Playwright.CreateAsync(); + + //Initialize a browser - 'Chromium' can be changed to 'Firefox' or 'Web kit + var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions + { + Headless = Environment.GetEnvironmentVariable("HEADLESS") == "true" // -> Use this option to be able to see your test running + }); + //Setup a browser context + var context1 = await browser.NewContextAsync(new() { ViewportSize = new() { @@ -97,43 +96,43 @@ public async Task RegisterSingleInstancePractitioner() } }); - //Initialize a page on the browser context. - User = await context1.NewPageAsync(); - - for (int i = 0; ; ++i) - { - try - { - await User.GotoAsync($"http://localhost:{Port}"); - break; - } - catch - { - if (i == 60) - throw; - } - - await Task.Delay(1000); - } - } - - - [AfterScenario] // -> Notice how we're doing these steps after each scenario - public static async Task StopServer() - { - App.Kill(true); - while (!App.HasExited) - { - await Task.Delay(100); - } - - if (StdOutput != null && StdError != null) - { - StdOutput.Flush(); - StdError.Flush(); - - StdError.Dispose(); - StdError.Dispose(); - } - } + //Initialize a page on the browser context. + User = await context1.NewPageAsync(); + + for (int i = 0; ; ++i) + { + try + { + await User.GotoAsync($"http://localhost:{Port}"); + break; + } + catch + { + if (i == 60) + throw; + } + + await Task.Delay(1000); + } + } + + + [AfterScenario] // -> Notice how we're doing these steps after each scenario + public static async Task StopServer() + { + App.Kill(true); + while (!App.HasExited) + { + await Task.Delay(100); + } + + if (StdOutput != null && StdError != null) + { + StdOutput.Flush(); + StdError.Flush(); + + StdError.Dispose(); + StdError.Dispose(); + } + } } diff --git a/src/NodeDev.EndToEndTests/ImplicitUsings.cs b/src/NodeDev.EndToEndTests/ImplicitUsings.cs index faa2266..c5abf5a 100644 --- a/src/NodeDev.EndToEndTests/ImplicitUsings.cs +++ b/src/NodeDev.EndToEndTests/ImplicitUsings.cs @@ -1,3 +1 @@ -global using FluentAssertions; -global using NUnit; global using Reqnroll; diff --git a/src/NodeDev.EndToEndTests/Pages/HomePage.cs b/src/NodeDev.EndToEndTests/Pages/HomePage.cs index 476edb1..a5f0801 100644 --- a/src/NodeDev.EndToEndTests/Pages/HomePage.cs +++ b/src/NodeDev.EndToEndTests/Pages/HomePage.cs @@ -4,80 +4,80 @@ namespace NodeDev.EndToEndTests.Pages; public class HomePage { - private readonly IPage _user; + private readonly IPage _user; - public HomePage(Hooks.Hooks hooks) - { - _user = hooks.User; - } + public HomePage(Hooks.Hooks hooks) + { + _user = hooks.User; + } - private ILocator SearchAppBar => _user.Locator("[data-test-id='appBar']"); - private ILocator SearchNewProjectButton => SearchAppBar.Locator("[data-test-id='newProject']"); - private ILocator SearchProjectExplorer => _user.Locator("[data-test-id='projectExplorer']"); - private ILocator SearchProjectExplorerClasses => SearchProjectExplorer.Locator("[data-test-id='projectExplorerClass'] p"); - private ILocator SearchProjectExplorerTabsHeader => _user.Locator("[data-test-id='ProjectExplorerSection'] .mud-tabs-tabbar"); - private ILocator SearchClassExplorer => _user.Locator("[data-test-id='classExplorer']"); - private ILocator SearchSnackBarContainer => _user.Locator("#mud-snackbar-container"); + private ILocator SearchAppBar => _user.Locator("[data-test-id='appBar']"); + private ILocator SearchNewProjectButton => SearchAppBar.Locator("[data-test-id='newProject']"); + private ILocator SearchProjectExplorer => _user.Locator("[data-test-id='projectExplorer']"); + private ILocator SearchProjectExplorerClasses => SearchProjectExplorer.Locator("[data-test-id='projectExplorerClass'] p"); + private ILocator SearchProjectExplorerTabsHeader => _user.Locator("[data-test-id='ProjectExplorerSection'] .mud-tabs-tabbar"); + private ILocator SearchClassExplorer => _user.Locator("[data-test-id='classExplorer']"); + private ILocator SearchSnackBarContainer => _user.Locator("#mud-snackbar-container"); - public async Task CreateNewProject() - { - await SearchNewProjectButton.WaitForVisible(); + public async Task CreateNewProject() + { + await SearchNewProjectButton.WaitForVisible(); - await SearchNewProjectButton.ClickAsync(); + await SearchNewProjectButton.ClickAsync(); - await Task.Delay(100); - } + await Task.Delay(100); + } - public async Task HasClass(string name) - { - await SearchProjectExplorerClasses.GetByText(name).WaitForVisible(); - } + public async Task HasClass(string name) + { + await SearchProjectExplorerClasses.GetByText(name).WaitForVisible(); + } - public async Task ClickClass(string name) - { - await SearchProjectExplorerClasses.GetByText(name).ClickAsync(); - } + public async Task ClickClass(string name) + { + await SearchProjectExplorerClasses.GetByText(name).ClickAsync(); + } - public async Task OpenProjectExplorerProjectTab() - { - await SearchProjectExplorerTabsHeader.GetByText("PROJECT").ClickAsync(); + public async Task OpenProjectExplorerProjectTab() + { + await SearchProjectExplorerTabsHeader.GetByText("PROJECT").ClickAsync(); - await Task.Delay(100); - } + await Task.Delay(100); + } - public async Task OpenProjectExplorerClassTab() - { - await SearchProjectExplorerTabsHeader.GetByText("CLASS").ClickAsync(); + public async Task OpenProjectExplorerClassTab() + { + await SearchProjectExplorerTabsHeader.GetByText("CLASS").ClickAsync(); - await Task.Delay(100); - } + await Task.Delay(100); + } - public async Task FindMethodByName(string name) - { - await OpenProjectExplorerClassTab(); + public async Task FindMethodByName(string name) + { + await OpenProjectExplorerClassTab(); - var locator = SearchClassExplorer.Locator($"[data-test-id='Method'][data-test-method='{name}']"); - return locator; - } + var locator = SearchClassExplorer.Locator($"[data-test-id='Method'][data-test-method='{name}']"); + return locator; + } - public async Task HasMethodByName(string name) - { - var locator = await FindMethodByName(name); + public async Task HasMethodByName(string name) + { + var locator = await FindMethodByName(name); - await locator.WaitForVisible(); - } + await locator.WaitForVisible(); + } - public async Task SaveProject() - { - var saveBtn = SearchAppBar.Locator("[data-test-id='Save']"); + public async Task SaveProject() + { + var saveBtn = SearchAppBar.Locator("[data-test-id='Save']"); - await saveBtn.WaitForVisible(); + await saveBtn.WaitForVisible(); - await saveBtn.ClickAsync(); - } + await saveBtn.ClickAsync(); + } - public async Task SnackBarHasByText(string text) - { - await SearchSnackBarContainer.GetByText(text).WaitForVisible(); - } + public async Task SnackBarHasByText(string text) + { + await SearchSnackBarContainer.GetByText(text).WaitForVisible(); + } } \ No newline at end of file diff --git a/src/NodeDev.EndToEndTests/StepDefinitions/MainPageStepDefinitions.cs b/src/NodeDev.EndToEndTests/StepDefinitions/MainPageStepDefinitions.cs index 635aafa..5258535 100644 --- a/src/NodeDev.EndToEndTests/StepDefinitions/MainPageStepDefinitions.cs +++ b/src/NodeDev.EndToEndTests/StepDefinitions/MainPageStepDefinitions.cs @@ -6,44 +6,44 @@ namespace NodeDev.EndToEndTests.StepDefinitions; [Binding] public sealed class MainPageStepDefinitions { - private readonly IPage User; - private readonly HomePage HomePage; - - public MainPageStepDefinitions(Hooks.Hooks hooks, HomePage homePage) - { - User = hooks.User; - HomePage = homePage; - } - - [Given("I load the default project")] - public async Task GivenILoadTheDefaultProject() - { - await HomePage.CreateNewProject(); - } - - [Then("The {string} method in the {string} class should exist")] - public async Task ThenTheMethodInTheClassShouldExist(string method, string className) - { - await HomePage.OpenProjectExplorerProjectTab(); - - await HomePage.HasClass(className); - - await HomePage.ClickClass(className); - - await HomePage.OpenProjectExplorerClassTab(); - - await HomePage.HasMethodByName(method); - } - - [Given("I save the current project")] - public async Task GivenISaveTheCurrentProject() - { - await HomePage.SaveProject(); - } - - [Then("Snackbar should contain {string}")] - public async Task ThenSnackbarShouldContain(string text) - { - await HomePage.SnackBarHasByText(text); - } + private readonly IPage User; + private readonly HomePage HomePage; + + public MainPageStepDefinitions(Hooks.Hooks hooks, HomePage homePage) + { + User = hooks.User; + HomePage = homePage; + } + + [Given("I load the default project")] + public async Task GivenILoadTheDefaultProject() + { + await HomePage.CreateNewProject(); + } + + [Then("The {string} method in the {string} class should exist")] + public async Task ThenTheMethodInTheClassShouldExist(string method, string className) + { + await HomePage.OpenProjectExplorerProjectTab(); + + await HomePage.HasClass(className); + + await HomePage.ClickClass(className); + + await HomePage.OpenProjectExplorerClassTab(); + + await HomePage.HasMethodByName(method); + } + + [Given("I save the current project")] + public async Task GivenISaveTheCurrentProject() + { + await HomePage.SaveProject(); + } + + [Then("Snackbar should contain {string}")] + public async Task ThenSnackbarShouldContain(string text) + { + await HomePage.SnackBarHasByText(text); + } } diff --git a/src/NodeDev.Tests/EventsTests.cs b/src/NodeDev.Tests/EventsTests.cs index 7a6fac2..966301b 100644 --- a/src/NodeDev.Tests/EventsTests.cs +++ b/src/NodeDev.Tests/EventsTests.cs @@ -1,7 +1,6 @@ using NodeDev.Core; using NodeDev.Core.Class; using NodeDev.Core.Nodes; -using NodeDev.Core.Nodes.Flow; using System.Reactive.Linq; namespace NodeDev.Tests; diff --git a/src/NodeDev.Tests/GraphAnalysisTests.cs b/src/NodeDev.Tests/GraphAnalysisTests.cs index b80e857..399b3f1 100644 --- a/src/NodeDev.Tests/GraphAnalysisTests.cs +++ b/src/NodeDev.Tests/GraphAnalysisTests.cs @@ -93,129 +93,129 @@ public void GraphGetChunks_Branch_NoMerge_Has2Return() [Fact] public void GraphGetChunks_ForEach_Simple_AllowDeadEnd() { - var graph = GetMain("ForEach_Simple"); + var graph = GetMain("ForEach_Simple"); - var chunks = graph.GetChunks(GetEntryExec(graph), true); + var chunks = graph.GetChunks(GetEntryExec(graph), true); - Assert.Single(chunks.Chunks); + Assert.Single(chunks.Chunks); // Foreach as two exec outputs Assert.NotNull(chunks.Chunks[0].SubChunk); Assert.Equal(2, chunks.Chunks[0].SubChunk!.Count); - // First one ends with a dead end on Console.WriteLine, second one with a Return + // First one ends with a dead end on Console.WriteLine, second one with a Return Assert.Equal("Console.WriteLine", chunks.Chunks[0].SubChunk!.ElementAt(0).Value.DeadEndInputs![0].Parent.Name); - Assert.Equal("Return", chunks.Chunks[0].SubChunk!.ElementAt(1).Value.DeadEndInputs![0].Parent.Name); + Assert.Equal("Return", chunks.Chunks[0].SubChunk!.ElementAt(1).Value.DeadEndInputs![0].Parent.Name); - // The global dead end should contain both - Assert.NotNull(chunks.DeadEndInputs); - Assert.Equal(2, chunks.DeadEndInputs.Count); - Assert.Equal("Console.WriteLine", chunks.DeadEndInputs[0]!.Parent.Name); - Assert.Equal("Return", chunks.DeadEndInputs[1]!.Parent.Name); - } + // The global dead end should contain both + Assert.NotNull(chunks.DeadEndInputs); + Assert.Equal(2, chunks.DeadEndInputs.Count); + Assert.Equal("Console.WriteLine", chunks.DeadEndInputs[0]!.Parent.Name); + Assert.Equal("Return", chunks.DeadEndInputs[1]!.Parent.Name); + } - [Fact] + [Fact] public void GraphGetChunks_ForEach_WithInnerBranch_HasSubChunkInLoopExec() { - var graph = GetMain("ForEach_WithInnerBranch"); + var graph = GetMain("ForEach_WithInnerBranch"); - var chunks = graph.GetChunks(GetEntryExec(graph), false); + var chunks = graph.GetChunks(GetEntryExec(graph), false); // one big chunk with everything in it - Assert.Single(chunks.Chunks); + Assert.Single(chunks.Chunks); - // Foreach as two exec outputs - Assert.NotNull(chunks.Chunks[0].SubChunk); - Assert.Equal(2, chunks.Chunks[0].SubChunk!.Count); + // Foreach as two exec outputs + Assert.NotNull(chunks.Chunks[0].SubChunk); + Assert.Equal(2, chunks.Chunks[0].SubChunk!.Count); - // First one ends with a dead end on Console.WriteLine, second one with a Return - Assert.Equal("Console.WriteLine", chunks.Chunks[0].SubChunk!.ElementAt(0).Value.DeadEndInputs![0].Parent.Name); - Assert.Equal("Return", chunks.Chunks[0].SubChunk!.ElementAt(1).Value.DeadEndInputs![0].Parent.Name); + // First one ends with a dead end on Console.WriteLine, second one with a Return + Assert.Equal("Console.WriteLine", chunks.Chunks[0].SubChunk!.ElementAt(0).Value.DeadEndInputs![0].Parent.Name); + Assert.Equal("Return", chunks.Chunks[0].SubChunk!.ElementAt(1).Value.DeadEndInputs![0].Parent.Name); - // Loop has a subchunk with the branch - Assert.NotNull(chunks.Chunks[0].SubChunk!.ElementAt(0).Value.Chunks[0].SubChunk); - Assert.Equal(2, chunks.Chunks[0].SubChunk!.ElementAt(0).Value.Chunks[0].SubChunk!.Count); + // Loop has a subchunk with the branch + Assert.NotNull(chunks.Chunks[0].SubChunk!.ElementAt(0).Value.Chunks[0].SubChunk); + Assert.Equal(2, chunks.Chunks[0].SubChunk!.ElementAt(0).Value.Chunks[0].SubChunk!.Count); - // The global dead end should contain the return and both path of the Branch - Assert.NotNull(chunks.DeadEndInputs); - Assert.Equal(3, chunks.DeadEndInputs.Count); - Assert.Equal("Console.WriteLine", chunks.DeadEndInputs[0]!.Parent.Name); + // The global dead end should contain the return and both path of the Branch + Assert.NotNull(chunks.DeadEndInputs); + Assert.Equal(3, chunks.DeadEndInputs.Count); + Assert.Equal("Console.WriteLine", chunks.DeadEndInputs[0]!.Parent.Name); Assert.Equal("List.Add", chunks.DeadEndInputs[1]!.Parent.Name); - Assert.Equal("Return", chunks.DeadEndInputs[2]!.Parent.Name); - } + Assert.Equal("Return", chunks.DeadEndInputs[2]!.Parent.Name); + } [Fact] public void GraphGetChunks_Branch_Remerging_ShouldHaveTwoChunk() - { - var graph = GetMain("Branch_Remerging"); + { + var graph = GetMain("Branch_Remerging"); - var chunks = graph.GetChunks(GetEntryExec(graph), false); + var chunks = graph.GetChunks(GetEntryExec(graph), false); - Assert.Equal(3, chunks.Chunks.Count); - Assert.NotNull(chunks.DeadEndInputs); - Assert.Single(chunks.DeadEndInputs); - Assert.Equal("Return", chunks.DeadEndInputs[0]!.Parent.Name); - Assert.Equal("Return", chunks.Chunks[2]!.Input.Parent.Name); + Assert.Equal(3, chunks.Chunks.Count); + Assert.NotNull(chunks.DeadEndInputs); + Assert.Single(chunks.DeadEndInputs); + Assert.Equal("Return", chunks.DeadEndInputs[0]!.Parent.Name); + Assert.Equal("Return", chunks.Chunks[2]!.Input.Parent.Name); // The first chunk is the branch Assert.NotNull(chunks.Chunks[0].SubChunk); - Assert.Equal(2, chunks.Chunks[0].SubChunk!.Count); - // First path of the branch has two Console.WriteLine - Assert.Equal("Console.WriteLine", chunks.Chunks[0].SubChunk!.ElementAt(0).Value.Chunks[0].Output!.Parent.Name); - Assert.Equal("Console.WriteLine", chunks.Chunks[0].SubChunk!.ElementAt(0).Value.Chunks[1].Output!.Parent.Name); - // Second path has the List.Add - Assert.Equal("List.Add", chunks.Chunks[0].SubChunk!.ElementAt(1).Value.Chunks[0].Output!.Parent.Name); - // The branch remerge with the Console.ReadLine - Assert.Equal("Console.ReadLine", chunks.Chunks[0].SubChunk!.ElementAt(0).Value.InputMergePoint!.Parent.Name); - Assert.Equal("Console.ReadLine", chunks.Chunks[0].SubChunk!.ElementAt(1).Value.InputMergePoint!.Parent.Name); - - // Second chunk is the Console.ReadLine with Return as dead end - Assert.Equal("Console.ReadLine", chunks.Chunks[1].Output!.Parent.Name); - } + Assert.Equal(2, chunks.Chunks[0].SubChunk!.Count); + // First path of the branch has two Console.WriteLine + Assert.Equal("Console.WriteLine", chunks.Chunks[0].SubChunk!.ElementAt(0).Value.Chunks[0].Output!.Parent.Name); + Assert.Equal("Console.WriteLine", chunks.Chunks[0].SubChunk!.ElementAt(0).Value.Chunks[1].Output!.Parent.Name); + // Second path has the List.Add + Assert.Equal("List.Add", chunks.Chunks[0].SubChunk!.ElementAt(1).Value.Chunks[0].Output!.Parent.Name); + // The branch remerge with the Console.ReadLine + Assert.Equal("Console.ReadLine", chunks.Chunks[0].SubChunk!.ElementAt(0).Value.InputMergePoint!.Parent.Name); + Assert.Equal("Console.ReadLine", chunks.Chunks[0].SubChunk!.ElementAt(1).Value.InputMergePoint!.Parent.Name); + + // Second chunk is the Console.ReadLine with Return as dead end + Assert.Equal("Console.ReadLine", chunks.Chunks[1].Output!.Parent.Name); + } [Fact] public void GraphGetChunks_Branch_InvalidRemerging_Throws() - { - var graph = GetMain("Branch_InvalidRemerging"); + { + var graph = GetMain("Branch_InvalidRemerging"); - Assert.Throws(() => graph.GetChunks(GetEntryExec(graph), false)); - } + Assert.Throws(() => graph.GetChunks(GetEntryExec(graph), false)); + } [Fact] public void GraphGetChunks_Foreach_LoopMerge_Throws() - { - var graph = GetMain("ForEach_LoopMerge"); + { + var graph = GetMain("ForEach_LoopMerge"); - Assert.Throws(() => graph.GetChunks(GetEntryExec(graph), false)); - } + Assert.Throws(() => graph.GetChunks(GetEntryExec(graph), false)); + } [Fact] public void GraphGetChunks_ForEach_WithInnerBranchMerge_DoesntThrow() { - var graph = GetMain("ForEach_WithInnerBranchMerge"); + var graph = GetMain("ForEach_WithInnerBranchMerge"); - var chunks = graph.GetChunks(GetEntryExec(graph), false); + var chunks = graph.GetChunks(GetEntryExec(graph), false); - Assert.Single(chunks.Chunks); - Assert.NotNull(chunks.DeadEndInputs); - Assert.Equal(2, chunks.DeadEndInputs.Count); - Assert.Equal("Console.WriteLine", chunks.DeadEndInputs[0]!.Parent.Name); - Assert.Equal("Return", chunks.DeadEndInputs[1]!.Parent.Name); + Assert.Single(chunks.Chunks); + Assert.NotNull(chunks.DeadEndInputs); + Assert.Equal(2, chunks.DeadEndInputs.Count); + Assert.Equal("Console.WriteLine", chunks.DeadEndInputs[0]!.Parent.Name); + Assert.Equal("Return", chunks.DeadEndInputs[1]!.Parent.Name); - // The loop chunk contains a subchunk for the branch - Assert.NotNull(chunks.Chunks[0].SubChunk); - Assert.Equal(2, chunks.Chunks[0].SubChunk!.Count); - Assert.NotNull(chunks.Chunks[0].SubChunk!.ElementAt(0).Value.DeadEndInputs); - Assert.Single(chunks.Chunks[0].SubChunk!.ElementAt(0).Value.DeadEndInputs!); - Assert.Equal("Console.WriteLine", chunks.Chunks[0].SubChunk!.ElementAt(0).Value.DeadEndInputs![0].Parent.Name); - } + // The loop chunk contains a subchunk for the branch + Assert.NotNull(chunks.Chunks[0].SubChunk); + Assert.Equal(2, chunks.Chunks[0].SubChunk!.Count); + Assert.NotNull(chunks.Chunks[0].SubChunk!.ElementAt(0).Value.DeadEndInputs); + Assert.Single(chunks.Chunks[0].SubChunk!.ElementAt(0).Value.DeadEndInputs!); + Assert.Equal("Console.WriteLine", chunks.Chunks[0].SubChunk!.ElementAt(0).Value.DeadEndInputs![0].Parent.Name); + } [Fact] public void GraphGetChunks_Branch_Multiple_Remerging_Allowed() { - var graph = GetMain("Branch_Multiple_Remerging"); + var graph = GetMain("Branch_Multiple_Remerging"); - var chunks = graph.GetChunks(GetEntryExec(graph), false); + var chunks = graph.GetChunks(GetEntryExec(graph), false); Assert.Equal(3, chunks.Chunks.Count); // After the big first chunk, we have the Console.ReadLine diff --git a/src/NodeDev.Tests/GraphExecutorTests.cs b/src/NodeDev.Tests/GraphExecutorTests.cs index 1f4b8d7..a1f8f3c 100644 --- a/src/NodeDev.Tests/GraphExecutorTests.cs +++ b/src/NodeDev.Tests/GraphExecutorTests.cs @@ -1,7 +1,5 @@ -using MudBlazor; using NodeDev.Core; using NodeDev.Core.Class; -using NodeDev.Core.Connections; using NodeDev.Core.Nodes; using NodeDev.Core.Nodes.Debug; using NodeDev.Core.Nodes.Flow; @@ -171,7 +169,7 @@ public static T Run(Project project, BuildOptions buildOptions, params object process.WaitForExit(); - string? line= null; + string? line = null; while (true) { var newLine = process.StandardOutput.ReadLine(); @@ -189,7 +187,7 @@ public static T Run(Project project, BuildOptions buildOptions, params object finally { // Clean up - if(Directory.Exists(buildOptions.OutputPath)) + if (Directory.Exists(buildOptions.OutputPath)) Directory.Delete(buildOptions.OutputPath, true); } } @@ -289,31 +287,31 @@ public void TestProjectRun(SerializableBuildOptions options) Assert.Equal(1, output); } - // This test validates the TryCatchNode by simulating a scenario where an exception is thrown and caught. - // The test sets up a graph with an entry node, a TryCatchNode, and return nodes for both try and catch blocks. - // The try block attempts to parse an invalid integer string, which throws an exception. - // The catch block returns 1, indicating that the exception was caught. - // The test asserts that the output is 1, confirming that the exception was caught and handled correctly. - [Theory] - [MemberData(nameof(GetBuildOptions))] - public void TestTryCatchNode(SerializableBuildOptions options) - { - var project = new Project(Guid.NewGuid()); - var nodeClass = new NodeClass("Program", "Test", project); - project.Classes.Add(nodeClass); - - var graph = new Graph(); - var method = new NodeClassMethod(nodeClass, "MainInternal", nodeClass.TypeFactory.Get(), graph); - method.IsStatic = true; - nodeClass.Methods.Add(method); - graph.SelfMethod = nodeClass.Methods.First(); - - var entryNode = new EntryNode(graph); - var tryCatchNode = new TryCatchNode(graph); + // This test validates the TryCatchNode by simulating a scenario where an exception is thrown and caught. + // The test sets up a graph with an entry node, a TryCatchNode, and return nodes for both try and catch blocks. + // The try block attempts to parse an invalid integer string, which throws an exception. + // The catch block returns 1, indicating that the exception was caught. + // The test asserts that the output is 1, confirming that the exception was caught and handled correctly. + [Theory] + [MemberData(nameof(GetBuildOptions))] + public void TestTryCatchNode(SerializableBuildOptions options) + { + var project = new Project(Guid.NewGuid()); + var nodeClass = new NodeClass("Program", "Test", project); + project.Classes.Add(nodeClass); + + var graph = new Graph(); + var method = new NodeClassMethod(nodeClass, "MainInternal", nodeClass.TypeFactory.Get(), graph); + method.IsStatic = true; + nodeClass.Methods.Add(method); + graph.SelfMethod = nodeClass.Methods.First(); + + var entryNode = new EntryNode(graph); + var tryCatchNode = new TryCatchNode(graph); tryCatchNode.Outputs[3].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get(), overrideInitialType: true); - graph.AddNode(entryNode, false); - graph.AddNode(tryCatchNode, false); + graph.AddNode(entryNode, false); + graph.AddNode(tryCatchNode, false); // Create local variable var declareVariableNode = new DeclareVariableNode(graph); @@ -333,17 +331,17 @@ public void TestTryCatchNode(SerializableBuildOptions options) catchVariableNode.Inputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get(), true); // Variable catchVariableNode.Inputs[2].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get(), true); // Value catchVariableNode.Inputs[2].UpdateTextboxText("2"); - graph.AddNode(catchVariableNode, false); - graph.Connect(tryCatchNode.Outputs[1], catchVariableNode.Inputs[0], false); + graph.AddNode(catchVariableNode, false); + graph.Connect(tryCatchNode.Outputs[1], catchVariableNode.Inputs[0], false); graph.Connect(declareVariableNode.Outputs[1], catchVariableNode.Inputs[1], false); graph.Connect(catchVariableNode.Outputs[0], returnNode.Inputs[0], false); // Create the try block body var parseNode = new MethodCall(graph); - parseNode.SetMethodTarget(new RealMethodInfo(nodeClass.TypeFactory, typeof(int).GetMethod("Parse", new[] { typeof(string) })!, nodeClass.TypeFactory.Get())); - parseNode.Inputs[1].UpdateTextboxText("invalid"); - graph.AddNode(parseNode, false); - graph.Connect(tryCatchNode.Outputs[0], parseNode.Inputs[0], false); + parseNode.SetMethodTarget(new RealMethodInfo(nodeClass.TypeFactory, typeof(int).GetMethod("Parse", new[] { typeof(string) })!, nodeClass.TypeFactory.Get())); + parseNode.Inputs[1].UpdateTextboxText("invalid"); + graph.AddNode(parseNode, false); + graph.Connect(tryCatchNode.Outputs[0], parseNode.Inputs[0], false); var tryVariableNode = new SetVariableValueNode(graph); tryVariableNode.Inputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get(), true); // Variable @@ -356,11 +354,11 @@ public void TestTryCatchNode(SerializableBuildOptions options) graph.Connect(tryVariableNode.Outputs[0], returnNode.Inputs[0], false); CreateStaticMainWithConversion(nodeClass, method); - + var output = Run(project, options); - Assert.Equal(2, output); - } + Assert.Equal(2, output); + } [Theory] [MemberData(nameof(GetBuildOptions))] diff --git a/src/NodeDev.Tests/GraphManagerServiceTests.cs b/src/NodeDev.Tests/GraphManagerServiceTests.cs index 64680a4..9daf230 100644 --- a/src/NodeDev.Tests/GraphManagerServiceTests.cs +++ b/src/NodeDev.Tests/GraphManagerServiceTests.cs @@ -4,234 +4,229 @@ using NodeDev.Core.Nodes; using NodeDev.Core.Nodes.Flow; using NSubstitute; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NodeDev.Tests; public class GraphManagerServiceTests : NodeDevTestsBase { - [Fact] - public void ConnectTwoExecInOneOutput_ShouldDisconnectFirstExec() - { - var project = Project.CreateNewDefaultProject(out var main); - Assert.NotNull(main.EntryNode); - Assert.Single(main.ReturnNodes); - Assert.Equal(main.EntryNode.Outputs[0].Connections[0], main.ReturnNodes.Single().Inputs[0]); - - // create fake IGraphCanvas - var graphCanvas = Substitute.For(); - graphCanvas.Graph.Returns(main.Graph); - - var graphManager = new GraphManagerService(graphCanvas); - - // create a random method call used to test the connection - var methodCall = new MethodCall(main.Graph); - main.Graph.AddNode(methodCall, false); - - - // This should also disconnect the entry node's existing exec connection - graphManager.AddNewConnectionBetween(main.EntryNode.Outputs[0], methodCall.Inputs[0]); - - // main entry node was disconnected from the other node and is now connected to the method call - Assert.Single(main.EntryNode.Outputs[0].Connections); - Assert.Equal(main.EntryNode.Outputs[0].Connections[0], methodCall.Inputs[0]); - - // return node is not connected to anything - Assert.Empty(main.ReturnNodes.Single().Inputs[0].Connections); - - // check that each connection was updated - graphCanvas.Received().UpdatePortColor(Arg.Is(main.EntryNode.Outputs[0].Connections[0])); - graphCanvas.Received().UpdatePortColor(Arg.Is(methodCall.Inputs[0])); - graphCanvas.Received().UpdatePortColor(Arg.Is(main.ReturnNodes.Single().Inputs[0])); - - // check that the old connection was removed from the graph canvas - graphCanvas.Received().RemoveLinkFromGraphCanvas(Arg.Is(main.EntryNode.Outputs[0]), Arg.Is(main.ReturnNodes.Single().Inputs[0])); - } - - [Fact] - public void ConnectTwoOutputsInOneInput_ShouldDisconnectFirstOutput() - { - var project = Project.CreateNewDefaultProject(out var main); - Assert.NotNull(main.EntryNode); - Assert.Single(main.ReturnNodes); - Assert.Equal(main.EntryNode.Outputs[0].Connections[0], main.ReturnNodes.Single().Inputs[0]); - - // create fake IGraphCanvas - var graphCanvas = Substitute.For(); - graphCanvas.Graph.Returns(main.Graph); - - var graphManager = new GraphManagerService(graphCanvas); - - var addNode1 = AddNewAddNodeToGraph(main.Graph); - var addNode2 = AddNewAddNodeToGraph(main.Graph); - var addNode3 = AddNewAddNodeToGraph(main.Graph); - - // connect output of addNode1 to input of addNode3 - graphManager.AddNewConnectionBetween(addNode1.Outputs[0], addNode3.Inputs[0]); - graphCanvas.Received().UpdatePortColor(Arg.Is(addNode1.Outputs[0])); - graphCanvas.Received().UpdatePortColor(Arg.Is(addNode3.Inputs[0])); - Assert.Single(addNode1.Outputs[0].Connections); - Assert.Single(addNode3.Inputs[0].Connections); - Assert.Equal(addNode1.Outputs[0].Connections[0], addNode3.Inputs[0]); - - // connect output of addNode2 to input of addNode3. It should disconnect the existing connection - graphManager.AddNewConnectionBetween(addNode2.Outputs[0], addNode3.Inputs[0]); - graphCanvas.Received(2).UpdatePortColor(Arg.Is(addNode1.Outputs[0])); // when first adding, then when disconnecting - graphCanvas.Received(1).UpdatePortColor(Arg.Is(addNode2.Outputs[0])); - graphCanvas.Received(3).UpdatePortColor(Arg.Is(addNode3.Inputs[0])); // when first adding, adding a second time, then disconnecting - Assert.Empty(addNode1.Outputs[0].Connections); - Assert.Single(addNode2.Outputs[0].Connections); - Assert.Single(addNode3.Inputs[0].Connections); - Assert.Equal(addNode2.Outputs[0].Connections[0], addNode3.Inputs[0]); - - graphCanvas.Received().RemoveLinkFromGraphCanvas(Arg.Is(addNode1.Outputs[0]), Arg.Is(addNode3.Inputs[0])); - } - - [Fact] - public void ConnectTwoExecOutputsInOneInput_ShouldAllow() - { - var project = Project.CreateNewDefaultProject(out var main); - Assert.NotNull(main.EntryNode); - Assert.Single(main.ReturnNodes); - Assert.Equal(main.EntryNode.Outputs[0].Connections[0], main.ReturnNodes.Single().Inputs[0]); - - // create fake IGraphCanvas - var graphCanvas = Substitute.For(); - graphCanvas.Graph.Returns(main.Graph); - - var graphManager = new GraphManagerService(graphCanvas); - - // create a random method call used to test the connection - var methodCall = new MethodCall(main.Graph); - main.Graph.AddNode(methodCall, false); - - // connect output of addNode1 to input of addNode3 - graphManager.AddNewConnectionBetween(methodCall.Outputs[0], main.ReturnNodes.Single().Inputs[0]); - graphCanvas.Received().UpdatePortColor(Arg.Is(methodCall.Outputs[0])); - graphCanvas.Received().UpdatePortColor(Arg.Is(main.ReturnNodes.Single().Inputs[0])); - Assert.Single(main.EntryNode.Outputs[0].Connections); - Assert.Equal(2, main.ReturnNodes.Single().Inputs[0].Connections.Count); - Assert.Single(methodCall.Outputs[0].Connections); - Assert.Equal(methodCall.Outputs[0].Connections[0], main.ReturnNodes.Single().Inputs[0]); - graphCanvas.DidNotReceiveWithAnyArgs().RemoveLinkFromGraphCanvas(Arg.Any(), Arg.Any()); - } - - [Fact] - public void ConnectArrayToIEnumerableT_ShouldAllow() - { - var project = Project.CreateNewDefaultProject(out var main); - Assert.NotNull(main.EntryNode); - Assert.Single(main.ReturnNodes); - Assert.Equal(main.EntryNode.Outputs[0].Connections[0], main.ReturnNodes.Single().Inputs[0]); - - var typeFactory = main.TypeFactory; - - // create fake IGraphCanvas - var graphCanvas = Substitute.For(); - graphCanvas.Graph.Returns(main.Graph); - - var graphManager = new GraphManagerService(graphCanvas); - - // create a random method call used to test the connection - var methodCall = AddMethodCall(main.Graph, typeFactory.Get(), nameof(Array.Empty)); - methodCall.Outputs[1].UpdateTypeAndTextboxVisibility(typeFactory.Get(), overrideInitialType: true); - - var foreachNode = new ForeachNode(main.Graph); - main.Graph.AddNode(foreachNode, false); - - // connect output of Array.Empty() to input of foreachNode - graphManager.AddNewConnectionBetween(methodCall.Outputs[1], foreachNode.Inputs[1]); - graphCanvas.Received().UpdatePortColor(Arg.Is(methodCall.Outputs[1])); - graphCanvas.Received().UpdatePortColor(Arg.Is(foreachNode.Inputs[1])); - graphCanvas.Received().UpdatePortColor(Arg.Is(foreachNode.Outputs[1])); - Assert.Equal(typeFactory.Get>(), foreachNode.Inputs[1].Type); - Assert.Equal(typeFactory.Get(), foreachNode.Outputs[1].Type); - } - - [Fact] - public void ConnectListArrayToForeach_ShouldPropagateChange() - { - var project = Project.CreateNewDefaultProject(out var main); - Assert.NotNull(main.EntryNode); - Assert.Single(main.ReturnNodes); - Assert.Equal(main.EntryNode.Outputs[0].Connections[0], main.ReturnNodes.Single().Inputs[0]); - - var typeFactory = main.TypeFactory; - - // create fake IGraphCanvas - var graphCanvas = Substitute.For(); - graphCanvas.Graph.Returns(main.Graph); - - var graphManager = new GraphManagerService(graphCanvas); - - // create a random method call used to test the connection - var newListArray = new New(main.Graph); - newListArray.Outputs[1].UpdateTypeAndTextboxVisibility(typeFactory.Get>(), overrideInitialType: true); - newListArray.GenericConnectionTypeDefined(newListArray.Outputs[1]); - - var foreachNode = new ForeachNode(main.Graph); - main.Graph.AddNode(foreachNode, false); - - var foreachNode2 = new ForeachNode(main.Graph); - main.Graph.AddNode(foreachNode2, false); - - // connect output of foreachNode into input of foreachNode2 - graphManager.AddNewConnectionBetween(foreachNode.Outputs[1], foreachNode2.Inputs[1]); - - // connect output of new List to input of foreachNode - graphManager.AddNewConnectionBetween(newListArray.Outputs[1], foreachNode.Inputs[1]); - graphCanvas.Received().UpdatePortColor(Arg.Is(newListArray.Outputs[1])); - graphCanvas.Received().UpdatePortColor(Arg.Is(foreachNode.Inputs[1])); - graphCanvas.Received().UpdatePortColor(Arg.Is(foreachNode.Outputs[1])); - graphCanvas.Received().UpdatePortColor(Arg.Is(foreachNode2.Inputs[1])); - graphCanvas.Received().UpdatePortColor(Arg.Is(foreachNode2.Outputs[1])); - - // Input of foreach node should be IEnumerable, output should be string[] - Assert.Equal(typeFactory.Get>(), foreachNode.Inputs[1].Type); - Assert.Equal(typeFactory.Get(), foreachNode.Outputs[1].Type); - - // Input of foreach node 2 should be string[], output should be string - Assert.Equal(typeFactory.Get>(), foreachNode2.Inputs[1].Type); - Assert.Equal(typeFactory.Get(), foreachNode2.Outputs[1].Type); - } - - [Fact] - public void ConnectArrayToArrayT_ShouldPropagateChange() - { - var project = Project.CreateNewDefaultProject(out var main); - Assert.NotNull(main.EntryNode); - Assert.Single(main.ReturnNodes); - Assert.Equal(main.EntryNode.Outputs[0].Connections[0], main.ReturnNodes.Single().Inputs[0]); - - var typeFactory = main.TypeFactory; - - // create fake IGraphCanvas - var graphCanvas = Substitute.For(); - graphCanvas.Graph.Returns(main.Graph); - - var graphManager = new GraphManagerService(graphCanvas); - - // output string[] - var newArray = new New(main.Graph); - newArray.Outputs[1].UpdateTypeAndTextboxVisibility(typeFactory.Get(), overrideInitialType: true); - newArray.GenericConnectionTypeDefined(newArray.Outputs[1]); - - var arrayGet = new ArrayGet(main.Graph); - main.Graph.AddNode(arrayGet, false); - - // connect output of foreachNode into input of foreachNode2 - graphManager.AddNewConnectionBetween(newArray.Outputs[1], arrayGet.Inputs[0]); - - graphCanvas.Received().UpdatePortColor(Arg.Is(newArray.Outputs[1])); - graphCanvas.Received().UpdatePortColor(Arg.Is(arrayGet.Inputs[0])); - graphCanvas.Received().UpdatePortColor(Arg.Is(arrayGet.Outputs[0])); - - // Input of arrayGet should be string[], output should be string - Assert.Equal(typeFactory.Get(), arrayGet.Inputs[0].Type); - Assert.Equal(typeFactory.Get(), arrayGet.Outputs[0].Type); - } + [Fact] + public void ConnectTwoExecInOneOutput_ShouldDisconnectFirstExec() + { + var project = Project.CreateNewDefaultProject(out var main); + Assert.NotNull(main.EntryNode); + Assert.Single(main.ReturnNodes); + Assert.Equal(main.EntryNode.Outputs[0].Connections[0], main.ReturnNodes.Single().Inputs[0]); + + // create fake IGraphCanvas + var graphCanvas = Substitute.For(); + graphCanvas.Graph.Returns(main.Graph); + + var graphManager = new GraphManagerService(graphCanvas); + + // create a random method call used to test the connection + var methodCall = new MethodCall(main.Graph); + main.Graph.AddNode(methodCall, false); + + + // This should also disconnect the entry node's existing exec connection + graphManager.AddNewConnectionBetween(main.EntryNode.Outputs[0], methodCall.Inputs[0]); + + // main entry node was disconnected from the other node and is now connected to the method call + Assert.Single(main.EntryNode.Outputs[0].Connections); + Assert.Equal(main.EntryNode.Outputs[0].Connections[0], methodCall.Inputs[0]); + + // return node is not connected to anything + Assert.Empty(main.ReturnNodes.Single().Inputs[0].Connections); + + // check that each connection was updated + graphCanvas.Received().UpdatePortColor(Arg.Is(main.EntryNode.Outputs[0].Connections[0])); + graphCanvas.Received().UpdatePortColor(Arg.Is(methodCall.Inputs[0])); + graphCanvas.Received().UpdatePortColor(Arg.Is(main.ReturnNodes.Single().Inputs[0])); + + // check that the old connection was removed from the graph canvas + graphCanvas.Received().RemoveLinkFromGraphCanvas(Arg.Is(main.EntryNode.Outputs[0]), Arg.Is(main.ReturnNodes.Single().Inputs[0])); + } + + [Fact] + public void ConnectTwoOutputsInOneInput_ShouldDisconnectFirstOutput() + { + var project = Project.CreateNewDefaultProject(out var main); + Assert.NotNull(main.EntryNode); + Assert.Single(main.ReturnNodes); + Assert.Equal(main.EntryNode.Outputs[0].Connections[0], main.ReturnNodes.Single().Inputs[0]); + + // create fake IGraphCanvas + var graphCanvas = Substitute.For(); + graphCanvas.Graph.Returns(main.Graph); + + var graphManager = new GraphManagerService(graphCanvas); + + var addNode1 = AddNewAddNodeToGraph(main.Graph); + var addNode2 = AddNewAddNodeToGraph(main.Graph); + var addNode3 = AddNewAddNodeToGraph(main.Graph); + + // connect output of addNode1 to input of addNode3 + graphManager.AddNewConnectionBetween(addNode1.Outputs[0], addNode3.Inputs[0]); + graphCanvas.Received().UpdatePortColor(Arg.Is(addNode1.Outputs[0])); + graphCanvas.Received().UpdatePortColor(Arg.Is(addNode3.Inputs[0])); + Assert.Single(addNode1.Outputs[0].Connections); + Assert.Single(addNode3.Inputs[0].Connections); + Assert.Equal(addNode1.Outputs[0].Connections[0], addNode3.Inputs[0]); + + // connect output of addNode2 to input of addNode3. It should disconnect the existing connection + graphManager.AddNewConnectionBetween(addNode2.Outputs[0], addNode3.Inputs[0]); + graphCanvas.Received(2).UpdatePortColor(Arg.Is(addNode1.Outputs[0])); // when first adding, then when disconnecting + graphCanvas.Received(1).UpdatePortColor(Arg.Is(addNode2.Outputs[0])); + graphCanvas.Received(3).UpdatePortColor(Arg.Is(addNode3.Inputs[0])); // when first adding, adding a second time, then disconnecting + Assert.Empty(addNode1.Outputs[0].Connections); + Assert.Single(addNode2.Outputs[0].Connections); + Assert.Single(addNode3.Inputs[0].Connections); + Assert.Equal(addNode2.Outputs[0].Connections[0], addNode3.Inputs[0]); + + graphCanvas.Received().RemoveLinkFromGraphCanvas(Arg.Is(addNode1.Outputs[0]), Arg.Is(addNode3.Inputs[0])); + } + + [Fact] + public void ConnectTwoExecOutputsInOneInput_ShouldAllow() + { + var project = Project.CreateNewDefaultProject(out var main); + Assert.NotNull(main.EntryNode); + Assert.Single(main.ReturnNodes); + Assert.Equal(main.EntryNode.Outputs[0].Connections[0], main.ReturnNodes.Single().Inputs[0]); + + // create fake IGraphCanvas + var graphCanvas = Substitute.For(); + graphCanvas.Graph.Returns(main.Graph); + + var graphManager = new GraphManagerService(graphCanvas); + + // create a random method call used to test the connection + var methodCall = new MethodCall(main.Graph); + main.Graph.AddNode(methodCall, false); + + // connect output of addNode1 to input of addNode3 + graphManager.AddNewConnectionBetween(methodCall.Outputs[0], main.ReturnNodes.Single().Inputs[0]); + graphCanvas.Received().UpdatePortColor(Arg.Is(methodCall.Outputs[0])); + graphCanvas.Received().UpdatePortColor(Arg.Is(main.ReturnNodes.Single().Inputs[0])); + Assert.Single(main.EntryNode.Outputs[0].Connections); + Assert.Equal(2, main.ReturnNodes.Single().Inputs[0].Connections.Count); + Assert.Single(methodCall.Outputs[0].Connections); + Assert.Equal(methodCall.Outputs[0].Connections[0], main.ReturnNodes.Single().Inputs[0]); + graphCanvas.DidNotReceiveWithAnyArgs().RemoveLinkFromGraphCanvas(Arg.Any(), Arg.Any()); + } + + [Fact] + public void ConnectArrayToIEnumerableT_ShouldAllow() + { + var project = Project.CreateNewDefaultProject(out var main); + Assert.NotNull(main.EntryNode); + Assert.Single(main.ReturnNodes); + Assert.Equal(main.EntryNode.Outputs[0].Connections[0], main.ReturnNodes.Single().Inputs[0]); + + var typeFactory = main.TypeFactory; + + // create fake IGraphCanvas + var graphCanvas = Substitute.For(); + graphCanvas.Graph.Returns(main.Graph); + + var graphManager = new GraphManagerService(graphCanvas); + + // create a random method call used to test the connection + var methodCall = AddMethodCall(main.Graph, typeFactory.Get(), nameof(Array.Empty)); + methodCall.Outputs[1].UpdateTypeAndTextboxVisibility(typeFactory.Get(), overrideInitialType: true); + + var foreachNode = new ForeachNode(main.Graph); + main.Graph.AddNode(foreachNode, false); + + // connect output of Array.Empty() to input of foreachNode + graphManager.AddNewConnectionBetween(methodCall.Outputs[1], foreachNode.Inputs[1]); + graphCanvas.Received().UpdatePortColor(Arg.Is(methodCall.Outputs[1])); + graphCanvas.Received().UpdatePortColor(Arg.Is(foreachNode.Inputs[1])); + graphCanvas.Received().UpdatePortColor(Arg.Is(foreachNode.Outputs[1])); + Assert.Equal(typeFactory.Get>(), foreachNode.Inputs[1].Type); + Assert.Equal(typeFactory.Get(), foreachNode.Outputs[1].Type); + } + + [Fact] + public void ConnectListArrayToForeach_ShouldPropagateChange() + { + var project = Project.CreateNewDefaultProject(out var main); + Assert.NotNull(main.EntryNode); + Assert.Single(main.ReturnNodes); + Assert.Equal(main.EntryNode.Outputs[0].Connections[0], main.ReturnNodes.Single().Inputs[0]); + + var typeFactory = main.TypeFactory; + + // create fake IGraphCanvas + var graphCanvas = Substitute.For(); + graphCanvas.Graph.Returns(main.Graph); + + var graphManager = new GraphManagerService(graphCanvas); + + // create a random method call used to test the connection + var newListArray = new New(main.Graph); + newListArray.Outputs[1].UpdateTypeAndTextboxVisibility(typeFactory.Get>(), overrideInitialType: true); + newListArray.GenericConnectionTypeDefined(newListArray.Outputs[1]); + + var foreachNode = new ForeachNode(main.Graph); + main.Graph.AddNode(foreachNode, false); + + var foreachNode2 = new ForeachNode(main.Graph); + main.Graph.AddNode(foreachNode2, false); + + // connect output of foreachNode into input of foreachNode2 + graphManager.AddNewConnectionBetween(foreachNode.Outputs[1], foreachNode2.Inputs[1]); + + // connect output of new List to input of foreachNode + graphManager.AddNewConnectionBetween(newListArray.Outputs[1], foreachNode.Inputs[1]); + graphCanvas.Received().UpdatePortColor(Arg.Is(newListArray.Outputs[1])); + graphCanvas.Received().UpdatePortColor(Arg.Is(foreachNode.Inputs[1])); + graphCanvas.Received().UpdatePortColor(Arg.Is(foreachNode.Outputs[1])); + graphCanvas.Received().UpdatePortColor(Arg.Is(foreachNode2.Inputs[1])); + graphCanvas.Received().UpdatePortColor(Arg.Is(foreachNode2.Outputs[1])); + + // Input of foreach node should be IEnumerable, output should be string[] + Assert.Equal(typeFactory.Get>(), foreachNode.Inputs[1].Type); + Assert.Equal(typeFactory.Get(), foreachNode.Outputs[1].Type); + + // Input of foreach node 2 should be string[], output should be string + Assert.Equal(typeFactory.Get>(), foreachNode2.Inputs[1].Type); + Assert.Equal(typeFactory.Get(), foreachNode2.Outputs[1].Type); + } + + [Fact] + public void ConnectArrayToArrayT_ShouldPropagateChange() + { + var project = Project.CreateNewDefaultProject(out var main); + Assert.NotNull(main.EntryNode); + Assert.Single(main.ReturnNodes); + Assert.Equal(main.EntryNode.Outputs[0].Connections[0], main.ReturnNodes.Single().Inputs[0]); + + var typeFactory = main.TypeFactory; + + // create fake IGraphCanvas + var graphCanvas = Substitute.For(); + graphCanvas.Graph.Returns(main.Graph); + + var graphManager = new GraphManagerService(graphCanvas); + + // output string[] + var newArray = new New(main.Graph); + newArray.Outputs[1].UpdateTypeAndTextboxVisibility(typeFactory.Get(), overrideInitialType: true); + newArray.GenericConnectionTypeDefined(newArray.Outputs[1]); + + var arrayGet = new ArrayGet(main.Graph); + main.Graph.AddNode(arrayGet, false); + + // connect output of foreachNode into input of foreachNode2 + graphManager.AddNewConnectionBetween(newArray.Outputs[1], arrayGet.Inputs[0]); + + graphCanvas.Received().UpdatePortColor(Arg.Is(newArray.Outputs[1])); + graphCanvas.Received().UpdatePortColor(Arg.Is(arrayGet.Inputs[0])); + graphCanvas.Received().UpdatePortColor(Arg.Is(arrayGet.Outputs[0])); + + // Input of arrayGet should be string[], output should be string + Assert.Equal(typeFactory.Get(), arrayGet.Inputs[0].Type); + Assert.Equal(typeFactory.Get(), arrayGet.Outputs[0].Type); + } } diff --git a/src/NodeDev.Tests/NodeClassTypeCreatorTests.cs b/src/NodeDev.Tests/NodeClassTypeCreatorTests.cs index b53c7ab..7ba533b 100644 --- a/src/NodeDev.Tests/NodeClassTypeCreatorTests.cs +++ b/src/NodeDev.Tests/NodeClassTypeCreatorTests.cs @@ -2,7 +2,6 @@ using NodeDev.Core.Class; using NodeDev.Core.Nodes; using NodeDev.Core.Nodes.Flow; -using NodeDev.Core.Types; using System.Reflection; namespace NodeDev.Tests; diff --git a/src/NodeDev.Tests/NodeDevTestsBase.cs b/src/NodeDev.Tests/NodeDevTestsBase.cs index 75712c4..7363c63 100644 --- a/src/NodeDev.Tests/NodeDevTestsBase.cs +++ b/src/NodeDev.Tests/NodeDevTestsBase.cs @@ -7,28 +7,28 @@ namespace NodeDev.Tests; public class NodeDevTestsBase { - protected Add AddNewAddNodeToGraph(Graph graph) - { - var addNode = new Add(graph); - graph.AddNode(addNode, false); + protected Add AddNewAddNodeToGraph(Graph graph) + { + var addNode = new Add(graph); + graph.AddNode(addNode, false); - addNode.Inputs[0].UpdateTypeAndTextboxVisibility(graph.Project.TypeFactory.Get(), overrideInitialType: true); - addNode.Inputs[1].UpdateTypeAndTextboxVisibility(graph.Project.TypeFactory.Get(), overrideInitialType: true); - addNode.Outputs[0].UpdateTypeAndTextboxVisibility(graph.Project.TypeFactory.Get(), overrideInitialType: true); + addNode.Inputs[0].UpdateTypeAndTextboxVisibility(graph.Project.TypeFactory.Get(), overrideInitialType: true); + addNode.Inputs[1].UpdateTypeAndTextboxVisibility(graph.Project.TypeFactory.Get(), overrideInitialType: true); + addNode.Outputs[0].UpdateTypeAndTextboxVisibility(graph.Project.TypeFactory.Get(), overrideInitialType: true); - return addNode; - } + return addNode; + } - protected MethodCall AddMethodCall(Graph graph, TypeBase type, string methodName, params TypeBase[] args) - { - var methods = type.GetMethods(methodName); + protected MethodCall AddMethodCall(Graph graph, TypeBase type, string methodName, params TypeBase[] args) + { + var methods = type.GetMethods(methodName); - var methodCall = new MethodCall(graph); - graph.AddNode(methodCall, false); + var methodCall = new MethodCall(graph); + graph.AddNode(methodCall, false); - var method = methods.First(x => x.GetParameters().Select(y => y.ParameterType).SequenceEqual(args)); - methodCall.SetMethodTarget(method); + var method = methods.First(x => x.GetParameters().Select(y => y.ParameterType).SequenceEqual(args)); + methodCall.SetMethodTarget(method); - return methodCall; - } + return methodCall; + } } diff --git a/src/NodeDev.Tests/NodeProviderTests.cs b/src/NodeDev.Tests/NodeProviderTests.cs index 1fee79b..e5a2e0c 100644 --- a/src/NodeDev.Tests/NodeProviderTests.cs +++ b/src/NodeDev.Tests/NodeProviderTests.cs @@ -1,12 +1,10 @@ using NodeDev.Core; -using NodeDev.Core.Class; -using NodeDev.Core.Nodes.Flow; namespace NodeDev.Tests; public class NodeProviderTests { - + [Fact] public void TestsNodeMethod() { @@ -15,11 +13,11 @@ public void TestsNodeMethod() project.Classes.Add(graph.SelfClass); - var methods = NodeProvider.Search(project, graph.SelfMethod.Name, null); + var methods = NodeProvider.Search(project, graph.SelfMethod.Name, null); Assert.Contains(methods, x => x is NodeProvider.MethodCallNode methodCall && methodCall.MethodInfo == graph.SelfMethod); - methods = NodeProvider.Search(project, graph.SelfMethod.Name + "asd", null); + methods = NodeProvider.Search(project, graph.SelfMethod.Name + "asd", null); Assert.DoesNotContain(methods, x => x is NodeProvider.MethodCallNode methodCall && methodCall.MethodInfo == graph.SelfMethod); } diff --git a/src/NodeDev.Tests/RealTypeTests.cs b/src/NodeDev.Tests/RealTypeTests.cs index 1806909..88a35de 100644 --- a/src/NodeDev.Tests/RealTypeTests.cs +++ b/src/NodeDev.Tests/RealTypeTests.cs @@ -1,5 +1,4 @@ using NodeDev.Core.Types; -using System.Text.Json; namespace NodeDev.Tests; @@ -38,7 +37,7 @@ public void Constructor_BasicTypeParsing() type = new RealType(typeFactory, typeof(List<>), null); // this should throw an exception since we can't create a RealType with a generic without passing the UndefinedGenericType as argument }); - type = typeFactory.Get(typeof(List), null); + type = typeFactory.Get(typeof(List), null); Assert.Same(typeof(List<>), type.BackendType); } diff --git a/src/NodeDev.Tests/SerializableBuildOptions.cs b/src/NodeDev.Tests/SerializableBuildOptions.cs index 8b76251..8eca09d 100644 --- a/src/NodeDev.Tests/SerializableBuildOptions.cs +++ b/src/NodeDev.Tests/SerializableBuildOptions.cs @@ -6,26 +6,26 @@ namespace NodeDev.Tests; public class SerializableBuildOptions : IXunitSerializable { - public bool Debug; - public SerializableBuildOptions(bool debug) - { - Debug = debug; - } + public bool Debug; + public SerializableBuildOptions(bool debug) + { + Debug = debug; + } - public SerializableBuildOptions() - { } + public SerializableBuildOptions() + { } - public void Deserialize(IXunitSerializationInfo info) - { - Debug = info.GetValue("Debug"); - } + public void Deserialize(IXunitSerializationInfo info) + { + Debug = info.GetValue("Debug"); + } - public void Serialize(IXunitSerializationInfo info) - { - info.AddValue("Debug", Debug); - } + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue("Debug", Debug); + } - // implicit conversion between SerializableBuildOptions and BuildOptions - public static implicit operator BuildOptions(SerializableBuildOptions options) => - new (options.Debug ? BuildExpressionOptions.Debug : BuildExpressionOptions.Release, false, Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); + // implicit conversion between SerializableBuildOptions and BuildOptions + public static implicit operator BuildOptions(SerializableBuildOptions options) => + new(options.Debug ? BuildExpressionOptions.Debug : BuildExpressionOptions.Release, false, Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); } diff --git a/src/NodeDev.Tests/SerializationTests.cs b/src/NodeDev.Tests/SerializationTests.cs index e92eac8..a59a6af 100644 --- a/src/NodeDev.Tests/SerializationTests.cs +++ b/src/NodeDev.Tests/SerializationTests.cs @@ -1,14 +1,12 @@ using NodeDev.Core; -using NodeDev.Core.Class; -using NodeDev.Core.Nodes; namespace NodeDev.Tests; public class SerializationTests { - [Theory] - [MemberData(nameof(GraphExecutorTests.GetBuildOptions), MemberType = typeof(GraphExecutorTests))] - public void TestBasicSerialization(SerializableBuildOptions options) + [Theory] + [MemberData(nameof(GraphExecutorTests.GetBuildOptions), MemberType = typeof(GraphExecutorTests))] + public void TestBasicSerialization(SerializableBuildOptions options) { var graph = GraphExecutorTests.CreateSimpleAddGraph(out _, out _, out _); var project = graph.SelfClass.Project; diff --git a/src/NodeDev.Tests/TypeBaseTests.cs b/src/NodeDev.Tests/TypeBaseTests.cs index f0695ea..b143a15 100644 --- a/src/NodeDev.Tests/TypeBaseTests.cs +++ b/src/NodeDev.Tests/TypeBaseTests.cs @@ -1,5 +1,4 @@ using NodeDev.Core.Types; -using System.Text.Json; namespace NodeDev.Tests; @@ -65,25 +64,25 @@ public void Assignations_IsAssignableTo_Basic() Assert.Null(changedGenericsRight); Assert.True(childList.IsAssignableTo(parentEnumerable, out changedGenericsLeft, out changedGenericsRight)); - Assert.Empty(changedGenericsLeft); - Assert.Empty(changedGenericsRight); + Assert.Empty(changedGenericsLeft); + Assert.Empty(changedGenericsRight); - Assert.False(parentEnumerable.IsAssignableTo(childList, out changedGenericsLeft, out changedGenericsRight)); - Assert.Null(changedGenericsLeft); - Assert.Null(changedGenericsRight); + Assert.False(parentEnumerable.IsAssignableTo(childList, out changedGenericsLeft, out changedGenericsRight)); + Assert.Null(changedGenericsLeft); + Assert.Null(changedGenericsRight); - Assert.True(childListList.IsAssignableTo(parentReadOnlyEnumerable, out changedGenericsLeft, out changedGenericsRight)); - Assert.Empty(changedGenericsLeft); - Assert.Empty(changedGenericsRight); + Assert.True(childListList.IsAssignableTo(parentReadOnlyEnumerable, out changedGenericsLeft, out changedGenericsRight)); + Assert.Empty(changedGenericsLeft); + Assert.Empty(changedGenericsRight); - Assert.False(parentReadOnlyEnumerable.IsAssignableTo(childListList, out changedGenericsLeft, out changedGenericsRight)); - Assert.Null(changedGenericsLeft); - Assert.Null(changedGenericsRight); + Assert.False(parentReadOnlyEnumerable.IsAssignableTo(childListList, out changedGenericsLeft, out changedGenericsRight)); + Assert.Null(changedGenericsLeft); + Assert.Null(changedGenericsRight); - Assert.False(childListList.IsAssignableTo(parentListEnumerable, out changedGenericsLeft, out changedGenericsRight)); - Assert.Null(changedGenericsLeft); - Assert.Null(changedGenericsRight); - } + Assert.False(childListList.IsAssignableTo(parentListEnumerable, out changedGenericsLeft, out changedGenericsRight)); + Assert.Null(changedGenericsLeft); + Assert.Null(changedGenericsRight); + } [Fact] public void Assignations_IsDirectlyAssignable_InOut() @@ -97,9 +96,9 @@ public void Assignations_IsDirectlyAssignable_InOut() Assert.Empty(changedGenericsRight); Assert.False(parentEnumerable.IsDirectlyAssignableTo(childEnumerable, true, out changedGenericsLeft, out changedGenericsRight, out _)); - Assert.Null(changedGenericsLeft); - Assert.Null(changedGenericsRight); - } + Assert.Null(changedGenericsLeft); + Assert.Null(changedGenericsRight); + } [Fact] public void Assignations_IsDirectlyAssignable_Basic() @@ -109,32 +108,32 @@ public void Assignations_IsDirectlyAssignable_Basic() var type = typeFactory.Get(typeof(List), null); Assert.True(type.IsDirectlyAssignableTo(type, true, out var changedGenericsLeft, out var changedGenericsRight, out _)); - Assert.Empty(changedGenericsLeft); - Assert.Empty(changedGenericsRight); + Assert.Empty(changedGenericsLeft); + Assert.Empty(changedGenericsRight); - Assert.True(type.IsDirectlyAssignableTo(typeFactory.Get(typeof(List<>), [new UndefinedGenericType("T")]), true, out changedGenericsLeft, out changedGenericsRight, out _)); - Assert.Empty(changedGenericsLeft); + Assert.True(type.IsDirectlyAssignableTo(typeFactory.Get(typeof(List<>), [new UndefinedGenericType("T")]), true, out changedGenericsLeft, out changedGenericsRight, out _)); + Assert.Empty(changedGenericsLeft); Assert.Single(changedGenericsRight); Assert.Equal("T", changedGenericsRight.First().Key); Assert.Equal(typeof(int), changedGenericsRight.First().Value.MakeRealType()); Assert.False(type.IsDirectlyAssignableTo(typeFactory.Get(typeof(IEnumerable<>), [new UndefinedGenericType("T")]), true, out changedGenericsLeft, out changedGenericsRight, out _)); - Assert.Null(changedGenericsLeft); - Assert.Null(changedGenericsRight); + Assert.Null(changedGenericsLeft); + Assert.Null(changedGenericsRight); - Assert.True(type.IsDirectlyAssignableTo(new UndefinedGenericType("T"), true, out changedGenericsLeft, out changedGenericsRight, out _)); - Assert.Empty(changedGenericsLeft); + Assert.True(type.IsDirectlyAssignableTo(new UndefinedGenericType("T"), true, out changedGenericsLeft, out changedGenericsRight, out _)); + Assert.Empty(changedGenericsLeft); Assert.Single(changedGenericsRight); Assert.Equal("T", changedGenericsRight.First().Key); Assert.Same(type, changedGenericsRight.First().Value); Assert.True(new UndefinedGenericType("T").IsDirectlyAssignableTo(type, true, out changedGenericsLeft, out changedGenericsRight, out _)); - Assert.Empty(changedGenericsRight); - Assert.Single(changedGenericsLeft); - Assert.Equal("T", changedGenericsLeft.First().Key); - Assert.Same(type, changedGenericsLeft.First().Value); + Assert.Empty(changedGenericsRight); + Assert.Single(changedGenericsLeft); + Assert.Equal("T", changedGenericsLeft.First().Key); + Assert.Same(type, changedGenericsLeft.First().Value); - Assert.True(new UndefinedGenericType("T").IsDirectlyAssignableTo(new UndefinedGenericType("T2"), true, out changedGenericsLeft, out changedGenericsRight, out _)); + Assert.True(new UndefinedGenericType("T").IsDirectlyAssignableTo(new UndefinedGenericType("T2"), true, out changedGenericsLeft, out changedGenericsRight, out _)); Assert.Empty(changedGenericsLeft); Assert.Empty(changedGenericsRight); } @@ -164,26 +163,26 @@ public void Assignations_IsDirectlyAssignable_BasicArray() Assert.True(typeArr.IsDirectlyAssignableTo(undefined, true, out changedGenericsLeft, out changedGenericsRight, out _)); Assert.Empty(changedGenericsLeft); Assert.Single(changedGenericsRight); - Assert.Equal("T", changedGenericsRight.First().Key); + Assert.Equal("T", changedGenericsRight.First().Key); Assert.Same(typeArr, changedGenericsRight.First().Value); // int[] -> T[] Assert.True(typeArr.IsDirectlyAssignableTo(undefinedArr, true, out changedGenericsLeft, out changedGenericsRight, out _)); - Assert.Empty(changedGenericsLeft); - Assert.Single(changedGenericsRight); - Assert.Equal("T", changedGenericsRight.First().Key); + Assert.Empty(changedGenericsLeft); + Assert.Single(changedGenericsRight); + Assert.Equal("T", changedGenericsRight.First().Key); Assert.Same(typeArr.ArrayInnerType, changedGenericsRight.First().Value); // T -> int[] Assert.True(undefined.IsDirectlyAssignableTo(typeArr, true, out changedGenericsLeft, out changedGenericsRight, out _)); - Assert.Empty(changedGenericsRight); + Assert.Empty(changedGenericsRight); Assert.Single(changedGenericsLeft); - Assert.Equal("T", changedGenericsLeft.First().Key); + Assert.Equal("T", changedGenericsLeft.First().Key); Assert.Same(typeArr, changedGenericsLeft.First().Value); // T[] -> int[] Assert.True(undefinedArr.IsDirectlyAssignableTo(typeArr, true, out changedGenericsLeft, out changedGenericsRight, out _)); - Assert.Empty(changedGenericsRight); + Assert.Empty(changedGenericsRight); Assert.Single(changedGenericsLeft); Assert.Equal("T", changedGenericsLeft.First().Key); Assert.Same(typeArr.ArrayInnerType, changedGenericsLeft.First().Value); @@ -192,7 +191,7 @@ public void Assignations_IsDirectlyAssignable_BasicArray() Assert.True(undefinedArr.IsDirectlyAssignableTo(undefinedArr, true, out changedGenericsLeft, out changedGenericsRight, out _)); Assert.Empty(changedGenericsLeft); Assert.Empty(changedGenericsRight); - } + } [Fact] public void Assignations_IsAssignableTo_BasicArray() @@ -212,8 +211,8 @@ public void Assignations_IsAssignableTo_BasicArray() Assert.Empty(changedGenericsLeft); Assert.Empty(changedGenericsRight); - // List -> List - Assert.False(typeArr.IsAssignableTo(type, out _, out _)); + // List -> List + Assert.False(typeArr.IsAssignableTo(type, out _, out _)); // List -> List Assert.False(type.IsAssignableTo(typeArr, out _, out _)); @@ -222,7 +221,7 @@ public void Assignations_IsAssignableTo_BasicArray() Assert.True(typeArr.IsAssignableTo(undefined, out changedGenericsLeft, out changedGenericsRight)); Assert.Empty(changedGenericsLeft); Assert.Single(changedGenericsRight); - Assert.Equal("T", changedGenericsRight.First().Key); + Assert.Equal("T", changedGenericsRight.First().Key); Assert.Same(typeArr.Generics[0], changedGenericsRight.First().Value); // List -> List @@ -274,29 +273,29 @@ public void Assignations_IsAssignableTo_ArrayToImplementation() Assert.Empty(changedGenericsLeft); Assert.Empty(changedGenericsRight); - // List -> IEnumerable - Assert.False(typeArr.IsAssignableTo(typeImpl, out _, out _)); + // List -> IEnumerable + Assert.False(typeArr.IsAssignableTo(typeImpl, out _, out _)); // List -> IEnumerable Assert.False(type.IsAssignableTo(typeImplArr, out _, out _)); - // IEnumerable -> List - Assert.False(typeImplArr.IsAssignableTo(type, out _, out _)); + // IEnumerable -> List + Assert.False(typeImplArr.IsAssignableTo(type, out _, out _)); - // IEnumerable -> List - Assert.False(typeImpl.IsAssignableTo(typeArr, out _, out _)); + // IEnumerable -> List + Assert.False(typeImpl.IsAssignableTo(typeArr, out _, out _)); - // IEnumerable -> List - Assert.False(typeImplArr.IsAssignableTo(typeArr, out _, out _)); + // IEnumerable -> List + Assert.False(typeImplArr.IsAssignableTo(typeArr, out _, out _)); - // IEnumerable -> List - Assert.False(typeImpl.IsAssignableTo(type, out _, out _)); + // IEnumerable -> List + Assert.False(typeImpl.IsAssignableTo(type, out _, out _)); - // List -> IEnumerable - Assert.True(typeArr.IsAssignableTo(undefinedImpl, out changedGenericsLeft, out changedGenericsRight)); + // List -> IEnumerable + Assert.True(typeArr.IsAssignableTo(undefinedImpl, out changedGenericsLeft, out changedGenericsRight)); Assert.Empty(changedGenericsLeft); Assert.Single(changedGenericsRight); - Assert.Equal("T", changedGenericsRight.First().Key); + Assert.Equal("T", changedGenericsRight.First().Key); Assert.Same(typeArr.Generics[0], changedGenericsRight.First().Value); // List -> IEnumerable diff --git a/src/NodeDev.Tests/TypeFactoryTests.cs b/src/NodeDev.Tests/TypeFactoryTests.cs index 5f905d2..7bba6d5 100644 --- a/src/NodeDev.Tests/TypeFactoryTests.cs +++ b/src/NodeDev.Tests/TypeFactoryTests.cs @@ -1,5 +1,4 @@ using NodeDev.Core.Types; -using System.Text.Json; namespace NodeDev.Tests; diff --git a/src/NodeDev.Tests/UndefinedGenericTypeTests.cs b/src/NodeDev.Tests/UndefinedGenericTypeTests.cs index e2ec96a..72d429d 100644 --- a/src/NodeDev.Tests/UndefinedGenericTypeTests.cs +++ b/src/NodeDev.Tests/UndefinedGenericTypeTests.cs @@ -30,8 +30,8 @@ public void SerializeUndefinedGenericType_Deserialize() Assert.IsType(deserialized); Assert.Equal(undefinedGenericType.Name, ((UndefinedGenericType)deserialized).Name); - Assert.Equal(undefinedGenericType.NbArrayLevels, ((UndefinedGenericType)deserialized).NbArrayLevels); - } + Assert.Equal(undefinedGenericType.NbArrayLevels, ((UndefinedGenericType)deserialized).NbArrayLevels); + } } \ No newline at end of file