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); }
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); }
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); }
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); }
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); }
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); } }