Ejemplo n.º 1
0
        static State UpdateType(State previousState, UpdateTypeAction action)
        {
            VSGraphModel graphModel = (VSGraphModel)((GraphModel)previousState.CurrentGraphModel);

            if (action.Handle.IsValid)
            {
                Undo.RegisterCompleteObjectUndo(action.VariableDeclarationModel.SerializableAsset, "Update Type");

                if (action.VariableDeclarationModel.DataType != action.Handle)
                {
                    action.VariableDeclarationModel.CreateInitializationValue();
                }

                action.VariableDeclarationModel.DataType = action.Handle;

                foreach (var usage in graphModel.FindUsages <VariableNodeModel>(action.VariableDeclarationModel))
                {
                    usage.UpdateTypeFromDeclaration();
                }

                previousState.MarkForUpdate(UpdateFlags.RequestRebuild);
            }

            return(previousState);
        }
        static State CreateGraphAsset(State previousState, CreateGraphAssetAction action)
        {
            previousState.AssetModel?.Dispose();
            using (new AssetWatcher.Scope())
            {
                GraphAssetModel graphAssetModel = GraphAssetModel.Create(action.Name, action.AssetPath, action.AssetType, action.WriteOnDisk);

                var graphModel = graphAssetModel.CreateGraph(action.GraphType, action.Name, action.StencilType, action.WriteOnDisk);
                if (action.GraphTemplate != null)
                {
                    action.GraphTemplate.InitBasicGraph(graphModel as VSGraphModel);
                    graphModel.LastChanges.ModelsToAutoAlign.AddRange(graphModel.Stencil.GetEntryPoints((VSGraphModel)graphModel));
                }

                previousState.AssetModel = graphAssetModel;
                if (action.Instance)
                {
                    previousState.EditorDataModel.BoundObject = action.Instance;
                }
            }
            if (action.WriteOnDisk)
            {
                AssetDatabase.SaveAssets();
            }

            AssetWatcher.Instance.WatchGraphAssetAtPath(action.AssetPath, (GraphAssetModel)previousState.AssetModel);
            previousState.MarkForUpdate(UpdateFlags.All);


            return(previousState);
        }
