diff --git a/src/.editorconfig b/src/.editorconfig index be36feb..c171c23 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -51,15 +51,15 @@ dotnet_style_operator_placement_when_wrapping = beginning_of_line tab_width = 4 indent_size = 4 end_of_line = crlf -dotnet_style_coalesce_expression = true:suggestion +dotnet_style_coalesce_expression = false:suggestion dotnet_style_null_propagation = true:suggestion dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion dotnet_style_prefer_auto_properties = true:silent dotnet_style_object_initializer = true:suggestion dotnet_style_collection_initializer = true:suggestion dotnet_style_prefer_simplified_boolean_expressions = true:suggestion -dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion -dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion +dotnet_style_prefer_conditional_expression_over_return = false:suggestion dotnet_style_explicit_tuple_names = true:suggestion dotnet_style_prefer_inferred_tuple_names = true:suggestion dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion @@ -69,6 +69,9 @@ dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion dotnet_style_namespace_match_folder = true:suggestion indent_style = tab +# Code Quality +dotnet_code_quality.CA1826.exclude_ordefault_methods = true + [*.cs] csharp_indent_labels = one_less_than_current csharp_space_around_binary_operators = before_and_after @@ -78,7 +81,7 @@ csharp_prefer_braces = true:silent csharp_style_namespace_declarations = block_scoped:silent csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = true:silent -csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_prefer_primary_constructors = false:suggestion csharp_prefer_system_threading_lock = true:suggestion csharp_style_expression_bodied_methods = false:silent csharp_style_expression_bodied_constructors = false:silent diff --git a/src/NodeDev.Blazor/Components/ClassExplorer.razor b/src/NodeDev.Blazor/Components/ClassExplorer.razor index 2428846..183b454 100644 --- a/src/NodeDev.Blazor/Components/ClassExplorer.razor +++ b/src/NodeDev.Blazor/Components/ClassExplorer.razor @@ -202,9 +202,9 @@ }).Result; NodeDev.Core.Types.TypeBase typeBase; - if (result.Data is Type type) + if (result?.Data is Type type) typeBase = Class.TypeFactory.Get(type, null); - else if (result.Data is NodeDev.Core.Types.TypeBase t) + else if (result?.Data is NodeDev.Core.Types.TypeBase t) typeBase = t; else return; @@ -234,7 +234,7 @@ { if (CurrentlyEditingItem!.Type == TreeItemType.Method) { - var method = new Core.Class.NodeClassMethod(Class, Text, Class.TypeFactory.Get(typeof(void), null), new Core.Graph()); + var method = new Core.Class.NodeClassMethod(Class, Text, Class.TypeFactory.Get(typeof(void), null)); Class.AddMethod(method, createEntryAndReturn: true); Items.First(x => x.Value!.Type == TreeItemType.MethodsFolder).Children!.RemoveAll(x => x.Value == CurrentlyEditingItem); diff --git a/src/NodeDev.Blazor/Components/DebuggerConsolePanel.razor.cs b/src/NodeDev.Blazor/Components/DebuggerConsolePanel.razor.cs index b07f286..96cd3df 100644 --- a/src/NodeDev.Blazor/Components/DebuggerConsolePanel.razor.cs +++ b/src/NodeDev.Blazor/Components/DebuggerConsolePanel.razor.cs @@ -78,6 +78,8 @@ private void AddText(string text) public void Dispose() { + GC.SuppressFinalize(this); + GraphExecutionChangedDisposable?.Dispose(); RefreshRequiredDisposable?.Dispose(); diff --git a/src/NodeDev.Blazor/Components/EditMethodMenu.razor b/src/NodeDev.Blazor/Components/EditMethodMenu.razor index 0d185b3..40d86a9 100644 --- a/src/NodeDev.Blazor/Components/EditMethodMenu.razor +++ b/src/NodeDev.Blazor/Components/EditMethodMenu.razor @@ -19,7 +19,7 @@ - + @@ -27,7 +27,7 @@ - + @@ -73,9 +73,9 @@ }).Result; NodeDev.Core.Types.TypeBase typeBase; - if (result.Data is Type type) + if (result?.Data is Type type) typeBase = Method.Class.TypeFactory.Get(type, null); - else if (result.Data is NodeDev.Core.Types.TypeBase t) + else if (result?.Data is NodeDev.Core.Types.TypeBase t) typeBase = t; else return; diff --git a/src/NodeDev.Blazor/Components/GraphCanvas.razor.cs b/src/NodeDev.Blazor/Components/GraphCanvas.razor.cs index 76a26b6..d248f3a 100644 --- a/src/NodeDev.Blazor/Components/GraphCanvas.razor.cs +++ b/src/NodeDev.Blazor/Components/GraphCanvas.razor.cs @@ -21,766 +21,732 @@ namespace NodeDev.Blazor.Components; public partial class GraphCanvas : ComponentBase, IDisposable, IGraphCanvas { - [Parameter, EditorRequired] - public Graph Graph { get; set; } = null!; - - [CascadingParameter] - public Index IndexPage { get; set; } = null!; - - [Inject] - internal DebuggedPathService DebuggedPathService { get; set; } = null!; - - private GraphManagerService GraphManagerService => Graph.Manager; - - private int PopupX = 0; - private int PopupY = 0; - private Vector2 PopupNodePosition; - private Connection? PopupNodeConnection; - private Node? PopupNode; - - private GraphNodeModel? SelectedNodeModel { get; set; } - - private BlazorDiagram Diagram { get; set; } = null!; - - #region OnInitialized - - protected override void OnInitialized() - { - base.OnInitialized(); - - Graph.GraphCanvas = this; - - var options = new BlazorDiagramOptions - { - GridSize = 30, - AllowMultiSelection = true, - Zoom = - { - Enabled = true, - Inverse = true - }, - Links = - { - DefaultRouter = new NormalRouter(), - DefaultPathGenerator = new SmoothPathGeneratorWithDirectVertices() - }, - }; - Diagram = new BlazorDiagram(options); - Diagram.RegisterComponent(); - Diagram.KeyDown += Diagram_KeyDown; - - Diagram.Nodes.Removed += OnNodeRemoved; - Diagram.Links.Added += x => OnConnectionAdded(x, false); - Diagram.Links.Removed += OnConnectionRemoved; - Diagram.SelectionChanged += SelectionChanged; - } + [Parameter, EditorRequired] + public Graph Graph { get; set; } = null!; + + [CascadingParameter] + public Index IndexPage { get; set; } = null!; + + [Inject] + internal DebuggedPathService DebuggedPathService { get; set; } = null!; + + private GraphManagerService GraphManagerService => Graph.Manager; + + private int PopupX = 0; + private int PopupY = 0; + private Vector2 PopupNodePosition; + private Connection? PopupNodeConnection; + private Node? PopupNode; + + private BlazorDiagram Diagram { get; set; } = null!; + + #region OnInitialized + + protected override void OnInitialized() + { + base.OnInitialized(); + + Graph.GraphCanvas = this; + + var options = new BlazorDiagramOptions + { + GridSize = 30, + AllowMultiSelection = true, + Zoom = + { + Enabled = true, + Inverse = true + }, + Links = + { + DefaultRouter = new NormalRouter(), + DefaultPathGenerator = new SmoothPathGeneratorWithDirectVertices() + }, + }; + Diagram = new BlazorDiagram(options); + Diagram.RegisterComponent(); + Diagram.KeyDown += Diagram_KeyDown; + + Diagram.Nodes.Removed += OnNodeRemoved; + Diagram.Links.Added += x => OnConnectionAdded(x, false); + Diagram.Links.Removed += OnConnectionRemoved; + Diagram.SelectionChanged += SelectionChanged; + } + + #endregion + + #region OnAfterRenderAsync + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); - #endregion - - #region OnAfterRenderAsync + if (firstRender) + { + await Task.Delay(100); + Diagram.Batch(InitializeCanvasWithGraphNodes); - protected override async Task OnAfterRenderAsync(bool firstRender) - { - await base.OnAfterRenderAsync(firstRender); + GraphChangedSubscription = Graph.SelfClass.Project.GraphChanged.Where(x => x.RequireUIRefresh && x.Graph == Graph).AcceptThenSample(TimeSpan.FromMilliseconds(250)).Subscribe(OnGraphChangedFromCore); + NodeExecutingSubscription = Graph.SelfClass.Project.GraphNodeExecuting.Where(x => x.Executor.Graph == Graph).Buffer(TimeSpan.FromMilliseconds(250)).Subscribe(OnGraphNodeExecuting); + NodeExecutedSubscription = Graph.SelfClass.Project.GraphNodeExecuted.Where(x => x.Executor.Graph == Graph).Sample(TimeSpan.FromMilliseconds(250)).Subscribe(OnGraphNodeExecuted); + } + } - if (firstRender) - { - await Task.Delay(100); - await Graph.Invoke(() => Diagram.Batch(InitializeCanvasWithGraphNodes)); + #endregion - GraphChangedSubscription = Graph.SelfClass.Project.GraphChanged.Where(x => x.RequireUIRefresh && x.Graph == Graph).AcceptThenSample(TimeSpan.FromMilliseconds(250)).Subscribe(OnGraphChangedFromCore); - NodeExecutingSubscription = Graph.SelfClass.Project.GraphNodeExecuting.Where(x => x.Executor.Graph == Graph).Buffer(TimeSpan.FromMilliseconds(250)).Subscribe(OnGraphNodeExecuting); - NodeExecutedSubscription = Graph.SelfClass.Project.GraphNodeExecuted.Where(x => x.Executor.Graph == Graph).Sample(TimeSpan.FromMilliseconds(250)).Subscribe(OnGraphNodeExecuted); - } - } + #region OnGraphNodeExecuting / OnGraphNodeExecuted - #endregion + private void OnGraphNodeExecuting(IList<(GraphExecutor Executor, Node Node, Connection Exec)> options) + { + InvokeAsync(() => + { + foreach (var option in options.DistinctBy(x => x.Exec)) + { + var nodeModel = Diagram.Nodes.OfType().FirstOrDefault(x => x.Node == option.Node); + if (nodeModel == null) + return; - #region OnGraphNodeExecuting / OnGraphNodeExecuted + _ = nodeModel.OnNodeExecuting(option.Exec); + } + }); + } - private void OnGraphNodeExecuting(IList<(GraphExecutor Executor, Node Node, Connection Exec)> options) - { - InvokeAsync(() => - { - foreach (var option in options.DistinctBy(x => x.Exec)) - { - var nodeModel = Diagram.Nodes.OfType().FirstOrDefault(x => x.Node == option.Node); - if (nodeModel == null) - return; + private void OnGraphNodeExecuted((GraphExecutor Executor, Node Node, Connection Exec) options) + { + InvokeAsync(() => + { + var nodeModel = Diagram.Nodes.OfType().FirstOrDefault(x => x.Node == options.Node); + if (nodeModel == null) + return; - _ = nodeModel.OnNodeExecuting(option.Exec); - } - }); - } + nodeModel.OnNodeExecuted(options.Exec); + }); + } - private void OnGraphNodeExecuted((GraphExecutor Executor, Node Node, Connection Exec) options) - { - InvokeAsync(() => - { - var nodeModel = Diagram.Nodes.OfType().FirstOrDefault(x => x.Node == options.Node); - if (nodeModel == null) - return; + #endregion - nodeModel.OnNodeExecuted(options.Exec); - }); - } + #region OnGraphChangedFromCore / RefreshAll - #endregion + private void OnGraphChangedFromCore((Graph, bool) _) + { + InvokeAsync(() => + { + UpdateNodes(); // update all the nodes + + StateHasChanged(); + }); + } - #region OnGraphChangedFromCore / RefreshAll + #endregion - private void OnGraphChangedFromCore((Graph, bool) _) - { - InvokeAsync(() => - { - UpdateNodes(Graph.Nodes.Values); // update all the nodes - - StateHasChanged(); - }); - } - - #endregion - - #region UpdateConnectionType - - public void UpdatePortColor(Connection connection) - { - var node = Diagram.Nodes.OfType().FirstOrDefault(x => x.Node == connection.Parent); - if (node == null) - return; - - var port = node.GetPort(connection); - - var color = GetTypeShapeColor(connection.Type, node.Node.TypeFactory); - foreach (LinkModel link in port.Links) - link.Color = color; - - Diagram.Refresh(); - } - - #endregion - - #region UpdateNodes - - private void UpdateNodes(IEnumerable nodes) - { - Diagram.Batch(() => - { - DisableConnectionUpdate = true; - DisableNodeRemovedUpdate = true; - - Diagram.Links.Clear(); - Diagram.Nodes.Clear(); - - InitializeCanvasWithGraphNodes(); - - DisableNodeRemovedUpdate = false; - DisableConnectionUpdate = false; - }); - } - - #endregion - - #region Events from client - - #region Node Removed - - bool DisableNodeRemovedUpdate = false; - - public void OnNodeRemoved(NodeModel nodeModel) - { - if (DisableNodeRemovedUpdate) - return; - - Graph.Invoke(() => - { - var node = ((GraphNodeModel)nodeModel).Node; - - foreach (var input in node.Inputs) - { - foreach (var connection in input.Connections) - GraphManagerService.DisconnectConnectionBetween(input, connection); - } - - foreach (var output in node.Outputs) - { - foreach (var connection in output.Connections) - GraphManagerService.DisconnectConnectionBetween(output, connection); - } - - GraphManagerService.RemoveNode(node); - }); - } - - #endregion - - #region Connection Added / Removed, Vertex Added / Removed - - private bool DisableConnectionUpdate = false; - private void OnConnectionUpdated(BaseLinkModel baseLinkModel, Anchor old, Anchor newAnchor) - { - if (DisableConnectionUpdate || baseLinkModel.Source is PositionAnchor || baseLinkModel.Target is PositionAnchor) - return; - - Graph.Invoke(() => - { - var source = ((GraphPortModel?)baseLinkModel.Source.Model); - var destination = ((GraphPortModel?)baseLinkModel.Target.Model); - - if (source == null || destination == null) - return; - - if (source.Alignment == PortAlignment.Left) // it's an input, let's swap it so the "source" is an output - { - DisableConnectionUpdate = true; - var old = baseLinkModel.Source; - baseLinkModel.SetSource(baseLinkModel.Target); // this is necessary as everything assumes that the source is an output and vice versa - baseLinkModel.SetTarget(old); - DisableConnectionUpdate = false; - - var tmp = source; - source = destination; - destination = tmp; - } - - GraphManagerService.AddNewConnectionBetween(source.Connection, destination.Connection); - }); - } - - /// - /// This is called when the user starts dragging a connection. The link that is being dragged is not yet connected to the ports, the target will be a temporary PositionAnchor. - /// This is also called during the initialization when creating the links from the graph itself. In that case 'force' is set to true to make sure the connection is created properly no matter what. - /// - public void OnConnectionAdded(BaseLinkModel baseLinkModel, bool force) - { - if (DisableConnectionUpdate && !force) - return; - - baseLinkModel.SourceChanged += OnConnectionUpdated; - baseLinkModel.TargetChanged += OnConnectionUpdated; - baseLinkModel.TargetMarker = LinkMarker.Arrow; - baseLinkModel.Segmentable = true; - baseLinkModel.DoubleClickToSegment = true; - baseLinkModel.VertexAdded += BaseLinkModel_VertexAdded; - baseLinkModel.VertexRemoved += BaseLinkModel_VertexRemoved; - - if (baseLinkModel is LinkModel link) - { - if (link.Source.Model is GraphPortModel source) - { - link.Color = GetTypeShapeColor(source.Connection.Type, source.Connection.Parent.TypeFactory); - } - } - } - - /// - /// Return the output connection except for execs, in that case we return the input connection. - /// This is because vertices are stored for the port, and execs conveniently only have one output connection while other types only have one input connection. - /// - /// - private Connection GetConnectionContainingVertices(Connection source, Connection destination) - { - if (source.Type.IsExec) // execs can only have one connection, therefor they always contains the vertex information - return source; - else // if this is not an exec, the destination (input) will always contain the vertex information - return destination; - } - - private void UpdateVerticesInConnection(Connection source, Connection destination, BaseLinkModel linkModel) - { - var connection = GetConnectionContainingVertices(source, destination); - - connection.UpdateVertices(linkModel.Vertices.Select(x => new Vector2((float)x.Position.X, (float)x.Position.Y))); - - var other = connection == source ? destination : source; - other.UpdateVertices([]); // make sure there's no leftover vertices - } - - private bool DisableVertexAddDuringLoading = false; - private void BaseLinkModel_VertexRemoved(BaseLinkModel baseLinkModel, LinkVertexModel vertex) - { - if (baseLinkModel is LinkModel link && link.Source.Model is GraphPortModel source && link.Target.Model is GraphPortModel destination) - UpdateVerticesInConnection(source.Connection, destination.Connection, link); - } - - private void BaseLinkModel_VertexAdded(BaseLinkModel baseLinkModel, LinkVertexModel vertex) - { - if (baseLinkModel is LinkModel link && link.Source.Model is GraphPortModel source && link.Target.Model is GraphPortModel destination) - { - vertex.Moved += _ => Vertex_Moved(link, vertex); - - if (!DisableVertexAddDuringLoading) - UpdateVerticesInConnection(source.Connection, destination.Connection, link); - } - } - - private void Vertex_Moved(LinkModel link, LinkVertexModel vertex) - { - if (link.Source.Model is GraphPortModel source && link.Target.Model is GraphPortModel destination) - UpdateVerticesInConnection(source.Connection, destination.Connection, link); - } - - /// - /// Event called from the UI when client deleted a connection between two nodes. - /// This is also called when the user drops a connection onto the canvas, in that case the source or target will be a PositionAnchor. - /// - public void OnConnectionRemoved(BaseLinkModel baseLinkModel) - { - if (DisableConnectionUpdate) - return; - - Graph.Invoke(() => - { - var source = ((GraphPortModel?)baseLinkModel.Source.Model)?.Connection; - var destination = ((GraphPortModel?)baseLinkModel.Target.Model)?.Connection; - - if (source != null && destination != null) - { - GraphManagerService.DisconnectConnectionBetween(source, destination); - - UpdateVerticesInConnection(source, destination, baseLinkModel); - } - else - { - - if (baseLinkModel.Source is PositionAnchor positionAnchor && destination != null) - OnPortDroppedOnCanvas(destination, positionAnchor.GetPlainPosition()!); - else if (baseLinkModel.Target is PositionAnchor positionAnchor2 && source != null) - OnPortDroppedOnCanvas(source, positionAnchor2.GetPlainPosition()!); - } - }); - } - - #endregion - - #region Node Moved - - public void OnNodeMoved(MovableModel movableModel) - { - var node = ((GraphNodeModel)movableModel).Node; - - var decoration = node.GetOrAddDecoration(() => new(Vector2.Zero)); - decoration.Position = new((float)movableModel.Position.X, (float)movableModel.Position.Y); - } - - #endregion - - #region OnPortDroppedOnCanvas - - private bool IsShowingNodeSelection = false; - - public void OnPortDroppedOnCanvas(Connection connection, global::Blazor.Diagrams.Core.Geometry.Point point) - { - PopupNode = connection.Parent; - PopupNodeConnection = connection; - var screenPosition = Diagram.GetScreenPoint(point.X, point.Y) - Diagram.Container!.NorthWest; - PopupX = (int)screenPosition.X; - PopupY = (int)screenPosition.Y; - PopupNodePosition = new((float)point.X, (float)point.Y); - IsShowingNodeSelection = true; - - StateHasChanged(); - } - - private void OnNewNodeTypeSelected(NodeProvider.NodeSearchResult searchResult) - { - var node = GraphManagerService.AddNode(searchResult, node => - { - node.AddDecoration(new NodeDecorationPosition(new(PopupNodePosition.X, PopupNodePosition.Y))); - }); - - Diagram.Batch(() => - { - if (PopupNodeConnection != null && PopupNode != null) - { - // check if the source was an input or output and choose the proper destination based on that - List sources, destinations; - bool isPopupNodeInput = PopupNodeConnection.IsInput; - if (isPopupNodeInput) - { - sources = PopupNode.Inputs; - destinations = node.Outputs; - } - else - { - sources = PopupNode.Outputs; - destinations = node.Inputs; - } - - Connection? destination = null; - if (PopupNodeConnection.Type is UndefinedGenericType) // can connect to anything except exec - destination = destinations.FirstOrDefault(x => !x.Type.IsExec); - else // can connect to anything that is assignable to the type - destination = destinations.FirstOrDefault(x => PopupNodeConnection.Type.IsAssignableTo(x.Type, out _, out _) || (x.Type is UndefinedGenericType && !PopupNodeConnection.Type.IsExec)); - - // if we found a connection, connect them together - if (destination != null) - { - var source = isPopupNodeInput ? destination : PopupNodeConnection; - var target = isPopupNodeInput ? PopupNodeConnection : destination; + #region UpdateConnectionType + + public void UpdatePortColor(Connection connection) + { + var node = Diagram.Nodes.OfType().FirstOrDefault(x => x.Node == connection.Parent); + if (node == null) + return; + + var port = node.GetPort(connection); + + var color = GetTypeShapeColor(connection.Type, node.Node.TypeFactory); + foreach (var link in port.Links.Cast()) + link.Color = color; + + Diagram.Refresh(); + } + + #endregion + + #region UpdateNodes + + private void UpdateNodes() + { + Diagram.Batch(() => + { + DisableConnectionUpdate = true; + DisableNodeRemovedUpdate = true; + + Diagram.Links.Clear(); + Diagram.Nodes.Clear(); + + InitializeCanvasWithGraphNodes(); + + DisableNodeRemovedUpdate = false; + DisableConnectionUpdate = false; + }); + } + + #endregion + + #region Events from client + + #region Node Removed + + bool DisableNodeRemovedUpdate = false; + + public void OnNodeRemoved(NodeModel nodeModel) + { + if (DisableNodeRemovedUpdate) + return; + + var node = ((GraphNodeModel)nodeModel).Node; + + foreach (var input in node.Inputs) + { + foreach (var connection in input.Connections) + GraphManagerService.DisconnectConnectionBetween(input, connection); + } + + foreach (var output in node.Outputs) + { + foreach (var connection in output.Connections) + GraphManagerService.DisconnectConnectionBetween(output, connection); + } + + GraphManagerService.RemoveNode(node); + } + + #endregion + + #region Connection Added / Removed, Vertex Added / Removed + + private bool DisableConnectionUpdate = false; + private void OnConnectionUpdated(BaseLinkModel baseLinkModel, Anchor old, Anchor newAnchor) + { + if (DisableConnectionUpdate || baseLinkModel.Source is PositionAnchor || baseLinkModel.Target is PositionAnchor) + return; + + var source = ((GraphPortModel?)baseLinkModel.Source.Model); + var destination = ((GraphPortModel?)baseLinkModel.Target.Model); + + if (source == null || destination == null) + return; + + if (source.Alignment == PortAlignment.Left) // it's an input, let's swap it so the "source" is an output + { + DisableConnectionUpdate = true; + var old2 = baseLinkModel.Source; + baseLinkModel.SetSource(baseLinkModel.Target); // this is necessary as everything assumes that the source is an output and vice versa + baseLinkModel.SetTarget(old2); + DisableConnectionUpdate = false; + + (destination, source) = (source, destination); + } + + GraphManagerService.AddNewConnectionBetween(source.Connection, destination.Connection); + } + + /// + /// This is called when the user starts dragging a connection. The link that is being dragged is not yet connected to the ports, the target will be a temporary PositionAnchor. + /// This is also called during the initialization when creating the links from the graph itself. In that case 'force' is set to true to make sure the connection is created properly no matter what. + /// + public void OnConnectionAdded(BaseLinkModel baseLinkModel, bool force) + { + if (DisableConnectionUpdate && !force) + return; + + baseLinkModel.SourceChanged += OnConnectionUpdated; + baseLinkModel.TargetChanged += OnConnectionUpdated; + baseLinkModel.TargetMarker = LinkMarker.Arrow; + baseLinkModel.Segmentable = true; + baseLinkModel.DoubleClickToSegment = true; + baseLinkModel.VertexAdded += BaseLinkModel_VertexAdded; + baseLinkModel.VertexRemoved += BaseLinkModel_VertexRemoved; + + if (baseLinkModel is LinkModel link) + { + if (link.Source.Model is GraphPortModel source) + { + link.Color = GetTypeShapeColor(source.Connection.Type, source.Connection.Parent.TypeFactory); + } + } + } + + /// + /// Return the output connection except for execs, in that case we return the input connection. + /// This is because vertices are stored for the port, and execs conveniently only have one output connection while other types only have one input connection. + /// + /// + private static Connection GetConnectionContainingVertices(Connection source, Connection destination) + { + if (source.Type.IsExec) // execs can only have one connection, therefor they always contains the vertex information + return source; + else // if this is not an exec, the destination (input) will always contain the vertex information + return destination; + } + + private static void UpdateVerticesInConnection(Connection source, Connection destination, BaseLinkModel linkModel) + { + var connection = GetConnectionContainingVertices(source, destination); + + connection.UpdateVertices(linkModel.Vertices.Select(x => new Vector2((float)x.Position.X, (float)x.Position.Y))); + + var other = connection == source ? destination : source; + other.UpdateVertices([]); // make sure there's no leftover vertices + } + + private bool DisableVertexAddDuringLoading = false; + private void BaseLinkModel_VertexRemoved(BaseLinkModel baseLinkModel, LinkVertexModel vertex) + { + if (baseLinkModel is LinkModel link && link.Source.Model is GraphPortModel source && link.Target.Model is GraphPortModel destination) + UpdateVerticesInConnection(source.Connection, destination.Connection, link); + } + + private void BaseLinkModel_VertexAdded(BaseLinkModel baseLinkModel, LinkVertexModel vertex) + { + if (baseLinkModel is LinkModel link && link.Source.Model is GraphPortModel source && link.Target.Model is GraphPortModel destination) + { + vertex.Moved += _ => Vertex_Moved(link); + + if (!DisableVertexAddDuringLoading) + UpdateVerticesInConnection(source.Connection, destination.Connection, link); + } + } + + private static void Vertex_Moved(LinkModel link) + { + if (link.Source.Model is GraphPortModel source && link.Target.Model is GraphPortModel destination) + UpdateVerticesInConnection(source.Connection, destination.Connection, link); + } + + /// + /// Event called from the UI when client deleted a connection between two nodes. + /// This is also called when the user drops a connection onto the canvas, in that case the source or target will be a PositionAnchor. + /// + public void OnConnectionRemoved(BaseLinkModel baseLinkModel) + { + if (DisableConnectionUpdate) + return; + + var source = ((GraphPortModel?)baseLinkModel.Source.Model)?.Connection; + var destination = ((GraphPortModel?)baseLinkModel.Target.Model)?.Connection; + + if (source != null && destination != null) + { + GraphManagerService.DisconnectConnectionBetween(source, destination); + + UpdateVerticesInConnection(source, destination, baseLinkModel); + } + else + { + + if (baseLinkModel.Source is PositionAnchor positionAnchor && destination != null) + OnPortDroppedOnCanvas(destination, positionAnchor.GetPlainPosition()!); + else if (baseLinkModel.Target is PositionAnchor positionAnchor2 && source != null) + OnPortDroppedOnCanvas(source, positionAnchor2.GetPlainPosition()!); + } + } + + #endregion + + #region Node Moved + + public static void OnNodeMoved(MovableModel movableModel) + { + var node = ((GraphNodeModel)movableModel).Node; + + var decoration = node.GetOrAddDecoration(() => new(Vector2.Zero)); + decoration.Position = new((float)movableModel.Position.X, (float)movableModel.Position.Y); + } + + #endregion + + #region OnPortDroppedOnCanvas + + private bool IsShowingNodeSelection = false; + + public void OnPortDroppedOnCanvas(Connection connection, global::Blazor.Diagrams.Core.Geometry.Point point) + { + PopupNode = connection.Parent; + PopupNodeConnection = connection; + var screenPosition = Diagram.GetScreenPoint(point.X, point.Y) - Diagram.Container!.NorthWest; + PopupX = (int)screenPosition.X; + PopupY = (int)screenPosition.Y; + PopupNodePosition = new((float)point.X, (float)point.Y); + IsShowingNodeSelection = true; + + StateHasChanged(); + } + + private void OnNewNodeTypeSelected(NodeProvider.NodeSearchResult searchResult) + { + var node = GraphManagerService.AddNode(searchResult, node => + { + node.AddDecoration(new NodeDecorationPosition(new(PopupNodePosition.X, PopupNodePosition.Y))); + }); + + Diagram.Batch(() => + { + if (PopupNodeConnection != null && PopupNode != null) + { + // check if the source was an input or output and choose the proper destination based on that + List sources, destinations; + bool isPopupNodeInput = PopupNodeConnection.IsInput; + if (isPopupNodeInput) + { + sources = PopupNode.Inputs; + destinations = node.Outputs; + } + else + { + sources = PopupNode.Outputs; + destinations = node.Inputs; + } + + Connection? destination = null; + if (PopupNodeConnection.Type is UndefinedGenericType) // can connect to anything except exec + destination = destinations.FirstOrDefault(x => !x.Type.IsExec); + else // can connect to anything that is assignable to the type + destination = destinations.FirstOrDefault(x => PopupNodeConnection.Type.IsAssignableTo(x.Type, out _, out _) || (x.Type is UndefinedGenericType && !PopupNodeConnection.Type.IsExec)); + + // if we found a connection, connect them together + if (destination != null) + { + var source = isPopupNodeInput ? destination : PopupNodeConnection; + var target = isPopupNodeInput ? PopupNodeConnection : destination; - GraphManagerService.AddNewConnectionBetween(source, target); - } - } - - CancelPopup(); - }); - - } - - #endregion - - #region OnOverloadSelectionRequested / OnNewOverloadSelected + GraphManagerService.AddNewConnectionBetween(source, target); + } + } - private bool IsShowingOverloadSelection = false; + CancelPopup(); + }); + + } + + #endregion - public void OnOverloadSelectionRequested(GraphNodeModel graphNode) - { - PopupNode = graphNode.Node; - IsShowingOverloadSelection = true; - - StateHasChanged(); - } - - private void OnNewOverloadSelected(Node.AlternateOverload overload) - { - if (PopupNode == null) - return; - - GraphManagerService.SelectNodeOverload(PopupNode, overload); - - CancelPopup(); - } + #region OnOverloadSelectionRequested / OnNewOverloadSelected - #endregion - - #region OnGenericTypeSelectionMenuAsked - - private bool IsShowingGenericTypeSelection = false; - private string? GenericTypeSelectionMenuGeneric; + private bool IsShowingOverloadSelection = false; - public void OnGenericTypeSelectionMenuAsked(GraphNodeModel nodeModel, string undefinedGenericType) - { - PopupNode = nodeModel.Node; - var p = Diagram.GetScreenPoint(nodeModel.Position.X, nodeModel.Position.Y) - Diagram.Container!.NorthWest; - PopupX = (int)p.X; - PopupY = (int)p.Y; - GenericTypeSelectionMenuGeneric = undefinedGenericType; - IsShowingGenericTypeSelection = true; - - StateHasChanged(); - } - - private void OnGenericTypeSelected(TypeBase type) - { - if (PopupNode == null || GenericTypeSelectionMenuGeneric == null) - return; - - GraphManagerService.PropagateNewGeneric(PopupNode, new Dictionary() { [GenericTypeSelectionMenuGeneric] = type }, false, null, overrideInitialTypes: true); - - // Prefer updating the nodes directly instead of calling Graph.RaiseGraphChanged(true) to be sure it is called as soon as possible - //UpdateNodes(Graph.Nodes.Values.ToList()); - - CancelPopup(); - } - - #endregion + public void OnOverloadSelectionRequested(GraphNodeModel graphNode) + { + PopupNode = graphNode.Node; + IsShowingOverloadSelection = true; - #region OnTextboxValueChanged + StateHasChanged(); + } + + private void OnNewOverloadSelected(Node.AlternateOverload overload) + { + if (PopupNode == null) + return; + + GraphManagerService.SelectNodeOverload(PopupNode, overload); + + CancelPopup(); + } - public void OnTextboxValueChanged(GraphPortModel port, string? text) - { - var connection = port.Connection; + #endregion - if (connection.Type.AllowTextboxEdit) - { - connection.UpdateTextboxText(text); + #region OnGenericTypeSelectionMenuAsked + + private bool IsShowingGenericTypeSelection = false; + private string? GenericTypeSelectionMenuGeneric; + + public void OnGenericTypeSelectionMenuAsked(GraphNodeModel nodeModel, string undefinedGenericType) + { + PopupNode = nodeModel.Node; + var p = Diagram.GetScreenPoint(nodeModel.Position.X, nodeModel.Position.Y) - Diagram.Container!.NorthWest; + PopupX = (int)p.X; + PopupY = (int)p.Y; + GenericTypeSelectionMenuGeneric = undefinedGenericType; + IsShowingGenericTypeSelection = true; + + StateHasChanged(); + } + + private void OnGenericTypeSelected(TypeBase type) + { + if (PopupNode == null || GenericTypeSelectionMenuGeneric == null) + return; - Graph.RaiseGraphChanged(false); - } - } + GraphManagerService.PropagateNewGeneric(PopupNode, new Dictionary() { [GenericTypeSelectionMenuGeneric] = type }, false, null, overrideInitialTypes: true); - #endregion + // Prefer updating the nodes directly instead of calling Graph.RaiseGraphChanged(true) to be sure it is called as soon as possible + //UpdateNodes(Graph.Nodes.Values.ToList()); - #region OnNodeDoubleClick + CancelPopup(); + } - public void OnNodeDoubleClick(Node node) - { - if (node is MethodCall methodCall && methodCall.TargetMethod is NodeClassMethod nodeClassMethod) - { - IndexPage.OpenMethod(nodeClassMethod); + #endregion - DebuggedPathService.EnterExecutor(node); - } - } + #region OnTextboxValueChanged - #endregion + public void OnTextboxValueChanged(GraphPortModel port, string? text) + { + var connection = port.Connection; - #region SelectionChanged + if (connection.Type.AllowTextboxEdit) + { + connection.UpdateTextboxText(text); - private void SelectionChanged(SelectableModel obj) - { - foreach (var node in Diagram.Nodes.OfType()) - { - if (!obj.Selected && node.IsEditingName) - { - node.IsEditingName = false; - node.Refresh(); - } - } - } + Graph.RaiseGraphChanged(false); + } + } - #endregion + #endregion - #region Diagram_KeyDown - - private void Diagram_KeyDown(global::Blazor.Diagrams.Core.Events.KeyboardEventArgs obj) - { - // Detect f2 key to start editing the name of the selected node - if (obj.Key == "F2") - { - var node = Diagram.Nodes.Where(x => x.Selected).OfType().FirstOrDefault(); - if (node != null && node.Node.AllowEditingName) - { - node.IsEditingName = true; - node.Refresh(); - } - } - } - - #endregion - - #region OnNodeRenamed - - internal void OnNodeRenamed(GraphNodeModel node) - { - node.IsEditingName = false; - - node.Refresh(); - - // When the name of a node changes, refresh the connected nodes in case they also need to refresh - foreach (var link in node.PortLinks.OfType()) - { - if (link.Source.Model is GraphPortModel source) - source.Parent.Refresh(); - if (link.Target.Model is GraphPortModel target) - target.Parent.Refresh(); - } - } + #region OnNodeDoubleClick - #endregion - - #endregion - - #region ShowAddNode - - public void ShowAddNode() - { - IsShowingNodeSelection = true; - PopupX = 300; - PopupY = 300; - } - - #endregion - - #region CancelPopup - - private void CancelPopup() - { - IsShowingGenericTypeSelection = IsShowingNodeSelection = IsShowingOverloadSelection = false; - PopupNode = null; - PopupNodeConnection = null; - } - - #endregion - - #region RemoveNode - - public void RemoveNode(Node node) - { - var nodeModel = Diagram.Nodes.OfType().FirstOrDefault(x => x.Node == node); - - if (nodeModel != null) - Diagram.Nodes.Remove(nodeModel); - } - - #endregion - - #region AddLink / RemoveLink - - public void RemoveLinkFromGraphCanvas(Connection source, Connection destination) - { - Graph.Invoke(() => - { - DisableConnectionUpdate = true; - try - { - Diagram.Links.Remove(Diagram.Links.First(x => (x.Source.Model as GraphPortModel)?.Connection == source && (x.Target.Model as GraphPortModel)?.Connection == destination)); - } - finally - { - DisableConnectionUpdate = false; - } - }); - } - - public void AddLinkToGraphCanvas(Connection source, Connection destination) - { - DisableConnectionUpdate = true; - try - { - if(source.IsInput) - (destination, source) = (source, destination); - - var sourceNode = Diagram.Nodes.OfType().First(x => x.Node == source.Parent); - var destinationNode = Diagram.Nodes.OfType().First(x => x.Node == destination.Parent); - var sourcePort = sourceNode.GetPort(source); - var destinationPort = destinationNode.GetPort(destination); - - // Make sure there isn't already an existing identical link - if(Diagram.Links.OfType().Any( x => (x.Source as SinglePortAnchor)?.Port == sourcePort && (x.Target as SinglePortAnchor)?.Port == destinationPort)) - return; - - var link = Diagram.Links.Add(new LinkModel(sourcePort, destinationPort)); - - OnConnectionAdded(link, true); - } - finally - { - DisableConnectionUpdate = false; - } - } - - #endregion - - #region AddNode - - public void AddNode(Node node) - { - var nodeModel = Diagram.Nodes.Add(new GraphNodeModel(node)); - foreach (var connection in node.InputsAndOutputs) - nodeModel.AddPort(new GraphPortModel(nodeModel, connection, node.Inputs.Contains(connection))); - - nodeModel.Moved += OnNodeMoved; - } - - #endregion - - #region AddNodeLinks - - private void AddNodeLinks(Node node, bool onlyOutputs) - { - var nodeModel = Diagram.Nodes.OfType().First(x => x.Node == node); - foreach (var connection in onlyOutputs ? node.Outputs : node.InputsAndOutputs) // just process the outputs so we don't connect "input to output" and "output to input" on the same connections - { - var portModel = nodeModel.GetPort(connection); - foreach (var other in connection.Connections) - { - var otherNodeModel = Diagram.Nodes.OfType().First(x => x.Node == other.Parent); - var otherPortModel = otherNodeModel.GetPort(other); - - var source = portModel; - var target = otherPortModel; - - // if we're processing the inputs, we need to swap the source and target to reflect the proper direction - if (!onlyOutputs && node.Inputs.Contains(connection)) - { - source = otherPortModel; - target = portModel; - } - - // disable the connection update while adding the link so we can call it ourself and 'force' it to be sure it actually runs - // if we don't do that, we'll have to call it again after adding the link and put the 'force' parameter to true, but then - // it might be run twice, resulting in all callbacks being called twice! - DisableConnectionUpdate = true; - var link = Diagram.Links.Add(new LinkModel(source, target)); - - DisableConnectionUpdate = false; - OnConnectionAdded(link, true); - - var connectionWithVertices = GetConnectionContainingVertices(source.Connection, target.Connection); - - if (connectionWithVertices.Vertices.Count != 0) - { - Diagram.Batch(() => - { - DisableVertexAddDuringLoading = true; - - foreach (var vertex in connectionWithVertices.Vertices) - link.AddVertex(new(vertex.X, vertex.Y)); - - DisableVertexAddDuringLoading = false; - }); - } - - - - } - } - } - - - #endregion - - #region Refresh - - public void Refresh(Node node) - { - var nodeModel = Diagram.Nodes.OfType().FirstOrDefault(x => x.Node == node); - - nodeModel?.Refresh(); - } - - #endregion - - #region Initialize - - private void InitializeCanvasWithGraphNodes() - { - // add the nodes themselves - foreach (var node in Graph.Nodes.Values) - AddNode(node); - - // add links - foreach (var node in Graph.Nodes.Values) - AddNodeLinks(node, true); - } - - public static string GetTypeShapeColor(TypeBase type, TypeFactory typeFactory) - { - if (type.HasUndefinedGenerics) - return "yellow"; - else if (type == typeFactory.Get()) - return "purple"; - else if (type.IsClass) - return "green"; - else if (type.IsExec) - return "gray"; - else if (type == typeFactory.Get()) - return "red"; - else - return "blue"; - } - - #endregion - - #region Dispose - - private IDisposable? GraphChangedSubscription; - private IDisposable? NodeExecutingSubscription; - private IDisposable? NodeExecutedSubscription; - public void Dispose() - { - if(Graph.GraphCanvas == this) - Graph.GraphCanvas = null; - - GraphChangedSubscription?.Dispose(); - NodeExecutingSubscription?.Dispose(); - NodeExecutedSubscription?.Dispose(); - GraphChangedSubscription = null; - NodeExecutingSubscription = null; - NodeExecutedSubscription = null; - } - - #endregion + public void OnNodeDoubleClick(Node node) + { + if (node is MethodCall methodCall && methodCall.TargetMethod is NodeClassMethod nodeClassMethod) + { + IndexPage.OpenMethod(nodeClassMethod); + + DebuggedPathService.EnterExecutor(node); + } + } + + #endregion + + #region SelectionChanged + + private void SelectionChanged(SelectableModel obj) + { + foreach (var node in Diagram.Nodes.OfType()) + { + if (!obj.Selected && node.IsEditingName) + { + node.IsEditingName = false; + node.Refresh(); + } + } + } + + #endregion + + #region Diagram_KeyDown + + private void Diagram_KeyDown(global::Blazor.Diagrams.Core.Events.KeyboardEventArgs obj) + { + // Detect f2 key to start editing the name of the selected node + if (obj.Key == "F2") + { + var node = Diagram.Nodes.Where(x => x.Selected).OfType().FirstOrDefault(); + if (node != null && node.Node.AllowEditingName) + { + node.IsEditingName = true; + node.Refresh(); + } + } + } + + #endregion + + #endregion + + #region ShowAddNode + + public void ShowAddNode() + { + IsShowingNodeSelection = true; + PopupX = 300; + PopupY = 300; + } + + #endregion + + #region CancelPopup + + private void CancelPopup() + { + IsShowingGenericTypeSelection = IsShowingNodeSelection = IsShowingOverloadSelection = false; + PopupNode = null; + PopupNodeConnection = null; + } + + #endregion + + #region RemoveNode + + public void RemoveNode(Node node) + { + var nodeModel = Diagram.Nodes.OfType().FirstOrDefault(x => x.Node == node); + + if (nodeModel != null) + Diagram.Nodes.Remove(nodeModel); + } + + #endregion + + #region AddLink / RemoveLink + + public void RemoveLinkFromGraphCanvas(Connection source, Connection destination) + { + DisableConnectionUpdate = true; + try + { + Diagram.Links.Remove(Diagram.Links.First(x => (x.Source.Model as GraphPortModel)?.Connection == source && (x.Target.Model as GraphPortModel)?.Connection == destination)); + } + finally + { + DisableConnectionUpdate = false; + } + } + + public void AddLinkToGraphCanvas(Connection source, Connection destination) + { + DisableConnectionUpdate = true; + try + { + if (source.IsInput) + (destination, source) = (source, destination); + + var sourceNode = Diagram.Nodes.OfType().First(x => x.Node == source.Parent); + var destinationNode = Diagram.Nodes.OfType().First(x => x.Node == destination.Parent); + var sourcePort = sourceNode.GetPort(source); + var destinationPort = destinationNode.GetPort(destination); + + // Make sure there isn't already an existing identical link + if (Diagram.Links.OfType().Any(x => (x.Source as SinglePortAnchor)?.Port == sourcePort && (x.Target as SinglePortAnchor)?.Port == destinationPort)) + return; + + var link = Diagram.Links.Add(new LinkModel(sourcePort, destinationPort)); + + OnConnectionAdded(link, true); + } + finally + { + DisableConnectionUpdate = false; + } + } + + #endregion + + #region AddNode + + public void AddNode(Node node) + { + var nodeModel = Diagram.Nodes.Add(new GraphNodeModel(node)); + foreach (var connection in node.InputsAndOutputs) + nodeModel.AddPort(new GraphPortModel(nodeModel, connection, node.Inputs.Contains(connection))); + + nodeModel.Moved += OnNodeMoved; + } + + #endregion + + #region AddNodeLinks + + private void AddNodeLinks(Node node, bool onlyOutputs) + { + var nodeModel = Diagram.Nodes.OfType().First(x => x.Node == node); + foreach (var connection in onlyOutputs ? node.Outputs : node.InputsAndOutputs) // just process the outputs so we don't connect "input to output" and "output to input" on the same connections + { + var portModel = nodeModel.GetPort(connection); + foreach (var other in connection.Connections) + { + var otherNodeModel = Diagram.Nodes.OfType().First(x => x.Node == other.Parent); + var otherPortModel = otherNodeModel.GetPort(other); + + var source = portModel; + var target = otherPortModel; + + // if we're processing the inputs, we need to swap the source and target to reflect the proper direction + if (!onlyOutputs && node.Inputs.Contains(connection)) + { + source = otherPortModel; + target = portModel; + } + + // disable the connection update while adding the link so we can call it ourself and 'force' it to be sure it actually runs + // if we don't do that, we'll have to call it again after adding the link and put the 'force' parameter to true, but then + // it might be run twice, resulting in all callbacks being called twice! + DisableConnectionUpdate = true; + var link = Diagram.Links.Add(new LinkModel(source, target)); + + DisableConnectionUpdate = false; + OnConnectionAdded(link, true); + + var connectionWithVertices = GetConnectionContainingVertices(source.Connection, target.Connection); + + if (connectionWithVertices.Vertices.Count != 0) + { + Diagram.Batch(() => + { + DisableVertexAddDuringLoading = true; + + foreach (var vertex in connectionWithVertices.Vertices) + link.AddVertex(new(vertex.X, vertex.Y)); + + DisableVertexAddDuringLoading = false; + }); + } + + + + } + } + } + + + #endregion + + #region Refresh + + public void Refresh(Node node) + { + var nodeModel = Diagram.Nodes.OfType().FirstOrDefault(x => x.Node == node); + + nodeModel?.Refresh(); + } + + #endregion + + #region Initialize + + private void InitializeCanvasWithGraphNodes() + { + // add the nodes themselves + foreach (var node in Graph.Nodes.Values) + AddNode(node); + + // add links + foreach (var node in Graph.Nodes.Values) + AddNodeLinks(node, true); + } + + public static string GetTypeShapeColor(TypeBase type, TypeFactory typeFactory) + { + if (type.HasUndefinedGenerics) + return "yellow"; + else if (type == typeFactory.Get()) + return "purple"; + else if (type.IsClass) + return "green"; + else if (type.IsExec) + return "gray"; + else if (type == typeFactory.Get()) + return "red"; + else + return "blue"; + } + + #endregion + + #region Dispose + + private IDisposable? GraphChangedSubscription; + private IDisposable? NodeExecutingSubscription; + private IDisposable? NodeExecutedSubscription; + public void Dispose() + { + GC.SuppressFinalize(this); + + if (Graph.GraphCanvas == this) + Graph.GraphCanvas = null; + + GraphChangedSubscription?.Dispose(); + NodeExecutingSubscription?.Dispose(); + NodeExecutedSubscription?.Dispose(); + GraphChangedSubscription = null; + NodeExecutingSubscription = null; + NodeExecutedSubscription = null; + } + + #endregion } diff --git a/src/NodeDev.Blazor/DiagramsModels/GraphNodeWidget.razor b/src/NodeDev.Blazor/DiagramsModels/GraphNodeWidget.razor index 1e2bf02..d1696df 100644 --- a/src/NodeDev.Blazor/DiagramsModels/GraphNodeWidget.razor +++ b/src/NodeDev.Blazor/DiagramsModels/GraphNodeWidget.razor @@ -67,7 +67,18 @@ private void OnAfterNodeRenamed() { - GraphCanvas.OnNodeRenamed(Node); + Node.IsEditingName = false; + + Node.Refresh(); + + // When the name of a node changes, refresh the connected nodes in case they also need to refresh + foreach (var link in Node.PortLinks.OfType()) + { + if (link.Source.Model is GraphPortModel source) + source.Parent.Refresh(); + if (link.Target.Model is GraphPortModel target) + target.Parent.Refresh(); + } } private void OnKeyDown(KeyboardEventArgs args) diff --git a/src/NodeDev.Blazor/DiagramsModels/SmoothPathGeneratorWithDirectVertices.cs b/src/NodeDev.Blazor/DiagramsModels/SmoothPathGeneratorWithDirectVertices.cs index 2d406f2..53928fa 100644 --- a/src/NodeDev.Blazor/DiagramsModels/SmoothPathGeneratorWithDirectVertices.cs +++ b/src/NodeDev.Blazor/DiagramsModels/SmoothPathGeneratorWithDirectVertices.cs @@ -43,7 +43,7 @@ public override PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel lin .AddMoveTo(route[0].X, route[0].Y) .AddCubicBezierCurve(route[1].X, route[1].Y, route[2].X, route[2].Y, route[3].X, route[3].Y); - return new PathGeneratorResult(path, Array.Empty(), sourceAngle, route[0], targetAngle, route[^1]); + return new PathGeneratorResult(path, [], sourceAngle, route[0], targetAngle, route[^1]); } private PathGeneratorResult CurveThroughPoints(Point[] route, BaseLinkModel link) diff --git a/src/NodeDev.Blazor/NodeDecorations/NodeDecorationPosition.cs b/src/NodeDev.Blazor/NodeAttributes/NodeDecorationPosition.cs similarity index 100% rename from src/NodeDev.Blazor/NodeDecorations/NodeDecorationPosition.cs rename to src/NodeDev.Blazor/NodeAttributes/NodeDecorationPosition.cs diff --git a/src/NodeDev.Blazor/Services/AppOptionsContainer.cs b/src/NodeDev.Blazor/Services/AppOptionsContainer.cs index 426b514..2642eb0 100644 --- a/src/NodeDev.Blazor/Services/AppOptionsContainer.cs +++ b/src/NodeDev.Blazor/Services/AppOptionsContainer.cs @@ -6,7 +6,7 @@ public class AppOptionsContainer { private readonly string OptionsFileName; - private AppOptions appOptions = new AppOptions(); + private AppOptions appOptions = new(); public AppOptions AppOptions { get => appOptions; diff --git a/src/NodeDev.Blazor/Services/DebuggedPathService.cs b/src/NodeDev.Blazor/Services/DebuggedPathService.cs index 838d423..f6836ed 100644 --- a/src/NodeDev.Blazor/Services/DebuggedPathService.cs +++ b/src/NodeDev.Blazor/Services/DebuggedPathService.cs @@ -13,7 +13,7 @@ internal class DebuggedPathService /// The stack of nodes throught which we are debugging. /// Ie, if we are debugging Main -> MethodCall1 -> MethodCall2, this will contain MethodCall1, MethodCall2. Or more the actual nodes of each of them /// - private readonly List GraphIndexesAndNodes_ = new(); + private readonly List GraphIndexesAndNodes_ = []; /// /// The queue of nodes throught which we are debugging. diff --git a/src/NodeDev.Core/Class/NodeClass.cs b/src/NodeDev.Core/Class/NodeClass.cs index 84d74ed..67ba241 100644 --- a/src/NodeDev.Core/Class/NodeClass.cs +++ b/src/NodeDev.Core/Class/NodeClass.cs @@ -4,29 +4,22 @@ namespace NodeDev.Core.Class { - public class NodeClass + public class NodeClass(string name, string @namespace, Project project) { - public readonly Project Project; + public readonly Project Project = project; public TypeFactory TypeFactory => Project.TypeFactory; public TypeBase ClassTypeBase => Project.GetNodeClassType(this); - public string Name { get; set; } + public string Name { get; set; } = name; - public string Namespace { get; set; } + public string Namespace { get; set; } = @namespace; internal List _Methods = []; public IReadOnlyList Methods => _Methods; - public List Properties { get; } = new(); - - public NodeClass(string name, string @namespace, Project project) - { - Name = name; - Namespace = @namespace; - Project = project; - } + public List Properties { get; } = []; #region AddMethod diff --git a/src/NodeDev.Core/Class/NodeClassMethod.cs b/src/NodeDev.Core/Class/NodeClassMethod.cs index 2f38274..016d772 100644 --- a/src/NodeDev.Core/Class/NodeClassMethod.cs +++ b/src/NodeDev.Core/Class/NodeClassMethod.cs @@ -9,15 +9,13 @@ namespace NodeDev.Core.Class public class NodeClassMethod : IMethodInfo { internal record class SerializedNodeClassMethod(string Name, TypeBase.SerializedType ReturnType, List Parameters, Graph.SerializedGraph Graph, bool IsStatic); - public NodeClassMethod(NodeClass ownerClass, string name, TypeBase returnType, Graph graph, bool isStatic = false) + public NodeClassMethod(NodeClass ownerClass, string name, TypeBase returnType, bool isStatic = false) { Class = ownerClass; Name = name; ReturnType = returnType; - Graph = graph; IsStatic = isStatic; - - Graph.SelfMethod = this; + Graph = new(this); } public NodeClass Class { get; } @@ -26,7 +24,7 @@ public NodeClassMethod(NodeClass ownerClass, string name, TypeBase returnType, G public TypeBase ReturnType { get; } - public List Parameters { get; } = new(); + public List Parameters { get; } = []; public Graph Graph { get; } @@ -84,8 +82,7 @@ public void AddDefaultParameter() } var entry = Graph.Nodes.Values.OfType().FirstOrDefault(); - if (entry != null) - entry.AddNewParameter(newParameter); + entry?.AddNewParameter(newParameter); } public IEnumerable GetParameters() @@ -111,9 +108,7 @@ public MethodInfo CreateMethodInfo() internal static NodeClassMethod Deserialize(NodeClass owner, SerializedNodeClassMethod serializedNodeClassMethod) { var returnType = TypeBase.Deserialize(owner.Project.TypeFactory, serializedNodeClassMethod.ReturnType); - var graph = new Graph(); - var nodeClassMethod = new NodeClassMethod(owner, serializedNodeClassMethod.Name, returnType, graph, serializedNodeClassMethod.IsStatic); - graph.SelfMethod = nodeClassMethod; // a bit / really ugly + var nodeClassMethod = new NodeClassMethod(owner, serializedNodeClassMethod.Name, returnType, serializedNodeClassMethod.IsStatic); foreach (var parameter in serializedNodeClassMethod.Parameters) nodeClassMethod.Parameters.Add(NodeClassMethodParameter.Deserialize(owner.Project.TypeFactory, parameter, nodeClassMethod)); diff --git a/src/NodeDev.Core/Class/NodeClassMethodParameter.cs b/src/NodeDev.Core/Class/NodeClassMethodParameter.cs index e4af087..40add4b 100644 --- a/src/NodeDev.Core/Class/NodeClassMethodParameter.cs +++ b/src/NodeDev.Core/Class/NodeClassMethodParameter.cs @@ -4,25 +4,18 @@ namespace NodeDev.Core.Class { - public class NodeClassMethodParameter : IMethodParameterInfo + public class NodeClassMethodParameter(string name, TypeBase parameterType, NodeClassMethod method) : IMethodParameterInfo { internal record class SerializedNodeClassMethodParameter(string Name, TypeBase.SerializedType ParameterType, bool? IsOut); - public string Name { get; private set; } + public string Name { get; private set; } = name; - public TypeBase ParameterType { get; private set; } + public TypeBase ParameterType { get; private set; } = parameterType; - public NodeClassMethod Method { get; } + public NodeClassMethod Method { get; } = method; 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); @@ -124,8 +117,7 @@ public void Rename(string name) } var entry = Method.Graph.Nodes.Values.OfType().FirstOrDefault(); - if (entry != null) - entry.RenameParameter(this, Method.Parameters.IndexOf(this)); + entry?.RenameParameter(this, Method.Parameters.IndexOf(this)); } public void ChangeType(TypeBase type) diff --git a/src/NodeDev.Core/Class/NodeClassProperty.cs b/src/NodeDev.Core/Class/NodeClassProperty.cs index bf74306..2af2f15 100644 --- a/src/NodeDev.Core/Class/NodeClassProperty.cs +++ b/src/NodeDev.Core/Class/NodeClassProperty.cs @@ -2,23 +2,17 @@ namespace NodeDev.Core.Class; -public class NodeClassProperty : IMemberInfo +public class NodeClassProperty(NodeClass ownerClass, string name, TypeBase propertyType) : IMemberInfo { internal record class SerializedNodeClassProperty(string Name, TypeBase.SerializedType Type); - public NodeClassProperty(NodeClass ownerClass, string name, TypeBase propertyType) - { - Class = ownerClass; - Name = name; - PropertyType = propertyType; - } - public NodeClass Class { get; } + public NodeClass Class { get; } = ownerClass; - public string Name { get; private set; } + public string Name { get; private set; } = name; - public TypeBase PropertyType { get; private set; } + public TypeBase PropertyType { get; private set; } = propertyType; - public List Parameters { get; } = new(); + public List Parameters { get; } = []; public TypeBase DeclaringType => Class.ClassTypeBase; diff --git a/src/NodeDev.Core/Class/NodeClassTypeCreator.cs b/src/NodeDev.Core/Class/NodeClassTypeCreator.cs index 6076f98..0b41881 100644 --- a/src/NodeDev.Core/Class/NodeClassTypeCreator.cs +++ b/src/NodeDev.Core/Class/NodeClassTypeCreator.cs @@ -32,7 +32,7 @@ internal NodeClassTypeCreator(Project project, BuildOptions buildOptions) Options = buildOptions; } - private static Assembly TemporaryReflectionAssembly; + private static Assembly? TemporaryReflectionAssembly; public void CreateProjectClassesAndAssembly() { // TODO Remove this when the new System.Reflection.Emit is available in .NET 10 @@ -57,11 +57,8 @@ public void CreateProjectClassesAndAssembly() // Creating all the types early so they are all accessible during expression tree generation foreach (var nodeClass in Project.Classes) { - GeneratedType generatedType; - if (GeneratedTypes.ContainsKey(Project.GetNodeClassType(nodeClass))) - generatedType = GeneratedTypes[Project.GetNodeClassType(nodeClass)]; - else - GeneratedTypes[Project.GetNodeClassType(nodeClass)] = generatedType = CreateGeneratedType(mb, nodeClass.Name); + if (!GeneratedTypes.ContainsKey(Project.GetNodeClassType(nodeClass))) + GeneratedTypes[Project.GetNodeClassType(nodeClass)] = CreateGeneratedType(mb, nodeClass.Name); } // Create the properties and methods in the real type diff --git a/src/NodeDev.Core/Graph.cs b/src/NodeDev.Core/Graph.cs index 88b6a74..160bd1a 100644 --- a/src/NodeDev.Core/Graph.cs +++ b/src/NodeDev.Core/Graph.cs @@ -7,13 +7,13 @@ namespace NodeDev.Core; -public class Graph +public class Graph(NodeClassMethod selfMethod) { internal Dictionary _Nodes = []; public IReadOnlyDictionary Nodes => _Nodes; public NodeClass SelfClass => SelfMethod.Class; - public NodeClassMethod SelfMethod { get; set; } + public NodeClassMethod SelfMethod { get; } = selfMethod; public Project Project => SelfMethod.Class.Project; @@ -36,7 +36,7 @@ public IGraphCanvas? GraphCanvas /// Get the GraphManagerService for this graph and its associated graph canvas. /// This property should be used all the time as it will keep itself up to date with the graph canvas. /// - public GraphManagerService Manager => _graphManagerService ??= new(GraphCanvas ?? new GraphCanvasNoUI(this)); + public GraphManagerService Manager => _graphManagerService ??= new(_graphCanvas ?? new GraphCanvasNoUI(this)); static Graph() { @@ -45,6 +45,7 @@ static Graph() 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}") { } @@ -413,24 +414,6 @@ private static void ConnectInputExpression(Connection input, BuildExpressionInfo #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 Serialization internal record class SerializedGraph(List Nodes); diff --git a/src/NodeDev.Core/ManagerServices/GraphCanvasNoUI.cs b/src/NodeDev.Core/ManagerServices/GraphCanvasNoUI.cs index 007c264..e63c637 100644 --- a/src/NodeDev.Core/ManagerServices/GraphCanvasNoUI.cs +++ b/src/NodeDev.Core/ManagerServices/GraphCanvasNoUI.cs @@ -7,14 +7,9 @@ namespace NodeDev.Core.ManagerServices; /// Represents a GraphCanvas that doesn't have a UI associated. /// This is used when we need to update stuff in a graph but we don't have a UI to update. /// -internal class GraphCanvasNoUI : IGraphCanvas +internal class GraphCanvasNoUI(Graph graph) : IGraphCanvas { - public GraphCanvasNoUI(Graph graph) - { - Graph = graph; - } - - public Graph Graph { get; } + public Graph Graph { get; } = graph; public void AddLinkToGraphCanvas(Connection source, Connection destination) { diff --git a/src/NodeDev.Core/ManagerServices/GraphManagerService.cs b/src/NodeDev.Core/ManagerServices/GraphManagerService.cs index 59da5df..f85272d 100644 --- a/src/NodeDev.Core/ManagerServices/GraphManagerService.cs +++ b/src/NodeDev.Core/ManagerServices/GraphManagerService.cs @@ -96,9 +96,7 @@ public void AddNewConnectionBetween(Connection source, Connection destination) { if (source.IsInput) { - var temp = source; - source = destination; - destination = temp; + (destination, source) = (source, destination); } if (!source._Connections.Contains(destination)) @@ -133,9 +131,7 @@ public void DisconnectConnectionBetween(Connection source, Connection destinatio { if(source.IsInput) { - var temp = source; - source = destination; - destination = temp; + (destination, source) = (source, destination); } source._Connections.Remove(destination); diff --git a/src/NodeDev.Core/NodeProvider.cs b/src/NodeDev.Core/NodeProvider.cs index 320e52a..c3e8431 100644 --- a/src/NodeDev.Core/NodeProvider.cs +++ b/src/NodeDev.Core/NodeProvider.cs @@ -10,12 +10,17 @@ namespace NodeDev.Core // you're welcome public static class NodeProvider { - private static List NodeTypes = new(); + private readonly static List NodeTypes = []; public static void Initialize() { AddNodesFromAssembly(typeof(NodeProvider).Assembly); } + static NodeProvider() + { + Initialize(); + } + // function load a list of all class that inherit from Node public static void AddNodesFromAssembly(Assembly assembly) diff --git a/src/NodeDev.Core/Nodes/GetPropertyOrField.cs b/src/NodeDev.Core/Nodes/GetPropertyOrField.cs index bb1d9f1..e92a0f7 100644 --- a/src/NodeDev.Core/Nodes/GetPropertyOrField.cs +++ b/src/NodeDev.Core/Nodes/GetPropertyOrField.cs @@ -83,6 +83,9 @@ public void SetMemberTarget(IMemberInfo memberInfo) internal override void BuildInlineExpression(BuildExpressionInfo info) { + if (TargetMember == null) + throw new InvalidOperationException("Target member is not set"); + var type = TargetMember.DeclaringType.MakeRealType(); var binding = BindingFlags.Public | BindingFlags.NonPublic | (TargetMember.IsStatic ? BindingFlags.Static : BindingFlags.Instance); diff --git a/src/NodeDev.Core/Nodes/MethodCall.cs b/src/NodeDev.Core/Nodes/MethodCall.cs index 3beb46c..d37819c 100644 --- a/src/NodeDev.Core/Nodes/MethodCall.cs +++ b/src/NodeDev.Core/Nodes/MethodCall.cs @@ -58,7 +58,7 @@ public override IEnumerable AlternatesOverloads { var parentType = TargetMethod?.DeclaringType; if (TargetMethod == null || parentType == null) - return Enumerable.Empty(); + return []; var methods = parentType.GetMethods(TargetMethod.Name); @@ -84,8 +84,8 @@ public override void SelectOverload(AlternateOverload overload, out List(); - removedConnections = new List(); + newConnections = []; + removedConnections = []; return; } diff --git a/src/NodeDev.Core/Nodes/Node.cs b/src/NodeDev.Core/Nodes/Node.cs index e78918a..120cb7d 100644 --- a/src/NodeDev.Core/Nodes/Node.cs +++ b/src/NodeDev.Core/Nodes/Node.cs @@ -24,9 +24,9 @@ public Node(Graph graph, string? id = null) public abstract string TitleColor { get; } - public List Inputs { get; } = new(); + public List Inputs { get; } = []; - public List Outputs { get; } = new(); + public List Outputs { get; } = []; public IEnumerable InputsAndOutputs => Inputs.Concat(Outputs); @@ -220,7 +220,7 @@ private static NodePaths SearchAllExecPaths(Connection outputExec, HashSet #region Decorations - public Dictionary Decorations { get; init; } = new(); + public Dictionary Decorations { get; init; } = []; public void AddDecoration(T attribute) where T : INodeDecoration => Decorations[typeof(T)] = attribute; @@ -249,21 +249,20 @@ internal SerializedNode Serialize() internal static Node Deserialize(Graph graph, SerializedNode serializedNodeObj) { - var type = graph.SelfClass.TypeFactory.GetTypeByFullName(serializedNodeObj.Type) ?? throw new Exception($"Unable to find type: {serializedNodeObj.Type}"); + var type = TypeFactory.GetTypeByFullName(serializedNodeObj.Type) ?? throw new Exception($"Unable to find type: {serializedNodeObj.Type}"); var node = (Node?)Activator.CreateInstance(type, graph, serializedNodeObj.Id) ?? throw new Exception($"Unable to create instance of type: {serializedNodeObj.Type}"); foreach (var decoration in serializedNodeObj.Decorations) { - var decorationType = graph.SelfClass.TypeFactory.GetTypeByFullName(decoration.Key) ?? throw new Exception($"Unable to find type: {decoration.Key}"); + var decorationType = TypeFactory.GetTypeByFullName(decoration.Key) ?? throw new Exception($"Unable to find type: {decoration.Key}"); var method = decorationType.GetMethod(nameof(INodeDecoration.Deserialize), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static); if (method == null) throw new Exception($"Unable to find Deserialize method on type: {decoration.Key}"); - var decorationObj = method.Invoke(null, new object[] { graph.SelfClass.TypeFactory, decoration.Value }) as INodeDecoration; - if (decorationObj == null) + if (method.Invoke(null, [graph.SelfClass.TypeFactory, decoration.Value]) is not INodeDecoration decorationObj) throw new Exception($"Unable to deserialize decoration: {decoration.Key}"); node.Decorations[decorationType] = decorationObj; diff --git a/src/NodeDev.Core/Project.cs b/src/NodeDev.Core/Project.cs index 7cfbb2d..1e3d007 100644 --- a/src/NodeDev.Core/Project.cs +++ b/src/NodeDev.Core/Project.cs @@ -29,10 +29,10 @@ internal record class SerializedProject(Guid Id, string NodeDevVersion, List Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? throw new Exception("Unable to get current NodeDev version"); - private List _Classes = []; + private readonly List _Classes = []; public IReadOnlyList Classes => _Classes; - private Dictionary NodeClassTypes = []; + private readonly Dictionary NodeClassTypes = []; public readonly TypeFactory TypeFactory; @@ -85,7 +85,7 @@ public static Project CreateNewDefaultProject(out NodeClassMethod main) project.AddClass(programClass); // Create the main method and add it to the project - main = new NodeClassMethod(programClass, "Main", project.TypeFactory.Get(), new Graph(), true); + main = new NodeClassMethod(programClass, "Main", project.TypeFactory.Get(), true); programClass.AddMethod(main, createEntryAndReturn: true); // Now that the method is created with its entry and return nodes, we can set the default return value of 0 @@ -126,6 +126,9 @@ public string Build(BuildOptions buildOptions) var metadataBuilder = assemblyBuilder.GenerateMetadata(out BlobBuilder? ilStream, out BlobBuilder? fieldData); var peHeaderBuilder = new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage); + if(ilStream == null || fieldData == null) + throw new InvalidOperationException("Unable to generate assembly metadata. ilStream or fieldData was null. This shouldn't happen"); + var peBuilder = new ManagedPEBuilder( header: peHeaderBuilder, metadataRootBuilder: new MetadataRootBuilder(metadataBuilder), @@ -209,7 +212,7 @@ public AssemblyBuilder BuildAndGetAssembly(BuildOptions buildOptions) return process.ExitCode; } - catch (Exception ex) + catch (Exception) { return null; } diff --git a/src/NodeDev.Core/Types/ExecType.cs b/src/NodeDev.Core/Types/ExecType.cs index 4eca3dd..257f06f 100644 --- a/src/NodeDev.Core/Types/ExecType.cs +++ b/src/NodeDev.Core/Types/ExecType.cs @@ -10,7 +10,7 @@ public class ExecType : TypeBase public override bool IsExec => true; - public override TypeBase[] Generics => Array.Empty(); + public override TypeBase[] Generics => []; public override string FriendlyName => "Exec"; @@ -43,7 +43,7 @@ internal protected override string Serialize() return ""; } - public new static ExecType Deserialize(TypeFactory typeFactory, string serialized) + public static ExecType Deserialize(TypeFactory typeFactory, string serialized) { return typeFactory.ExecType; } diff --git a/src/NodeDev.Core/Types/NodeClassArrayType.cs b/src/NodeDev.Core/Types/NodeClassArrayType.cs index 34afffa..1f5cb6c 100644 --- a/src/NodeDev.Core/Types/NodeClassArrayType.cs +++ b/src/NodeDev.Core/Types/NodeClassArrayType.cs @@ -77,7 +77,7 @@ 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) + public static NodeClassArrayType Deserialize(TypeFactory typeFactory, string serializedString) { var deserialized = System.Text.Json.JsonSerializer.Deserialize(serializedString); if (deserialized == null) diff --git a/src/NodeDev.Core/Types/NodeClassType.cs b/src/NodeDev.Core/Types/NodeClassType.cs index 79c023e..921d64c 100644 --- a/src/NodeDev.Core/Types/NodeClassType.cs +++ b/src/NodeDev.Core/Types/NodeClassType.cs @@ -56,7 +56,7 @@ public override TypeBase CloneWithGenerics(TypeBase[] newGenerics) return new NodeClassType(NodeClass, newGenerics); } - public new static NodeClassType Deserialize(TypeFactory typeFactory, string typeName) + public static NodeClassType Deserialize(TypeFactory typeFactory, string typeName) { return typeFactory.Project.GetNodeClassType(typeFactory.Project.Classes.First(x => x.Namespace + "." + x.Name == typeName)); } diff --git a/src/NodeDev.Core/Types/RealType.cs b/src/NodeDev.Core/Types/RealType.cs index 52a84d2..2cfe7bc 100644 --- a/src/NodeDev.Core/Types/RealType.cs +++ b/src/NodeDev.Core/Types/RealType.cs @@ -36,8 +36,8 @@ public class RealType : TypeBase /// /// Types that the UI will show a textbox for editing /// - private static readonly List AllowedEditTypes = new() - { + private static readonly List AllowedEditTypes = + [ typeof(int), typeof(string), typeof(bool), @@ -65,7 +65,7 @@ public class RealType : TypeBase typeof(ushort?), typeof(sbyte?), typeof(char?), - }; + ]; public override bool AllowTextboxEdit => AllowedEditTypes.Contains(BackendType); public override string? DefaultTextboxValue { @@ -245,17 +245,17 @@ private TypeBase MatchGenericTypes(Type typeUsingOurGenerics) return new RealType(TypeFactory, typeUsingOurGenerics, generics); } - private record class SerializedType(string TypeFullName, string[] SerializedGenerics); + private record class SerializedRealType(string TypeFullName, string[] SerializedGenerics); internal protected override string Serialize() { - return System.Text.Json.JsonSerializer.Serialize(new SerializedType(BackendType.FullName!, Generics.Select(x => x.SerializeWithFullTypeNameString()).ToArray())); + return System.Text.Json.JsonSerializer.Serialize(new SerializedRealType(BackendType.FullName!, Generics.Select(x => x.SerializeWithFullTypeNameString()).ToArray())); } - public new static RealType Deserialize(TypeFactory typeFactory, string serializedString) + public static RealType Deserialize(TypeFactory typeFactory, string serializedString) { - var serializedType = System.Text.Json.JsonSerializer.Deserialize(serializedString) ?? throw new Exception("Unable to deserialize type"); + var serializedType = System.Text.Json.JsonSerializer.Deserialize(serializedString) ?? throw new Exception("Unable to deserialize type"); - var type = typeFactory.GetTypeByFullName(serializedType.TypeFullName) ?? throw new Exception($"Type not found: {serializedType.TypeFullName}"); + var type = TypeFactory.GetTypeByFullName(serializedType.TypeFullName) ?? throw new Exception($"Type not found: {serializedType.TypeFullName}"); var generics = serializedType.SerializedGenerics.Select(x => DeserializeFullTypeNameString(typeFactory, x)).ToArray(); diff --git a/src/NodeDev.Core/Types/TypeBase.cs b/src/NodeDev.Core/Types/TypeBase.cs index 4510d5e..d76950d 100644 --- a/src/NodeDev.Core/Types/TypeBase.cs +++ b/src/NodeDev.Core/Types/TypeBase.cs @@ -347,9 +347,7 @@ public bool IsAssignableTo(TypeBase other, [MaybeNullWhen(false)] out Dictionary if (swapped) { - var temp = changedGenericsLeftLocally; - changedGenericsLeftLocally = changedGenericsRightLocally; - changedGenericsRightLocally = temp; + (changedGenericsRightLocally, changedGenericsLeftLocally) = (changedGenericsLeftLocally, changedGenericsRightLocally); } foreach (var changed in changedGenericsLeftLocally) @@ -367,9 +365,7 @@ public bool IsAssignableTo(TypeBase other, [MaybeNullWhen(false)] out Dictionary if (swapped) { - var temp = changedGenericsLeftLocally; - changedGenericsLeftLocally = changedGenericsRightLocally; - changedGenericsRightLocally = temp; + (changedGenericsRightLocally, changedGenericsLeftLocally) = (changedGenericsLeftLocally, changedGenericsRightLocally); } foreach (var changed in changedGenericsLeftLocally) @@ -452,11 +448,11 @@ public static TypeBase DeserializeFullTypeNameString(TypeFactory typeFactory, st public static TypeBase Deserialize(TypeFactory typeFactory, SerializedType serializedType) { - var type = typeFactory.GetTypeByFullName(serializedType.TypeFullName) ?? throw new Exception($"Type not found: {serializedType.TypeFullName}"); + var type = TypeFactory.GetTypeByFullName(serializedType.TypeFullName) ?? throw new Exception($"Type not found: {serializedType.TypeFullName}"); var deserializeMethod = type.GetMethod("Deserialize", BindingFlags.Public | BindingFlags.Static) ?? throw new Exception($"Deserialize method not found in type: {serializedType.TypeFullName}"); - var deserializedType = deserializeMethod.Invoke(null, new object[] { typeFactory, serializedType.SerializedTypeCustom }); + var deserializedType = deserializeMethod.Invoke(null, [typeFactory, serializedType.SerializedTypeCustom]); if (deserializedType is TypeBase typeBase) return typeBase; diff --git a/src/NodeDev.Core/Types/TypeFactory.cs b/src/NodeDev.Core/Types/TypeFactory.cs index d519917..d488189 100644 --- a/src/NodeDev.Core/Types/TypeFactory.cs +++ b/src/NodeDev.Core/Types/TypeFactory.cs @@ -5,8 +5,8 @@ namespace NodeDev.Core.Types; public class TypeFactory { - public List IncludedNamespaces = new() - { + public List IncludedNamespaces = + [ "System", "System.Collections.Generic", "System.Linq", @@ -14,19 +14,19 @@ public class TypeFactory "System.Threading", "System.Threading.Tasks", "System.Diagnostics", - }; - private Dictionary> TypeCorrespondances = new() + ]; + private readonly Dictionary> TypeCorrespondances = new() { - ["System.Int32"] = new() { "int" }, - ["System.Int64"] = new() { "long" }, - ["System.Single"] = new() { "float" }, - ["System.Double"] = new() { "double" }, - ["System.Boolean"] = new() { "bool" }, - ["System.String"] = new() { "string" }, - ["System.Void"] = new() { "void" }, + ["System.Int32"] = ["int"], + ["System.Int64"] = ["long"], + ["System.Single"] = ["float"], + ["System.Double"] = ["double"], + ["System.Boolean"] = ["bool"], + ["System.String"] = ["string"], + ["System.Void"] = ["void"], }; - private ExecType ExecType_; + private readonly ExecType ExecType_; private readonly Dictionary RealTypesCache = new(5_000); @@ -78,12 +78,12 @@ public RealType Get(Type type, TypeBase[]? generics) return realTypeWithGenerics; } - public Type? GetTypeByFullName(string name) + public static Type? GetTypeByFullName(string name) { return AppDomain.CurrentDomain.GetAssemblies().Select(x => x.GetType(name)).FirstOrDefault(x => x != null); } - private Type? GetTypeFromAllAssemblies(string name) + private static Type? GetTypeFromAllAssemblies(string name) { if (string.IsNullOrWhiteSpace(name)) return null; diff --git a/src/NodeDev.Tests/EventsTests.cs b/src/NodeDev.Tests/EventsTests.cs index 85fae13..f04f61c 100644 --- a/src/NodeDev.Tests/EventsTests.cs +++ b/src/NodeDev.Tests/EventsTests.cs @@ -19,11 +19,12 @@ public void TestPropertyRenameAndTypeChange() var prop = new NodeClassProperty(myClass, "MyProp", project.TypeFactory.Get()); myClass.Properties.Add(prop); - var graph = new Graph(); - var method = new NodeClassMethod(myClass, "Main", myClass.TypeFactory.Get(), graph); + var method = new NodeClassMethod(myClass, "Main", myClass.TypeFactory.Get()); method.Parameters.Add(new("A", myClass.TypeFactory.Get(), method)); myClass.AddMethod(method, true); + var graph = method.Graph; + var entryNode = graph.Nodes.Values.OfType().First(); var returnNode = graph.Nodes.Values.OfType().First(); diff --git a/src/NodeDev.Tests/GraphExecutorTests.cs b/src/NodeDev.Tests/GraphExecutorTests.cs index 003bc2a..e310e1f 100644 --- a/src/NodeDev.Tests/GraphExecutorTests.cs +++ b/src/NodeDev.Tests/GraphExecutorTests.cs @@ -16,14 +16,14 @@ public static Graph CreateSimpleAddGraph(out EntryNode entryNode, out var nodeClass = new NodeClass("Program", "Test", project); project.AddClass(nodeClass); - var graph = new Graph(); - var method = new NodeClassMethod(nodeClass, "MainInternal", nodeClass.TypeFactory.Get(), graph, true); + var method = new NodeClassMethod(nodeClass, "MainInternal", nodeClass.TypeFactory.Get(), true); nodeClass.AddMethod(method, createEntryAndReturn: false); - graph.SelfMethod = method; method.Parameters.Add(new("A", nodeClass.TypeFactory.Get(), method)); method.Parameters.Add(new("B", nodeClass.TypeFactory.Get(), method)); + var graph = method.Graph; + entryNode = new EntryNode(graph); returnNode = new ReturnNode(graph); @@ -52,13 +52,12 @@ public static Graph CreateSimpleAddGraph(out EntryNode entryNode, out public static Graph CreateStaticMainWithConversion(NodeClass nodeClass, NodeClassMethod internalMethod) { // Now that the fake method is created we need to create the real Main method, taking string[] as input and converting the first two elements to TIn - var graph = new Graph(); - var mainMethod = new NodeClassMethod(nodeClass, "Main", nodeClass.TypeFactory.Void, graph, true); + var mainMethod = new NodeClassMethod(nodeClass, "Main", nodeClass.TypeFactory.Void, true); nodeClass.AddMethod(mainMethod, createEntryAndReturn: false); - graph.SelfMethod = mainMethod; mainMethod.Parameters.Add(new("args", nodeClass.TypeFactory.Get(), mainMethod)); + var graph = mainMethod.Graph; var entryNode = new EntryNode(graph); var returnNode = new ReturnNode(graph); @@ -300,12 +299,11 @@ public void TestTryCatchNode(SerializableBuildOptions options) var nodeClass = new NodeClass("Program", "Test", project); project.AddClass(nodeClass); - var graph = new Graph(); - var method = new NodeClassMethod(nodeClass, "MainInternal", nodeClass.TypeFactory.Get(), graph); + var method = new NodeClassMethod(nodeClass, "MainInternal", nodeClass.TypeFactory.Get()); method.IsStatic = true; nodeClass.AddMethod(method, createEntryAndReturn: false); - graph.SelfMethod = nodeClass.Methods.First(); + var graph = method.Graph; var entryNode = new EntryNode(graph); var tryCatchNode = new TryCatchNode(graph); tryCatchNode.Outputs[3].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get(), overrideInitialType: true); @@ -368,13 +366,12 @@ public void TestDeclareAndSetVariable(SerializableBuildOptions options) var nodeClass = new NodeClass("Program", "Test", project); project.AddClass(nodeClass); - var graph = new Graph(); - var method = new NodeClassMethod(nodeClass, "MainInternal", nodeClass.TypeFactory.Get(), graph, true); + var method = new NodeClassMethod(nodeClass, "MainInternal", nodeClass.TypeFactory.Get(), true); nodeClass.AddMethod(method, createEntryAndReturn: false); - graph.SelfMethod = method; method.Parameters.Add(new("A", nodeClass.TypeFactory.Get(), method)); + var graph = method.Graph; var entryNode = new EntryNode(graph); graph.Manager.AddNode(entryNode); @@ -413,13 +410,12 @@ public void TestDeclareVariableDefaultValue(SerializableBuildOptions options) var nodeClass = new NodeClass("Program", "Test", project); project.AddClass(nodeClass); - var graph = new Graph(); - var method = new NodeClassMethod(nodeClass, "MainInternal", nodeClass.TypeFactory.Get(), graph, true); + var method = new NodeClassMethod(nodeClass, "MainInternal", nodeClass.TypeFactory.Get(), true); nodeClass.AddMethod(method, createEntryAndReturn: false); - graph.SelfMethod = method; method.Parameters.Add(new("A", nodeClass.TypeFactory.Get(), method)); + var graph = method.Graph; var entryNode = new EntryNode(graph); graph.Manager.AddNode(entryNode); diff --git a/src/NodeDev.Tests/NodeClassTypeCreatorTests.cs b/src/NodeDev.Tests/NodeClassTypeCreatorTests.cs index 597ca0d..51a6a1d 100644 --- a/src/NodeDev.Tests/NodeClassTypeCreatorTests.cs +++ b/src/NodeDev.Tests/NodeClassTypeCreatorTests.cs @@ -71,12 +71,12 @@ public async Task TestNewGetSet(SerializableBuildOptions options) var prop = new NodeClassProperty(myClass, "MyProp", project.TypeFactory.Get()); myClass.Properties.Add(prop); - var graph = new Graph(); - var method = new NodeClassMethod(myClass, "MainInternal", myClass.TypeFactory.Get(), graph); + var method = new NodeClassMethod(myClass, "MainInternal", myClass.TypeFactory.Get()); method.IsStatic = true; myClass.AddMethod(method, createEntryAndReturn: false); method.Parameters.Add(new("A", myClass.TypeFactory.Get(), method)); // TODO REMOVE + var graph = method.Graph; var entryNode = new EntryNode(graph); var returnNode = new ReturnNode(graph); diff --git a/src/NodeDev.Tests/TypeFactoryTests.cs b/src/NodeDev.Tests/TypeFactoryTests.cs index 7bba6d5..9f3f071 100644 --- a/src/NodeDev.Tests/TypeFactoryTests.cs +++ b/src/NodeDev.Tests/TypeFactoryTests.cs @@ -9,14 +9,14 @@ public void GetType_NetType() { var typeFactory = new TypeFactory(new(Guid.NewGuid())); - var type = typeFactory.GetTypeByFullName(typeof(string).FullName!); + var type = TypeFactory.GetTypeByFullName(typeof(string).FullName!); Assert.Same(typeof(string), type); - type = typeFactory.GetTypeByFullName(typeof(List).FullName!); + type = TypeFactory.GetTypeByFullName(typeof(List).FullName!); Assert.Same(typeof(List), type); - type = typeFactory.GetTypeByFullName(typeof(List<>).FullName!); + type = TypeFactory.GetTypeByFullName(typeof(List<>).FullName!); Assert.Same(typeof(List<>), type); }