Ejemplo n.º 3
0
        static State CreateObjectReference(State prevState, CreateObjectReferenceAction action)
        {
            var graph = action.GraphModel as VSGraphModel;

            if (action.Type == CreateObjectReferenceAction.ReferenceType.Subgraph)
            {
                DotsStencil.CreateSubGraphReference(graph, action.Objects.OfType <VSGraphAssetModel>(),
                                                    action.GraphSpacePosition);
            }
            else
            {
                if (prevState.EditorDataModel.BoundObject == null)
                {
                    Debug.LogError(
                        "Cannot create object references when a graph is opened in asset mode. Select a game object referencing this graph to do that.");
                    return(prevState);
                }

                var authoringComponent = (prevState.EditorDataModel.BoundObject as GameObject)
                                         ?.GetComponent <ScriptingGraphAuthoring>();
                Assert.IsNotNull(authoringComponent,
                                 "The currently bound object has no ScriptingGraphAuthoring component. This is impossible.");
                DotsStencil.CreateVariablesFromGameObjects(graph, authoringComponent,
                                                           action.Objects.OfType <GameObject>(), action.GraphSpacePosition,
                                                           action.Type == CreateObjectReferenceAction.ReferenceType.ObjectGraph);
            }

            prevState.MarkForUpdate(UpdateFlags.GraphTopology);
            return(prevState);
        }
        static State MergeStack(State previousState, MergeStackAction action)
        {
            VSGraphModel graphModel  = (VSGraphModel)previousState.CurrentGraphModel;
            var          stackModelA = (StackBaseModel)action.StackModelA;
            var          stackModelB = (StackBaseModel)action.StackModelB;

            Undo.RegisterCompleteObjectUndo((Object)graphModel.AssetModel, "Move stacked nodes");
            // Move all nodes from stackB to stackA
            stackModelA.MoveStackedNodes(stackModelB.NodeModels.ToList(), -1, false);

            // Move output connections of stackB to stackA
            var previousEdgeConnections = graphModel.GetEdgesConnections(stackModelB.OutputPorts.First()).ToList();

            foreach (var edge in previousEdgeConnections)
            {
                graphModel.CreateEdge(edge.InputPortModel, stackModelA.OutputPorts.First());
            }

            // Delete stackB
            graphModel.DeleteNode(stackModelB, GraphModel.DeleteConnections.True);

            previousState.MarkForUpdate(UpdateFlags.GraphTopology);

            return(previousState);
        }
        static State LoadGraphAsset(State previousState, LoadGraphAssetAction action)
        {
            if (ReferenceEquals(Selection.activeObject, previousState.AssetModel))
            {
                Selection.activeObject = null;
            }
            if (previousState.CurrentGraphModel != null)
            {
                // force queued compilation to happen now when unloading a graph
                if (previousState.EditorDataModel?.CompilationPending ?? false)
                {
                    previousState.EditorDataModel.RequestCompilation(RequestCompilationOptions.Default);
                    previousState.EditorDataModel.CompilationPending = false;
                }
            }
            previousState.AssetModel?.Dispose();
            previousState.EditorDataModel.PluginRepository?.UnregisterPlugins();

            var asset = AssetDatabase.LoadAssetAtPath <GraphAssetModel>(action.AssetPath);

            if (!asset)
            {
                Debug.LogError($"Could not load visual scripting asset at path '{action.AssetPath}'");
                return(previousState);
            }
            AssetWatcher.Instance.WatchGraphAssetAtPath(action.AssetPath, asset);

            switch (action.LoadType)
            {
            case LoadGraphAssetAction.Type.Replace:
                previousState.EditorDataModel.PreviousGraphModels.Clear();
                break;

            case LoadGraphAssetAction.Type.PushOnStack:
                previousState.EditorDataModel.PreviousGraphModels.Add(new OpenedGraph((GraphAssetModel)previousState.CurrentGraphModel?.AssetModel, previousState.EditorDataModel?.BoundObject));
                break;

            case LoadGraphAssetAction.Type.KeepHistory:
                break;
            }

            previousState.AssetModel = asset;
            previousState.EditorDataModel.BoundObject = action.BoundObject;
            previousState.MarkForUpdate(UpdateFlags.All);

            var graphModel = previousState.CurrentGraphModel;

            if (graphModel?.Stencil != null)
            {
                graphModel.Stencil.PreProcessGraph((VSGraphModel)previousState.CurrentGraphModel);
                if (action.AlignAfterLoad)
                {
                    graphModel.LastChanges.ModelsToAutoAlign.AddRange(graphModel.Stencil.GetEntryPoints((VSGraphModel)graphModel));
                }
            }

            CheckGraphIntegrity(previousState);

            return(previousState);
        }
        static State ChangePlacematPosition(State previousState, ChangePlacematPositionAction action)
        {
            if (action.ResizeFlags == ResizeFlags.None)
            {
                return(previousState);
            }

            Undo.RegisterCompleteObjectUndo((Object)previousState.AssetModel, "Resize Placemat");
            EditorUtility.SetDirty((Object)previousState.AssetModel);
            foreach (var placematModel in action.Models)
            {
                var newRect = placematModel.PositionAndSize;
                if ((action.ResizeFlags & ResizeFlags.Left) == ResizeFlags.Left)
                {
                    newRect.x = action.Value.x;
                }
                if ((action.ResizeFlags & ResizeFlags.Top) == ResizeFlags.Top)
                {
                    newRect.y = action.Value.y;
                }
                if ((action.ResizeFlags & ResizeFlags.Width) == ResizeFlags.Width)
                {
                    newRect.width = action.Value.width;
                }
                if ((action.ResizeFlags & ResizeFlags.Height) == ResizeFlags.Height)
                {
                    newRect.height = action.Value.height;
                }
                placematModel.PositionAndSize = newRect;
                previousState.MarkForUpdate(UpdateFlags.UpdateView, placematModel);
            }
            return(previousState);
        }
        static State BuildAllEditor(State previousState, BuildAllEditorAction action)
        {
            BuildAll(action.Callback);

            previousState.MarkForUpdate(UpdateFlags.All | UpdateFlags.CompilationResult);

            return(previousState);
        }
 static State RefreshUI(State previousState, RefreshUIAction action)
 {
     previousState.MarkForUpdate(action.UpdateFlags);
     if (action.ChangedModels != null)
     {
         ((VSGraphModel)previousState.CurrentGraphModel).LastChanges.ChangedElements.AddRange(action.ChangedModels);
     }
     return(previousState);
 }
Ejemplo n.º 9
0
        static State UpdateExposed(State previousState, UpdateExposedAction action)
        {
            Undo.RegisterCompleteObjectUndo(action.VariableDeclarationModel.SerializableAsset, "Update Exposed");
            action.VariableDeclarationModel.IsExposed = action.Exposed;

            previousState.MarkForUpdate(UpdateFlags.RequestRebuild);

            return(previousState);
        }
        static State CreatePlacemat(State previousState, CreatePlacematAction action)
        {
            Undo.RegisterCompleteObjectUndo((Object)previousState.AssetModel, "Create Placemat");
            EditorUtility.SetDirty((Object)previousState.AssetModel);

            ((VSGraphModel)previousState.CurrentGraphModel).CreatePlacemat(action.Title, action.Position);

            previousState.MarkForUpdate(UpdateFlags.GraphTopology);
            return(previousState);
        }
Ejemplo n.º 11
0
        static State CreateGraphVariableDeclaration(State previousState, CreateGraphVariableDeclarationAction action)
        {
            var graphModel = (VSGraphModel)previousState.CurrentGraphModel;
            VariableDeclarationModel variableDeclaration = graphModel.CreateGraphVariableDeclaration(action.Name, action.TypeHandle, action.IsExposed);

            variableDeclaration.Modifiers = action.ModifierFlags;
            previousState.EditorDataModel.ElementModelToRename = variableDeclaration;
            previousState.MarkForUpdate(UpdateFlags.RequestRebuild);
            return(previousState);
        }
 static State SetNodeEnabledState(State previousState, SetNodeEnabledStateAction action)
 {
     Undo.RegisterCompleteObjectUndo((Object)previousState.AssetModel, action.State == ModelState.Enabled ? "Enable Nodes" : "Disable Nodes");
     EditorUtility.SetDirty((Object)previousState.AssetModel);
     foreach (NodeModel nodeModel in action.NodeToConvert)
     {
         nodeModel.State = action.State;
     }
     previousState.MarkForUpdate(UpdateFlags.GraphTopology);
     return(previousState);
 }
        static State UnloadGraphAsset(State previousState, UnloadGraphAssetAction action)
        {
            if (previousState.CurrentGraphModel != null)
            {
                AssetWatcher.Instance.UnwatchGraphAssetAtPath(previousState.CurrentGraphModel.GetAssetPath());
            }
            previousState.UnloadCurrentGraphAsset();
            previousState.MarkForUpdate(UpdateFlags.All);

            return(previousState);
        }
        static State ExpandOrCollapsePlacemat(State previousState, ExpandOrCollapsePlacematAction action)
        {
            Undo.RegisterCompleteObjectUndo((Object)previousState.AssetModel, action.Collapse ? "Collapse Placemat" : "Expand Placemat");
            EditorUtility.SetDirty((Object)previousState.AssetModel);

            action.PlacematModel.Collapsed      = action.Collapse;
            action.PlacematModel.HiddenElements = action.PlacematModel.Collapsed ? action.CollapsedElements : null;

            previousState.MarkForUpdate(UpdateFlags.UpdateView, action.PlacematModel);
            return(previousState);
        }
 static State RenamePlacemat(State previousState, ChangePlacematTitleAction action)
 {
     Undo.RegisterCompleteObjectUndo((Object)previousState.AssetModel, "Rename Placemat");
     EditorUtility.SetDirty((Object)previousState.AssetModel);
     foreach (var placematModel in action.Models)
     {
         placematModel.Rename(action.Value);
         previousState.MarkForUpdate(UpdateFlags.UpdateView, placematModel);
     }
     return(previousState);
 }
        static State UpdateStickyNote(State previousState, UpdateStickyNoteAction action)
        {
            Undo.RegisterCompleteObjectUndo((Object)previousState.AssetModel, "Change Sticky Note Content");
            EditorUtility.SetDirty((Object)previousState.AssetModel);

            action.StickyNoteModel.Title    = action.Title;
            action.StickyNoteModel.Contents = action.Contents;

            previousState.MarkForUpdate(UpdateFlags.UpdateView, action.StickyNoteModel);
            return(previousState);
        }
Ejemplo n.º 17
0
        static State OpenDocumentation(State previousState, OpenDocumentationAction action)
        {
            foreach (var nodeModel in action.NodeModels)
            {
                // TODO: Get the right path for the documentation
                Help.BrowseURL("https://docs.unity3d.com/Manual/30_search.html?q=" + nodeModel.GetType().Name);
                break;
            }

            previousState.MarkForUpdate(UpdateFlags.None);
            return(previousState);
        }
 static State ResetElementColor(State previousState, ResetElementColorAction action)
 {
     Undo.RegisterCompleteObjectUndo((Object)previousState.AssetModel, "Reset Color");
     EditorUtility.SetDirty((Object)previousState.AssetModel);
     if (action.NodeModels != null)
     {
         foreach (var model in action.NodeModels)
         {
             model.HasUserColor = false;
             previousState.MarkForUpdate(UpdateFlags.UpdateView, model);
         }
     }
     if (action.PlacematModels != null)
     {
         foreach (var model in action.PlacematModels)
         {
             model.Color = PlacematModel.k_DefaultColor;
             previousState.MarkForUpdate(UpdateFlags.UpdateView, model);
         }
     }
     return(previousState);
 }
 static State ChangeElementColor(State previousState, ChangeElementColorAction action)
 {
     Undo.RegisterCompleteObjectUndo((Object)previousState.AssetModel, "Change Color");
     EditorUtility.SetDirty((Object)previousState.AssetModel);
     if (action.NodeModels != null)
     {
         foreach (var model in action.NodeModels)
         {
             model.ChangeColor(action.Color);
             previousState.MarkForUpdate(UpdateFlags.UpdateView, model);
         }
     }
     if (action.PlacematModels != null)
     {
         foreach (var model in action.PlacematModels)
         {
             model.Color = action.Color;
             previousState.MarkForUpdate(UpdateFlags.UpdateView, model);
         }
     }
     return(previousState);
 }
        static State UpdateStickyNoteTextSize(State previousState, UpdateStickyNoteTextSizeAction action)
        {
            Undo.RegisterCompleteObjectUndo((Object)previousState.AssetModel, "Change Sticky Note Font Size");
            EditorUtility.SetDirty((Object)previousState.AssetModel);

            foreach (var noteModel in action.Models)
            {
                noteModel.TextSize = action.Value;
                previousState.MarkForUpdate(UpdateFlags.UpdateView, noteModel);
            }

            return(previousState);
        }
        static State CreateNodeFromSearcher(State previousState, CreateNodeFromSearcherAction action)
        {
            var nodes = action.SelectedItem.CreateElements.Invoke(
                new GraphNodeCreationData(action.GraphModel, action.Position, guids: action.Guids));

            if (nodes.Any(n => n is EdgeModel))
            {
                previousState.CurrentGraphModel.LastChanges.ModelsToAutoAlign.AddRange(nodes);
            }

            previousState.MarkForUpdate(UpdateFlags.GraphTopology);
            return(previousState);
        }
        static State RenameElement(State previousState, RenameElementAction action)
        {
            var graphModel = (VSGraphModel)previousState.CurrentGraphModel;

            if (string.IsNullOrWhiteSpace(action.Name))
            {
                return(previousState);
            }

            Undo.RegisterCompleteObjectUndo((Object)graphModel.AssetModel, "Rename");
            action.RenamableModel.Rename(action.Name);
            EditorUtility.SetDirty((Object)graphModel.AssetModel);

            IGraphChangeList graphChangeList = previousState.CurrentGraphModel.LastChanges;

            VSGraphModel vsGraphModel = (VSGraphModel)previousState.CurrentGraphModel;

            if (action.RenamableModel is VariableDeclarationModel variableDeclarationModel)
            {
                graphChangeList.BlackBoardChanged = true;

                // update usage names
                graphChangeList.ChangedElements.AddRange(vsGraphModel.FindUsages <VariableNodeModel>(variableDeclarationModel));
            }
            else if (action.RenamableModel is IVariableModel variableModel)
            {
                graphChangeList.BlackBoardChanged = true;

                variableDeclarationModel = variableModel.DeclarationModel as VariableDeclarationModel;
                graphChangeList.ChangedElements.Add(variableDeclarationModel);

                graphChangeList.ChangedElements.AddRange(vsGraphModel.FindUsages <VariableNodeModel>(variableDeclarationModel));
            }
            else if (action.RenamableModel is IEdgePortalModel edgePortalModel)
            {
                variableDeclarationModel = edgePortalModel.DeclarationModel as VariableDeclarationModel;
                graphChangeList.ChangedElements.Add(variableDeclarationModel);
                graphChangeList.ChangedElements.AddRange(vsGraphModel.FindUsages <EdgePortalModel>(variableDeclarationModel));
            }
            else
            {
                graphChangeList.ChangedElements.Add(action.RenamableModel as IGraphElementModel);
            }


            previousState.MarkForUpdate(UpdateFlags.RequestCompilation | UpdateFlags.RequestRebuild);

            return(previousState);
        }
        static State SetPosition(State previousState, SetNodePositionAction action)
        {
            Undo.RegisterCompleteObjectUndo((Object)previousState.AssetModel, "Move");

            foreach (var model in action.Models)
            {
                if (model != null)
                {
                    model.Position = action.Value;
                }
                previousState.MarkForUpdate(UpdateFlags.UpdateView, model);
            }

            return(previousState);
        }
        static State SetCollapsed(State previousState, SetNodeCollapsedAction action)
        {
            Undo.RegisterCompleteObjectUndo((Object)previousState.AssetModel, "Collapse Node");

            foreach (var model in action.Models)
            {
                if (model is ICollapsible nodeModel)
                {
                    nodeModel.Collapsed = action.Value;
                }
                previousState.MarkForUpdate(UpdateFlags.UpdateView, model);
            }

            return(previousState);
        }
        static State ChangePlacematZOrders(State previousState, ChangePlacematZOrdersAction action)
        {
            Undo.RegisterCompleteObjectUndo((Object)previousState.AssetModel, "Change Placemats Ordering");
            EditorUtility.SetDirty((Object)previousState.AssetModel);

            for (var index = 0; index < action.Models.Length; index++)
            {
                var placematModel = action.Models[index];
                var zOrder        = action.Value[index];
                placematModel.ZOrder = zOrder;
                previousState.MarkForUpdate(UpdateFlags.UpdateView, placematModel);
            }

            return(previousState);
        }
        static State MoveElements(State previousState, MoveElementsAction action)
        {
            if (action.Models == null || action.Delta == Vector2.zero)
            {
                return(previousState);
            }

            Undo.RegisterCompleteObjectUndo((Object)previousState.AssetModel, "Move");

            foreach (var placematModel in action.Models.OfType <IGTFPlacematModel>())
            {
                placematModel.Move(action.Delta);
            }

            // TODO It would be nice to have a single way of moving things around and thus not having to deal with 3
            // separate collections.
            foreach (var nodeModel in action.Models.OfType <INodeModel>())
            {
                ((GraphModel)previousState.CurrentGraphModel).MoveNode(nodeModel, nodeModel.Position + action.Delta);
            }

            foreach (var stickyNoteModel in action.Models.OfType <IGTFStickyNoteModel>())
            {
                stickyNoteModel.PositionAndSize = new Rect(stickyNoteModel.PositionAndSize.position + action.Delta, stickyNoteModel.PositionAndSize.size);
            }

            // Only move an edge if it is connected on both ends to a moving node.
            var edgeModels = action.Models.OfType <IGTFEdgeModel>();

            if (edgeModels.Any())
            {
                var nodeModels = action.Models.OfType <IGTFNodeModel>().ToImmutableHashSet();
                foreach (var edgeModel in edgeModels)
                {
                    if (nodeModels.Contains(edgeModel.FromPort.NodeModel) && nodeModels.Contains(edgeModel.ToPort.NodeModel))
                    {
                        edgeModel.Move(action.Delta);
                    }
                }
            }

            previousState.MarkForUpdate(UpdateFlags.GraphGeometry);
            return(previousState);
        }
        static State ResizeStickyNote(State previousState, ResizeStickyNoteAction action)
        {
            if (action.ResizeWhat == ResizeFlags.None)
            {
                return(previousState);
            }

            Undo.RegisterCompleteObjectUndo((Object)previousState.AssetModel, "Resize Sticky Note");
            EditorUtility.SetDirty((Object)previousState.AssetModel);

            foreach (var noteModel in action.Models)
            {
                var newRect = noteModel.PositionAndSize;
                if ((action.ResizeWhat & ResizeFlags.Left) == ResizeFlags.Left)
                {
                    newRect.x = action.Value.x;
                }
                if ((action.ResizeWhat & ResizeFlags.Top) == ResizeFlags.Top)
                {
                    newRect.y = action.Value.y;
                }
                if ((action.ResizeWhat & ResizeFlags.Width) == ResizeFlags.Width)
                {
                    newRect.width = action.Value.width;
                }
                if ((action.ResizeWhat & ResizeFlags.Height) == ResizeFlags.Height)
                {
                    newRect.height = action.Value.height;
                }

                noteModel.PositionAndSize = newRect;
                previousState.MarkForUpdate(UpdateFlags.UpdateView, noteModel);
            }

            return(previousState);
        }
Ejemplo n.º 28
0
        static State CreatePortalOppositeAction(State previousState, CreatePortalsOppositeAction action)
        {
            if (action.PortalsToOpen == null)
            {
                return(previousState);
            }
            var portalsToOpen = action.PortalsToOpen.Where(p => p.CanCreateOppositePortal()).ToList();

            if (!portalsToOpen.Any())
            {
                return(previousState);
            }

            Undo.RegisterCompleteObjectUndo((Object)previousState.AssetModel, "Create Opposite Portals");
            EditorUtility.SetDirty((Object)previousState.AssetModel);

            foreach (var portalModel in portalsToOpen)
            {
                ((VSGraphModel)previousState.CurrentGraphModel).CreateOppositePortal(portalModel);
            }

            previousState.MarkForUpdate(UpdateFlags.GraphTopology);
            return(previousState);
        }
        static void DeleteElementsFromGraph(State previousState, IReadOnlyCollection <IGTFGraphElementModel> elementsToRemove, GraphModel graphModel)
        {
            IGTFGraphElementModel[] deletables = elementsToRemove.Where(x => x is IDeletable).Distinct().ToArray();

            var vsGraphModel = (VSGraphModel)graphModel;

            IStickyNoteModel[] stickyNotesToDelete         = GetStickyNotesToDelete(deletables);
            IPlacematModel[]   placematsToDelete           = GetPlacematsToDelete(deletables);
            IReadOnlyCollection <INodeModel> nodesToDelete = GetNodesToDelete(vsGraphModel, deletables);
            IReadOnlyCollection <IEdgeModel> edgesToDelete = GetEdgesToDelete(graphModel, deletables, nodesToDelete);

            VariableDeclarationModel[] declarationModelsToDelete = GetDeclarationModelsToDelete(deletables, nodesToDelete);

            if (declarationModelsToDelete.Any())
            {
                previousState.MarkForUpdate(UpdateFlags.RequestRebuild);
            }

            graphModel.DeleteStickyNotes(stickyNotesToDelete);
            graphModel.DeletePlacemats(placematsToDelete);
            graphModel.DeleteEdges(edgesToDelete);
            graphModel.DeleteNodes(nodesToDelete, GraphModel.DeleteConnections.False);
            vsGraphModel.DeleteVariableDeclarations(declarationModelsToDelete, false);
        }
        static State ConvertEdgesToPortals(State previousState, ConvertEdgesToPortalsAction action)
        {
            var graphModel = (VSGraphModel)previousState.CurrentGraphModel;

            if (action.EdgeData == null)
            {
                return(previousState);
            }

            var edgeData = action.EdgeData.ToList();

            if (!edgeData.Any())
            {
                return(previousState);
            }

            Undo.RegisterCompleteObjectUndo((Object)previousState.AssetModel, "Convert edges to portals");
            EditorUtility.SetDirty((Object)previousState.AssetModel);

            var existingPortalEntries = new Dictionary <IPortModel, IEdgePortalEntryModel>();
            var existingPortalExits   = new Dictionary <IPortModel, List <IEdgePortalExitModel> >();

            foreach (var edgeModel in edgeData)
            {
                ConvertEdgeToPortals(edgeModel);
            }

            // Adjust placement in case of multiple incoming exit portals so they don't overlap
            foreach (var portalList in existingPortalExits.Values.Where(l => l.Count > 1))
            {
                var  cnt    = portalList.Count;
                bool isEven = cnt % 2 == 0;
                int  offset = isEven ? k_PortalHeight / 2 : 0;
                for (int i = (cnt - 1) / 2; i >= 0; i--)
                {
                    portalList[i].Position           = new Vector2(portalList[i].Position.x, portalList[i].Position.y - offset);
                    portalList[cnt - 1 - i].Position = new Vector2(portalList[cnt - 1 - i].Position.x, portalList[cnt - 1 - i].Position.y + offset);
                    offset += k_PortalHeight;
                }
            }

            graphModel.DeleteEdges(edgeData.Select(d => d.edge));
            previousState.MarkForUpdate(UpdateFlags.GraphTopology);
            return(previousState);

            void ConvertEdgeToPortals((IEdgeModel edgeModel, Vector2 startPos, Vector2 endPos) data)
            {
                // Only a single portal per output port. Don't recreate if we already created one.
                var outputPortModel = data.edgeModel.OutputPortModel;

                if (!existingPortalEntries.TryGetValue(outputPortModel, out var portalEntry))
                {
                    if (outputPortModel.PortType == PortType.Execution)
                    {
                        portalEntry = graphModel.CreateNode <ExecutionEdgePortalEntryModel>();
                    }
                    else
                    {
                        portalEntry = graphModel.CreateNode <DataEdgePortalEntryModel>();
                    }
                    existingPortalEntries[outputPortModel] = portalEntry;

                    var nodeModel = outputPortModel.NodeModel;
                    portalEntry.Position = data.startPos + k_EntryPortalBaseOffset;

                    // y offset based on port order. hurgh.
                    var idx = nodeModel.OutputsByDisplayOrder.IndexOf(outputPortModel);
                    portalEntry.Position += Vector2.down * (k_PortalHeight * idx + 16); // Fudgy.

                    string portalName;
                    if (nodeModel is IConstantNodeModel constantNodeModel)
                    {
                        portalName = constantNodeModel.Type.FriendlyName();
                    }
                    else
                    {
                        portalName = nodeModel.Title;
                        if (!string.IsNullOrEmpty(outputPortModel.Name))
                        {
                            portalName += " - " + outputPortModel.Name;
                        }
                    }

                    ((EdgePortalModel)portalEntry).DeclarationModel = graphModel.CreateGraphPortalDeclaration(portalName);

                    graphModel.CreateEdge(portalEntry.InputPort, outputPortModel);
                }

                // We can have multiple portals on input ports however
                var inputPortModel = data.edgeModel.InputPortModel;

                if (!existingPortalExits.TryGetValue(inputPortModel, out var portalExits))
                {
                    portalExits = new List <IEdgePortalExitModel>();
                    existingPortalExits[inputPortModel] = portalExits;
                }

                IEdgePortalExitModel portalExit;

                if (inputPortModel.PortType == PortType.Execution)
                {
                    portalExit = graphModel.CreateNode <ExecutionEdgePortalExitModel>();
                }
                else
                {
                    portalExit = graphModel.CreateNode <DataEdgePortalExitModel>();
                }

                portalExits.Add(portalExit);

                portalExit.Position = data.endPos + k_ExitPortalBaseOffset;
                {
                    var nodeModel = inputPortModel.NodeModel;
                    // y offset based on port order. hurgh.
                    var idx = nodeModel.InputsByDisplayOrder.IndexOf(inputPortModel);
                    portalExit.Position += Vector2.down * (k_PortalHeight * idx + 16); // Fudgy.
                }

                ((EdgePortalModel)portalExit).DeclarationModel = portalEntry.DeclarationModel;

                graphModel.CreateEdge(inputPortModel, portalExit.OutputPort);
            }
        }