public void Setup(string nodeName, string nodeId, string unused_connectionIdToNextNode, Dictionary <string, List <Asset> > groupedSources, List <string> alreadyCached, Action <string, string, Dictionary <string, List <Asset> >, List <string> > Output) { try { ValidateBundleNameTemplate( bundleNameTemplate, () => { throw new NodeException(nodeName + ":Bundle Name Template is empty.", nodeId); } ); foreach (var name in variants.Values) { ValidateVariantName(name, variants.Values.ToList(), () => { throw new NodeException(nodeName + ":Variant is empty.", nodeId); }, () => { throw new NodeException(nodeName + ":Variant name cannot contain whitespace \"" + name + "\".", nodeId); }, () => { throw new NodeException(nodeName + ":Variant name already exists \"" + name + "\".", nodeId); }); } } catch (NodeException e) { AssetBundleGraphEditorWindow.AddNodeException(e); return; } var recommendedBundleOutputDir = FileUtility.PathCombine(AssetBundleGraphSettings.BUNDLIZER_CACHE_PLACE, nodeId, SystemDataUtility.GetCurrentPlatformKey()); var outputDict = new Dictionary <string, List <Asset> >(); foreach (var groupKey in groupedSources.Keys) { var inputSources = groupedSources[groupKey]; var reservedBundlePath = BundlizeAssets(nodeName, groupKey, inputSources, recommendedBundleOutputDir, false); if (string.IsNullOrEmpty(reservedBundlePath)) { continue; } var outputSources = new List <Asset>(); var newAssetData = Asset.CreateAssetWithImportPath(reservedBundlePath); outputSources.Add(newAssetData); outputDict[groupKey] = outputSources; } if (assetsOutputConnectionId != AssetBundleGraphSettings.BUNDLIZER_FAKE_CONNECTION_ID) { Output(nodeId, assetsOutputConnectionId, outputDict, new List <string>()); } }
private Type FindIncomingAssetType(ConnectionPointData inputPoint) { var assetGroups = AssetBundleGraphEditorWindow.GetIncomingAssetGroups(inputPoint); if (assetGroups == null) { return(null); } return(TypeUtility.FindIncomingAssetType(assetGroups.SelectMany(v => v.Value).ToList())); }
public void Setup(string nodeName, string nodeId, string unused_connectionIdToNextNode, Dictionary <string, List <Asset> > groupedSources, List <string> alreadyCached, Action <string, string, Dictionary <string, List <Asset> >, List <string> > Output) { // overlapping test. try { var overlappingCheckList = new List <string>(); for (var i = 0; i < containsKeywords.Count; i++) { var keywordAndKeytypeCombind = containsKeywords[i] + containsKeytypes[i]; if (overlappingCheckList.Contains(keywordAndKeytypeCombind)) { throw new NodeException(String.Format("Duplicated filter condition found for [Keyword:{0} Type:{1}]", containsKeywords[i], containsKeytypes[i]), nodeId); } overlappingCheckList.Add(keywordAndKeytypeCombind); } } catch (NodeException e) { AssetBundleGraphEditorWindow.AddNodeException(e); return; } foreach (var groupKey in groupedSources.Keys) { var outputDict = new Dictionary <string, List <Asset> >(); var inputSources = groupedSources[groupKey]; Action <string, List <string> > _PreOutput = (string connectionId, List <string> outputSources) => { var outputs = new List <Asset>(); foreach (var outputSource in outputSources) { foreach (var inputSource in inputSources) { if (outputSource == inputSource.GetAbsolutePathOrImportedPath()) { outputs.Add(inputSource); } } } outputDict[groupKey] = outputs; Output(nodeId, connectionId, outputDict, new List <string>()); }; try { Filter(inputSources, _PreOutput); } catch (Exception e) { Debug.LogError(nodeName + " Error:" + e); } } }
private void GroupingOutput(string nodeName, string nodeId, string connectionIdToNextNode, Dictionary <string, List <Asset> > groupedSources, Action <string, string, Dictionary <string, List <Asset> >, List <string> > Output) { try { ValidateGroupingKeyword( groupingKeyword, () => { throw new NodeException("Grouping Keyword can not be empty.", nodeId); }, () => { throw new NodeException(String.Format("Grouping Keyword must contain {0} for numbering: currently {1}", AssetBundleGraphSettings.KEYWORD_WILDCARD, groupingKeyword), nodeId); } ); } catch (NodeException e) { AssetBundleGraphEditorWindow.AddNodeException(e); return; } var outputDict = new Dictionary <string, List <Asset> >(); var mergedGroupedSources = new List <Asset>(); foreach (var groupKey in groupedSources.Keys) { mergedGroupedSources.AddRange(groupedSources[groupKey]); } foreach (var source in mergedGroupedSources) { var targetPath = source.GetAbsolutePathOrImportedPath(); var groupingKeywordPrefix = groupingKeyword.Split(AssetBundleGraphSettings.KEYWORD_WILDCARD)[0]; var groupingKeywordPostfix = groupingKeyword.Split(AssetBundleGraphSettings.KEYWORD_WILDCARD)[1]; var regex = new Regex(groupingKeywordPrefix + "(.*?)" + groupingKeywordPostfix); var match = regex.Match(targetPath); if (match.Success) { var newGroupingKey = match.Groups[1].Value; if (!outputDict.ContainsKey(newGroupingKey)) { outputDict[newGroupingKey] = new List <Asset>(); } outputDict[newGroupingKey].Add(source); } } Output(nodeId, connectionIdToNextNode, outputDict, new List <string>()); }
private void DoInspectorPrefabricatorGUI(NodeGUI node) { EditorGUILayout.HelpBox("Prefabricator: Create prefab with given assets and script.", MessageType.Info); UpdateNodeName(node); using (new EditorGUILayout.HorizontalScope(GUI.skin.box)) { GUILayout.Label("Prefabricator Class"); if (GUILayout.Button(node.scriptAttrNameOrClassName, "Popup")) { /* * collect type name or "Name" attribute parameter from extended-PrefabricatorBase class. */ var prefabricatorCandidateTypeNameOrAttrName = PrefabricatorBase.GetPrefabricatorAttrName_ClassNameDict(); // prepare for no class found. if (!prefabricatorCandidateTypeNameOrAttrName.Any()) { var menu = new GenericMenu(); menu.AddItem( new GUIContent("Generate Example Prefabricator Script"), false, () => { // generate sample. AssetBundleGraphEditorWindow.GenerateScript(AssetBundleGraphEditorWindow.ScriptType.SCRIPT_PREFABRICATOR); } ); menu.ShowAsContext(); return; } /* * displays type name or attribute if exist. */ NodeGUI.ShowTypeNamesMenu( node.scriptAttrNameOrClassName, prefabricatorCandidateTypeNameOrAttrName.Keys.ToList(), (string selectedClassNameOrAttrName) => { node.BeforeSave(); node.scriptAttrNameOrClassName = selectedClassNameOrAttrName; node.Save(); } ); } } }
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { LogUtility.Logger.Log("[OnPostprocessAllAssets]"); AssetBundleGraphEditorWindow.OnAssetsReimported(importedAssets, deletedAssets, movedAssets, movedFromAssetPaths); foreach (string str in deletedAssets) { LogUtility.Logger.Log("Deleted Asset: " + str); AssetReferenceDatabase.DeleteReference(str); } for (int i = 0; i < movedAssets.Length; i++) { LogUtility.Logger.Log("Moved Asset: " + movedAssets[i] + " from: " + movedFromAssetPaths[i]); AssetReferenceDatabase.MoveReference(movedFromAssetPaths[i], movedAssets[i]); } }
Perform( SaveData saveData, BuildTarget target, bool isRun, Action <NodeData, float> updateHandler = null ) { bool validateFailed = false; try { ValidateNameCollision(saveData); ValidateLoopConnection(saveData); } catch (NodeException e) { AssetBundleGraphEditorWindow.AddNodeException(e); validateFailed = true; } var resultDict = new Dictionary <ConnectionData, Dictionary <string, List <Asset> > >(); var performedIds = new List <string>(); var cacheDict = new Dictionary <NodeData, List <string> >(); // if validation failed, node may contain looped connections, so we are not going to // go into each operations. if (!validateFailed) { var leaf = saveData.CollectAllLeafNodes(); foreach (var leafNode in leaf) { if (leafNode.InputPoints.Count == 0) { DoNodeOperation(target, leafNode, null, null, saveData, resultDict, cacheDict, performedIds, isRun, updateHandler); } else { foreach (var inputPoint in leafNode.InputPoints) { DoNodeOperation(target, leafNode, inputPoint, null, saveData, resultDict, cacheDict, performedIds, isRun, updateHandler); } } } } return(resultDict); }
public void Setup(string nodeName, string nodeId, string connectionIdToNextNode, Dictionary <string, List <Asset> > groupedSources, List <string> alreadyCached, Action <string, string, Dictionary <string, List <Asset> >, List <string> > Output) { try { ValidateExportPath( exportFilePath, exportFilePath, () => { throw new NodeException(nodeName + ":Export Path is empty.", nodeId); }, () => { throw new NodeException(nodeName + ":Directory set to Export Path does not exist. Path:" + exportFilePath, nodeId); } ); } catch (NodeException e) { AssetBundleGraphEditorWindow.AddNodeException(e); return; } Export(nodeName, nodeId, connectionIdToNextNode, groupedSources, Output, false); }
private void UpdateNodeName(NodeGUI node) { var newName = EditorGUILayout.TextField("Node Name", node.Name); if (NodeGUIUtility.allNodeNames != null) { var overlapping = NodeGUIUtility.allNodeNames.GroupBy(x => x) .Where(group => group.Count() > 1) .Select(group => group.Key); if (overlapping.Any() && overlapping.Contains(newName)) { EditorGUILayout.HelpBox("This node name already exist. Please put other name:" + newName, MessageType.Error); AssetBundleGraphEditorWindow.AddNodeException(new NodeException("Node name " + newName + " already exist.", node.Id)); } } if (newName != node.Name) { using (new RecordUndoScope("Change Node Name", node, true)){ node.Name = newName; } } }
private Type FindFirstIncomingAssetType(ConnectionPointData inputPoint) { var assetGroupEnum = AssetBundleGraphEditorWindow.EnumurateIncomingAssetGroups(inputPoint); if (assetGroupEnum == null) { return(null); } if (assetGroupEnum.Any()) { var ag = assetGroupEnum.First(); if (ag.Values.Any()) { var assets = ag.Values.First(); if (assets.Count > 0) { return(assets[0].filterType); } } } return(null); }
private void DoInspectorModifierGUI(NodeGUI node) { EditorGUILayout.HelpBox("Modifier: Force apply asset settings to given assets.", MessageType.Info); UpdateNodeName(node); GUILayout.Space(10f); var currentModifierTargetType = IntegratedGUIModifier.ModifierOperationTargetTypeName(node.nodeId); using (new EditorGUILayout.VerticalScope(GUI.skin.box)) { // show incoming type of Assets and reset interface. { var isOperationDataExist = false; IntegratedGUIModifier.ValidateModifiyOperationData( node.nodeId, node.currentPlatform, () => { GUILayout.Label("No modifier data found, please Reload first."); }, () => { isOperationDataExist = true; } ); if (!isOperationDataExist) { return; } using (new EditorGUILayout.HorizontalScope()) { GUILayout.Label("Target Type:"); GUILayout.Label(currentModifierTargetType); } /* * reset whole platform's data for this modifier. */ if (GUILayout.Button("Reset Modifier")) { var modifierFolderPath = FileUtility.PathCombine(AssetBundleGraphSettings.MODIFIER_OPERATOR_DATAS_PLACE, node.nodeId); FileUtility.RemakeDirectory(modifierFolderPath); node.Save(); modifierOperatorInstance = null; return; } } GUILayout.Space(10f); var usingScriptMode = !string.IsNullOrEmpty(node.scriptClassName); // use modifier script manually. { GUIStyle s = new GUIStyle("TextFieldDropDownText"); /* * check prefabricator script-type string. */ if (string.IsNullOrEmpty(node.scriptClassName)) { s.fontStyle = FontStyle.Bold; s.fontSize = 12; } else { var loadedType = System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(node.scriptClassName); if (loadedType == null) { s.fontStyle = FontStyle.Bold; s.fontSize = 12; } } var before = !string.IsNullOrEmpty(node.scriptClassName); usingScriptMode = EditorGUILayout.ToggleLeft("Use ModifierOperator Script", !string.IsNullOrEmpty(node.scriptClassName)); // detect mode changed. if (before != usingScriptMode) { // checked. initialize value of scriptClassName. if (usingScriptMode) { node.BeforeSave(); node.scriptClassName = "MyModifier"; node.Save(); } // unchecked. if (!usingScriptMode) { node.BeforeSave(); node.scriptClassName = string.Empty; node.Save(); } } if (!usingScriptMode) { EditorGUI.BeginDisabledGroup(true); } GUILayout.Label("ここをドロップダウンにする。2"); var newScriptClass = EditorGUILayout.TextField("Classname", node.scriptClassName, s); if (newScriptClass != node.scriptClassName) { node.BeforeSave(); node.scriptClassName = newScriptClass; node.Save(); } if (!usingScriptMode) { EditorGUI.EndDisabledGroup(); } } GUILayout.Space(10f); if (usingScriptMode) { EditorGUI.BeginDisabledGroup(true); } // show for each platform tab. var currentPlatform = node.currentPlatform; node.currentPlatform = UpdateCurrentPlatform(node.currentPlatform); /* * if platform tab is changed, renew modifierOperatorInstance for that tab. */ if (currentPlatform != node.currentPlatform) { modifierOperatorInstance = null; } /* * reload modifierOperator instance from saved modifierOperator data. */ if (modifierOperatorInstance == null) { var modifierOperatorDataPath = IntegratedGUIModifier.ModifierDataPathForeachPlatform(node.nodeId, node.currentPlatform); // choose default modifierOperatorData if platform specified file is not exist. if (!File.Exists(modifierOperatorDataPath)) { modifierOperatorDataPath = IntegratedGUIModifier.ModifierDataPathForDefaultPlatform(node.nodeId); } var loadedModifierOperatorDataStr = string.Empty; using (var sr = new StreamReader(modifierOperatorDataPath)) { loadedModifierOperatorDataStr = sr.ReadToEnd(); } var modifierOperatorType = TypeUtility.SupportedModifierOperatorDefinition[currentModifierTargetType]; /* * create instance from saved modifierOperator data. */ modifierOperatorInstance = typeof(NodeGUIEditor) .GetMethod("FromJson") .MakeGenericMethod(modifierOperatorType) // set desired generic type here. .Invoke(this, new object[] { loadedModifierOperatorDataStr }) as ModifierOperators.OperatorBase; } /* * Show ModifierOperator Inspector. */ if (modifierOperatorInstance != null) { Action changed = () => { var data = JsonUtility.ToJson(modifierOperatorInstance); var prettified = AssetBundleGraphEditorWindow.PrettifyJson(data); var modifierOperatorDataPath = IntegratedGUIModifier.ModifierDataPathForeachPlatform(node.nodeId, node.currentPlatform); using (var sw = new StreamWriter(modifierOperatorDataPath)) { sw.Write(prettified); } // reflect change of data. AssetDatabase.Refresh(); modifierOperatorInstance = null; }; GUILayout.Space(10f); modifierOperatorInstance.DrawInspector(changed); } var deleted = UpdateDeleteSetting(node); if (deleted) { // source platform depended data is deleted. reload instance for reloading instance from data. modifierOperatorInstance = null; } if (usingScriptMode) { EditorGUI.EndDisabledGroup(); } } }
public static INodeOperation CreateOperation(SaveData saveData, NodeData currentNodeData) { INodeOperation executor = null; try { switch (currentNodeData.Kind) { case NodeKind.LOADER_GUI: { executor = new IntegratedGUILoader(); break; } case NodeKind.FILTER_GUI: { // Filter requires multiple output connections var connectionsToChild = saveData.Connections.FindAll(c => c.FromNodeId == currentNodeData.Id); executor = new IntegratedGUIFilter(connectionsToChild); break; } case NodeKind.IMPORTSETTING_GUI: { executor = new IntegratedGUIImportSetting(); break; } case NodeKind.MODIFIER_GUI: { executor = new IntegratedGUIModifier(); break; } case NodeKind.GROUPING_GUI: { executor = new IntegratedGUIGrouping(); break; } case NodeKind.PREFABBUILDER_GUI: { executor = new IntegratedPrefabBuilder(); break; } case NodeKind.BUNDLECONFIG_GUI: { executor = new IntegratedGUIBundleConfigurator(); break; } case NodeKind.BUNDLEBUILDER_GUI: { executor = new IntegratedGUIBundleBuilder(); break; } case NodeKind.EXPORTER_GUI: { executor = new IntegratedGUIExporter(); break; } default: { Debug.LogError(currentNodeData.Name + " is defined as unknown kind of node. value:" + currentNodeData.Kind); break; } } } catch (NodeException e) { AssetBundleGraphEditorWindow.AddNodeException(e); //Debug.LogError("error occured:\"" + e.reason + "\", please check information on node."); //throw new AssetBundleGraphException(node.Name + ": " + e.reason); } return(executor); }
/** * Collect build result: connectionId : < groupName : List<Asset> > */ // private static Dictionary<ConnectionData, Dictionary<string, List<Asset>>> // CollectResult (Dictionary<ConnectionData, Dictionary<string, List<Asset>>> buildResult) { // // var finalResult = new Dictionary<ConnectionData, Dictionary<string, List<Asset>>>(); // // foreach (var connection in buildResult.Keys) { // var groupDict = buildResult[connection]; // var finalGroupDict = new Dictionary<string, List<Asset>>(); // // foreach (var groupKey in groupDict.Keys) { // var assets = groupDict[groupKey]; // var finalAssets = new List<Asset>(); // // foreach (var assetData in assets) { // var bundled = assetData.isBundled; // // if (!string.IsNullOrEmpty(assetData.importFrom)) { // finalAssets.Add(new DepreacatedThroughputAsset(assetData.importFrom, bundled)); // continue; // } // // if (!string.IsNullOrEmpty(assetData.absoluteAssetPath)) { // var relativeAbsolutePath = assetData.absoluteAssetPath.Replace(FileUtility.ProjectPathWithSlash(), string.Empty); // finalAssets.Add(new DepreacatedThroughputAsset(relativeAbsolutePath, bundled)); // continue; // } // // if (!string.IsNullOrEmpty(assetData.exportTo)) { // finalAssets.Add(new DepreacatedThroughputAsset(assetData.exportTo, bundled)); // continue; // } // } // finalGroupDict[groupKey] = finalAssets; // } // finalResult[connection] = finalGroupDict; // } // return finalResult; // } /** * Perform Run or Setup from parent of given terminal node recursively. */ private static void DoNodeOperation( BuildTarget target, NodeData currentNodeData, ConnectionPointData currentInputPoint, ConnectionData connectionToOutput, SaveData saveData, Dictionary <ConnectionData, Dictionary <string, List <Asset> > > resultDict, Dictionary <NodeData, List <string> > cachedDict, List <string> performedIds, bool isActualRun, Action <NodeData, float> updateHandler = null ) { if (performedIds.Contains(currentNodeData.Id) || (currentInputPoint != null && performedIds.Contains(currentInputPoint.Id))) { return; } /* * Find connections coming into this node from parent node, and traverse recursively */ var connectionsToParents = saveData.Connections.FindAll(con => con.ToNodeId == currentNodeData.Id); foreach (var c in connectionsToParents) { var parentNode = saveData.Nodes.Find(node => node.Id == c.FromNodeId); UnityEngine.Assertions.Assert.IsNotNull(parentNode); // check if nodes can connect together ConnectionData.ValidateConnection(parentNode, currentNodeData); if (parentNode.InputPoints.Count > 0) { // if node has multiple input, node is operated per input foreach (var parentInputPoint in parentNode.InputPoints) { DoNodeOperation(target, parentNode, parentInputPoint, c, saveData, resultDict, cachedDict, performedIds, isActualRun, updateHandler); } } // if parent does not have input point, call with inputPoint==null else { DoNodeOperation(target, parentNode, null, c, saveData, resultDict, cachedDict, performedIds, isActualRun, updateHandler); } } // mark this point as performed if (currentInputPoint != null) { performedIds.Add(currentInputPoint.Id); } // Root node does not have input point, so we are storing node id instead. else { performedIds.Add(currentNodeData.Id); } /* * Perform node operation for this node */ if (updateHandler != null) { updateHandler(currentNodeData, 0f); } /* * has next node, run first time. */ var alreadyCachedPaths = new List <string>(); if (cachedDict.ContainsKey(currentNodeData)) { alreadyCachedPaths.AddRange(cachedDict[currentNodeData]); } // load already exist cache from node. alreadyCachedPaths.AddRange(GetCachedDataByNode(target, currentNodeData)); // Grab incoming assets from result by refering connections to parents var inputGroupAssets = new Dictionary <string, List <Asset> >(); if (currentInputPoint != null) { // aggregates all input assets coming from current inputPoint var connToParentsFromCurrentInput = saveData.Connections.FindAll(con => con.ToNodeConnectionPointId == currentInputPoint.Id); foreach (var rCon in connToParentsFromCurrentInput) { if (!resultDict.ContainsKey(rCon)) { continue; } var result = resultDict[rCon]; foreach (var groupKey in result.Keys) { if (!inputGroupAssets.ContainsKey(groupKey)) { inputGroupAssets[groupKey] = new List <Asset>(); } inputGroupAssets[groupKey].AddRange(result[groupKey]); } } } /* * the Action passes to NodeOperaitons. * It stores result to resultDict. */ Action <ConnectionData, Dictionary <string, List <Asset> >, List <string> > Output = (ConnectionData destinationConnection, Dictionary <string, List <Asset> > outputGroupAsset, List <string> cachedItems) => { if (destinationConnection != null) { if (!resultDict.ContainsKey(destinationConnection)) { resultDict[destinationConnection] = new Dictionary <string, List <Asset> >(); } /* * merge connection result by group key. */ foreach (var groupKey in outputGroupAsset.Keys) { if (!resultDict[destinationConnection].ContainsKey(groupKey)) { resultDict[destinationConnection][groupKey] = new List <Asset>(); } resultDict[destinationConnection][groupKey].AddRange(outputGroupAsset[groupKey]); } } if (isActualRun) { if (!cachedDict.ContainsKey(currentNodeData)) { cachedDict[currentNodeData] = new List <string>(); } if (cachedItems != null) { cachedDict[currentNodeData].AddRange(cachedItems); } } }; try { INodeOperation executor = CreateOperation(saveData, currentNodeData); if (executor != null) { if (isActualRun) { executor.Run(target, currentNodeData, currentInputPoint, connectionToOutput, inputGroupAssets, alreadyCachedPaths, Output); } else { executor.Setup(target, currentNodeData, currentInputPoint, connectionToOutput, inputGroupAssets, alreadyCachedPaths, Output); } } } catch (NodeException e) { AssetBundleGraphEditorWindow.AddNodeException(e); // since error occured, this node should stop running for other inputpoints. Adding node id to stop. if (!performedIds.Contains(currentNodeData.Id)) { performedIds.Add(currentNodeData.Id); } } if (updateHandler != null) { updateHandler(currentNodeData, 1f); } }
/** * Perform Run or Setup from parent of given terminal node recursively. */ private static void DoNodeOperation( string nodeId, List <NodeData> allNodes, List <ConnectionData> allConnections, Dictionary <string, Dictionary <string, List <Asset> > > resultDict, Dictionary <string, List <string> > cachedDict, List <string> usedConnectionIds, bool isActualRun, Action <string, float> updateHandler = null ) { var relatedNodes = allNodes.Where(relation => relation.nodeId == nodeId).ToList(); if (!relatedNodes.Any()) { return; } var currentNodeData = relatedNodes[0]; if (currentNodeData.IsAlreadyDone()) { return; } var nodeName = currentNodeData.nodeName; var nodeKind = currentNodeData.nodeKind; /* * Perform prarent node recursively from this node */ foreach (var connectionToParent in currentNodeData.connectionToParents) { var parentNodeId = connectionToParent.fromNodeId; var usedConnectionId = connectionToParent.connectionId; if (usedConnectionIds.Contains(usedConnectionId)) { throw new NodeException("connection loop detected.", parentNodeId); } usedConnectionIds.Add(usedConnectionId); var parentNode = allNodes.Where(node => node.nodeId == parentNodeId).ToList(); if (!parentNode.Any()) { return; } var parentNodeKind = parentNode[0].nodeKind; // check node kind order. SystemDataValidator.ValidateAssertNodeOrder(parentNodeKind, nodeKind); DoNodeOperation(parentNodeId, allNodes, allConnections, resultDict, cachedDict, usedConnectionIds, isActualRun, updateHandler); } /* * Perform node operation for this node */ // connections Ids from this node to child nodes. non-ordered. // actual running order depends on order of Node's OutputPoint order. var nonOrderedConnectionsFromThisNodeToChildNode = allConnections .Where(con => con.fromNodeId == nodeId) .ToList(); var orderedNodeOutputPointIds = allNodes.Where(node => node.nodeId == nodeId).SelectMany(node => node.outputPointIds).ToList(); /* * get connection ids which is orderd by node's outputPoint-order. */ var orderedConnectionIds = new List <string>(nonOrderedConnectionsFromThisNodeToChildNode.Count); foreach (var orderedNodeOutputPointId in orderedNodeOutputPointIds) { foreach (var nonOrderedConnectionFromThisNodeToChildNode in nonOrderedConnectionsFromThisNodeToChildNode) { var nonOrderedConnectionOutputPointId = nonOrderedConnectionFromThisNodeToChildNode.fromNodeOutputPointId; if (orderedNodeOutputPointId == nonOrderedConnectionOutputPointId) { orderedConnectionIds.Add(nonOrderedConnectionFromThisNodeToChildNode.connectionId); continue; } } } /* * FilterNode and BundlizerNode uses specific multiple output connections. * ExportNode does not have output. * but all other nodes has only one output connection and uses first connection. */ var firstConnectionIdFromThisNodeToChildNode = string.Empty; if (orderedConnectionIds.Any()) { firstConnectionIdFromThisNodeToChildNode = orderedConnectionIds[0]; } if (updateHandler != null) { updateHandler(nodeId, 0f); } /* * has next node, run first time. */ var alreadyCachedPaths = new List <string>(); if (cachedDict.ContainsKey(nodeId)) { alreadyCachedPaths.AddRange(cachedDict[nodeId]); } /* * load already exist cache from node. */ alreadyCachedPaths.AddRange(GetCachedDataByNodeKind(nodeKind, nodeId)); var inputParentResults = new Dictionary <string, List <Asset> >(); var receivingConnectionIds = allConnections .Where(con => con.toNodeId == nodeId) .Select(con => con.connectionId) .ToList(); foreach (var connecionId in receivingConnectionIds) { if (!resultDict.ContainsKey(connecionId)) { continue; } var result = resultDict[connecionId]; foreach (var groupKey in result.Keys) { if (!inputParentResults.ContainsKey(groupKey)) { inputParentResults[groupKey] = new List <Asset>(); } inputParentResults[groupKey].AddRange(result[groupKey]); } } /* * the Action passes to NodeOperaitons. * It stores result to resultDict. */ Action <string, string, Dictionary <string, List <Asset> >, List <string> > Output = (string dataSourceNodeId, string targetConnectionId, Dictionary <string, List <Asset> > result, List <string> justCached) => { var targetConnectionIds = allConnections .Where(con => con.connectionId == targetConnectionId) .Select(con => con.connectionId) .ToList(); if (!targetConnectionIds.Any()) { // if next connection does not exist, no results for next. // save results to resultDict with this endpoint node's id. resultDict[dataSourceNodeId] = new Dictionary <string, List <Asset> >(); foreach (var groupKey in result.Keys) { if (!resultDict[dataSourceNodeId].ContainsKey(groupKey)) { resultDict[dataSourceNodeId][groupKey] = new List <Asset>(); } resultDict[dataSourceNodeId][groupKey].AddRange(result[groupKey]); } return; } if (!resultDict.ContainsKey(targetConnectionId)) { resultDict[targetConnectionId] = new Dictionary <string, List <Asset> >(); } /* * merge connection result by group key. */ foreach (var groupKey in result.Keys) { if (!resultDict[targetConnectionId].ContainsKey(groupKey)) { resultDict[targetConnectionId][groupKey] = new List <Asset>(); } resultDict[targetConnectionId][groupKey].AddRange(result[groupKey]); } if (isActualRun) { if (!cachedDict.ContainsKey(nodeId)) { cachedDict[nodeId] = new List <string>(); } cachedDict[nodeId].AddRange(justCached); } }; try { if (isActualRun) { switch (nodeKind) { /* * Scripts */ case AssetBundleGraphSettings.NodeKind.FILTER_SCRIPT: { var scriptClassName = currentNodeData.scriptClassName; var executor = SystemDataUtility.CreateNodeOperationInstance <FilterBase>(scriptClassName, nodeId); executor.Run(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); break; } case AssetBundleGraphSettings.NodeKind.PREFABRICATOR_SCRIPT: { var scriptClassName = currentNodeData.scriptClassName; var executor = SystemDataUtility.CreateNodeOperationInstance <PrefabricatorBase>(scriptClassName, nodeId); executor.Run(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); break; } /* * GUIs */ case AssetBundleGraphSettings.NodeKind.LOADER_GUI: { var currentLoadFilePath = SystemDataUtility.GetCurrentPlatformValue(currentNodeData.loadFilePath); var executor = new IntegratedGUILoader(FileUtility.GetPathWithAssetsPath(currentLoadFilePath)); executor.Run(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); break; } case AssetBundleGraphSettings.NodeKind.FILTER_GUI: { /* * Filter requires "outputPoint ordered exist connection Id and Fake connection Id" for * exhausting assets by keyword and type correctly. * * outputPoint which has connection can through assets by keyword and keytype, * also outputPoint which doesn't have connection should take assets by keyword and keytype. */ var orderedConnectionIdsAndFakeConnectionIds = new string[orderedNodeOutputPointIds.Count]; for (var i = 0; i < orderedNodeOutputPointIds.Count; i++) { var orderedNodeOutputPointId = orderedNodeOutputPointIds[i]; foreach (var nonOrderedConnectionFromThisNodeToChildNode in nonOrderedConnectionsFromThisNodeToChildNode) { var connectionOutputPointId = nonOrderedConnectionFromThisNodeToChildNode.fromNodeOutputPointId; if (orderedNodeOutputPointId == connectionOutputPointId) { orderedConnectionIdsAndFakeConnectionIds[i] = nonOrderedConnectionFromThisNodeToChildNode.connectionId; break; } else { orderedConnectionIdsAndFakeConnectionIds[i] = AssetBundleGraphSettings.FILTER_FAKE_CONNECTION_ID; } } } var executor = new IntegratedGUIFilter(orderedConnectionIdsAndFakeConnectionIds, currentNodeData.containsKeywords, currentNodeData.containsKeytypes); executor.Run(nodeName, nodeId, string.Empty, inputParentResults, alreadyCachedPaths, Output); break; } case AssetBundleGraphSettings.NodeKind.IMPORTSETTING_GUI: { var executor = new IntegratedGUIImportSetting(); executor.Run(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); break; } case AssetBundleGraphSettings.NodeKind.MODIFIER_GUI: { var specificScriptClass = currentNodeData.scriptClassName; var executor = new IntegratedGUIModifier(specificScriptClass, SystemDataUtility.GetCurrentPlatformShortName()); executor.Run(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); break; } case AssetBundleGraphSettings.NodeKind.GROUPING_GUI: { var executor = new IntegratedGUIGrouping(SystemDataUtility.GetCurrentPlatformValue(currentNodeData.groupingKeyword)); executor.Run(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); break; } case AssetBundleGraphSettings.NodeKind.PREFABRICATOR_GUI: { var scriptClassName = currentNodeData.scriptClassName; if (string.IsNullOrEmpty(scriptClassName)) { Debug.LogError(nodeName + ": Classname is empty. Set valid classname. Configure valid script name from editor."); break; } var executor = SystemDataUtility.CreateNodeOperationInstance <PrefabricatorBase>(scriptClassName, nodeId); executor.Run(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); break; } case AssetBundleGraphSettings.NodeKind.BUNDLIZER_GUI: { /* * Bundlizer requires assetOutputConnectionId and additional resourceOutputConnectionId. * both-connected, or both-not-connected, or one of them is connected. 4 patterns exists. * * Bundler Node's outputPoint [0] is always the point for assetOutputConnectionId. * Bundler Node's outputPoint [1] is always the point for resourceOutputConnectionId. * * if one of these outputPoint don't have connection, use Fake connection id for correct output. * * * unorderedConnectionId \ * ----> orderedConnectionIdsAndFakeConnectionIds. * orderedOutputPointId / */ var orderedConnectionIdsAndFakeConnectionIds = new string[orderedNodeOutputPointIds.Count]; for (var i = 0; i < orderedNodeOutputPointIds.Count; i++) { var orderedNodeOutputPointId = orderedNodeOutputPointIds[i]; foreach (var nonOrderedConnectionFromThisNodeToChildNode in nonOrderedConnectionsFromThisNodeToChildNode) { var connectionOutputPointId = nonOrderedConnectionFromThisNodeToChildNode.fromNodeOutputPointId; if (orderedNodeOutputPointId == connectionOutputPointId) { orderedConnectionIdsAndFakeConnectionIds[i] = nonOrderedConnectionFromThisNodeToChildNode.connectionId; break; } else { orderedConnectionIdsAndFakeConnectionIds[i] = AssetBundleGraphSettings.BUNDLIZER_FAKE_CONNECTION_ID; } } } var bundleNameTemplate = SystemDataUtility.GetCurrentPlatformValue(currentNodeData.bundleNameTemplate); /* * TODO: variants needs to be execute Setup/Run as many times as its variants */ var executor = new IntegratedGUIBundlizer(bundleNameTemplate, orderedConnectionIdsAndFakeConnectionIds[0], currentNodeData.variants); executor.Run(nodeName, nodeId, string.Empty, inputParentResults, alreadyCachedPaths, Output); break; } case AssetBundleGraphSettings.NodeKind.BUNDLEBUILDER_GUI: { var bundleOptions = SystemDataUtility.GetCurrentPlatformValue(currentNodeData.enabledBundleOptions); var executor = new IntegratedGUIBundleBuilder(bundleOptions, allNodes.Select(nodeData => nodeData.nodeId).ToList()); executor.Run(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); break; } case AssetBundleGraphSettings.NodeKind.EXPORTER_GUI: { var exportTo = SystemDataUtility.GetCurrentPlatformValue(currentNodeData.exportFilePath); var executor = new IntegratedGUIExporter(FileUtility.GetPathWithProjectPath(exportTo)); executor.Run(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); break; } default: { Debug.LogError(nodeName + " is defined as unknown kind of node. value:" + nodeKind); break; } } } else { switch (nodeKind) { /* * Scripts */ case AssetBundleGraphSettings.NodeKind.FILTER_SCRIPT: { var scriptClassName = currentNodeData.scriptClassName; var executor = SystemDataUtility.CreateNodeOperationInstance <FilterBase>(scriptClassName, nodeId); executor.Setup(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); break; } case AssetBundleGraphSettings.NodeKind.PREFABRICATOR_SCRIPT: { var scriptClassName = currentNodeData.scriptClassName; var executor = SystemDataUtility.CreateNodeOperationInstance <PrefabricatorBase>(scriptClassName, nodeId); executor.Setup(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); break; } /* * GUIs */ case AssetBundleGraphSettings.NodeKind.LOADER_GUI: { var currentLoadFilePath = SystemDataUtility.GetCurrentPlatformValue(currentNodeData.loadFilePath); var executor = new IntegratedGUILoader(FileUtility.GetPathWithAssetsPath(currentLoadFilePath)); executor.Setup(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); break; } case AssetBundleGraphSettings.NodeKind.FILTER_GUI: { /* * Filter requires "outputPoint ordered exist connection Id and Fake connection Id" for * exhausting assets by keyword and type correctly. * * outputPoint which has connection can through assets by keyword and keytype, * also outputPoint which doesn't have connection should take assets by keyword and keytype. */ var orderedConnectionIdsAndFakeConnectionIds = new string[orderedNodeOutputPointIds.Count]; for (var i = 0; i < orderedNodeOutputPointIds.Count; i++) { var orderedNodeOutputPointId = orderedNodeOutputPointIds[i]; foreach (var nonOrderedConnectionFromThisNodeToChildNode in nonOrderedConnectionsFromThisNodeToChildNode) { var connectionOutputPointId = nonOrderedConnectionFromThisNodeToChildNode.fromNodeOutputPointId; if (orderedNodeOutputPointId == connectionOutputPointId) { orderedConnectionIdsAndFakeConnectionIds[i] = nonOrderedConnectionFromThisNodeToChildNode.connectionId; break; } else { orderedConnectionIdsAndFakeConnectionIds[i] = AssetBundleGraphSettings.FILTER_FAKE_CONNECTION_ID; } } } var executor = new IntegratedGUIFilter(orderedConnectionIdsAndFakeConnectionIds, currentNodeData.containsKeywords, currentNodeData.containsKeytypes); executor.Setup(nodeName, nodeId, string.Empty, inputParentResults, alreadyCachedPaths, Output); break; } case AssetBundleGraphSettings.NodeKind.IMPORTSETTING_GUI: { var executor = new IntegratedGUIImportSetting(); executor.Setup(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); break; } case AssetBundleGraphSettings.NodeKind.MODIFIER_GUI: { var specificScriptClass = currentNodeData.scriptClassName; var executor = new IntegratedGUIModifier(specificScriptClass, SystemDataUtility.GetCurrentPlatformShortName()); executor.Setup(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); break; } case AssetBundleGraphSettings.NodeKind.GROUPING_GUI: { var executor = new IntegratedGUIGrouping(SystemDataUtility.GetCurrentPlatformValue(currentNodeData.groupingKeyword)); executor.Setup(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); break; } case AssetBundleGraphSettings.NodeKind.PREFABRICATOR_GUI: { var scriptClassName = currentNodeData.scriptClassName; if (string.IsNullOrEmpty(scriptClassName)) { AssetBundleGraphEditorWindow.AddNodeException(new NodeException(nodeName + ": Classname is empty. Set valid classname.", nodeId)); break; } try { var executor = SystemDataUtility.CreateNodeOperationInstance <PrefabricatorBase>(scriptClassName, nodeId); executor.Setup(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); } catch (NodeException e) { AssetBundleGraphEditorWindow.AddNodeException(e); break; } break; } case AssetBundleGraphSettings.NodeKind.BUNDLIZER_GUI: { /* * Bundlizer requires assetOutputConnectionId and additional resourceOutputConnectionId. * both-connected, or both-not-connected, or one of them is connected. 4 patterns exists. * * Bundler Node's outputPoint [0] is always the point for assetOutputConnectionId. * Bundler Node's outputPoint [1] is always the point for resourceOutputConnectionId. * * if one of these outputPoint don't have connection, use Fake connection id for correct output. * * * unorderedConnectionId \ * ----> orderedConnectionIdsAndFakeConnectionIds. * orderedOutputPointId / */ var orderedConnectionIdsAndFakeConnectionIds = new string[orderedNodeOutputPointIds.Count]; for (var i = 0; i < orderedNodeOutputPointIds.Count; i++) { var orderedNodeOutputPointId = orderedNodeOutputPointIds[i]; foreach (var nonOrderedConnectionFromThisNodeToChildNode in nonOrderedConnectionsFromThisNodeToChildNode) { var connectionOutputPointId = nonOrderedConnectionFromThisNodeToChildNode.fromNodeOutputPointId; if (orderedNodeOutputPointId == connectionOutputPointId) { orderedConnectionIdsAndFakeConnectionIds[i] = nonOrderedConnectionFromThisNodeToChildNode.connectionId; break; } else { orderedConnectionIdsAndFakeConnectionIds[i] = AssetBundleGraphSettings.BUNDLIZER_FAKE_CONNECTION_ID; } } } var bundleNameTemplate = SystemDataUtility.GetCurrentPlatformValue(currentNodeData.bundleNameTemplate); /* * TODO: variants needs to be execute Setup/Run as many times as its variants */ var executor = new IntegratedGUIBundlizer(bundleNameTemplate, orderedConnectionIdsAndFakeConnectionIds[0], currentNodeData.variants); executor.Setup(nodeName, nodeId, string.Empty, inputParentResults, alreadyCachedPaths, Output); break; } case AssetBundleGraphSettings.NodeKind.BUNDLEBUILDER_GUI: { var bundleOptions = SystemDataUtility.GetCurrentPlatformValue(currentNodeData.enabledBundleOptions); var executor = new IntegratedGUIBundleBuilder(bundleOptions, allNodes.Select(nodeData => nodeData.nodeId).ToList()); executor.Setup(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); break; } case AssetBundleGraphSettings.NodeKind.EXPORTER_GUI: { var exportTo = SystemDataUtility.GetCurrentPlatformValue(currentNodeData.exportFilePath); var executor = new IntegratedGUIExporter(FileUtility.GetPathWithProjectPath(exportTo)); executor.Setup(nodeName, nodeId, firstConnectionIdFromThisNodeToChildNode, inputParentResults, alreadyCachedPaths, Output); break; } default: { Debug.LogError(nodeName + " is defined as unknown kind of node. value:" + nodeKind); break; } } } } catch (NodeException e) { AssetBundleGraphEditorWindow.AddNodeException(e); //Debug.LogError("error occured:\"" + e.reason + "\", please check information on node."); return; //throw new AssetBundleGraphException(nodeName + ": " + e.reason); } currentNodeData.Done(); if (updateHandler != null) { updateHandler(nodeId, 1f); } }
public void Setup(string nodeName, string nodeId, string connectionIdToNextNode, Dictionary <string, List <Asset> > unused, List <string> alreadyCached, Action <string, string, Dictionary <string, List <Asset> >, List <string> > Output) { try { ValidateLoadPath( loadFilePath, loadFilePath, () => { //throw new NodeException(nodeName + ": Load Path is empty.", nodeId); }, () => { throw new NodeException(nodeName + ": Directory not found: " + loadFilePath, nodeId); } ); } catch (NodeException e) { AssetBundleGraphEditorWindow.AddNodeException(e); return; } // SOMEWHERE_FULLPATH/PROJECT_FOLDER/Assets/ var assetsFolderPath = Application.dataPath + AssetBundleGraphSettings.UNITY_FOLDER_SEPARATOR; var outputSource = new List <Asset>(); var targetFilePaths = FileUtility.FilePathsInFolder(loadFilePath); try { foreach (var targetFilePath in targetFilePaths) { if (targetFilePath.Contains(AssetBundleGraphSettings.ASSETBUNDLEGRAPH_PATH)) { continue; } // already contained into Assets/ folder. // imported path is Assets/SOMEWHERE_FILE_EXISTS. if (targetFilePath.StartsWith(assetsFolderPath)) { var relativePath = targetFilePath.Replace(assetsFolderPath, AssetBundleGraphSettings.ASSETS_PATH); var assetType = TypeUtility.GetTypeOfAsset(relativePath); if (assetType == typeof(object)) { continue; } outputSource.Add(Asset.CreateNewAssetFromLoader(targetFilePath, relativePath)); continue; } throw new NodeException(nodeName + ": Invalid Load Path. Path must start with Assets/", nodeId); } } catch (NodeException e) { AssetBundleGraphEditorWindow.AddNodeException(e); return; } catch (Exception e) { Debug.LogError(nodeName + " Error:" + e); } var outputDir = new Dictionary <string, List <Asset> > { { "0", outputSource } }; Output(nodeId, connectionIdToNextNode, outputDir, new List <string>()); }
public void Setup(string nodeName, string nodeId, string connectionIdToNextNode, Dictionary <string, List <Asset> > groupedSources, List <string> alreadyCached, Action <string, string, Dictionary <string, List <Asset> >, List <string> > Output) { if (groupedSources.Keys.Count == 0) { return; } // Modifier merges multiple incoming groups into one. if (1 < groupedSources.Keys.Count) { Debug.LogWarning(nodeName + " Modifier merges incoming group into \"" + groupedSources.Keys.ToList()[0]); } var groupMergeKey = groupedSources.Keys.ToList()[0]; // merge all assets into single list. var inputSources = new List <Asset>(); foreach (var groupKey in groupedSources.Keys) { inputSources.AddRange(groupedSources[groupKey]); } if (!inputSources.Any()) { return; } // initialize as object. var modifierType = string.Empty; var first = true; foreach (var inputSource in inputSources) { var modifyTargetAssetPath = inputSource.importFrom; var assumedType = TypeUtility.FindTypeOfAsset(modifyTargetAssetPath); if (assumedType == null || assumedType == typeof(object)) { continue; } if (first) { first = false; modifierType = assumedType.ToString(); continue; } if (modifierType != assumedType.ToString()) { throw new NodeException("multiple Asset Type detected. consider reduce Asset Type number to only 1 by Filter. detected Asset Types is:" + modifierType + " , and " + assumedType.ToString(), nodeId); } } // modifierType is fixed. if (!string.IsNullOrEmpty(specifiedScriptClass)) { Debug.LogError("modifierのScript版実装中。"); return; } // check support. if (!TypeUtility.SupportedModifierOperatorDefinition.ContainsKey(modifierType)) { throw new NodeException("current incoming Asset Type:" + modifierType + " is unsupported.", nodeId); } // generate modifierOperatorData if data is not exist yet. { var modifierOperationDataFolderPath = AssetBundleGraphSettings.MODIFIER_OPERATOR_DATAS_PLACE; if (!Directory.Exists(modifierOperationDataFolderPath)) { Directory.CreateDirectory(modifierOperationDataFolderPath); } var opDataFolderPath = FileUtility.PathCombine(modifierOperationDataFolderPath, nodeId); if (!Directory.Exists(opDataFolderPath)) { Directory.CreateDirectory(opDataFolderPath); } // ready default platform path. var modifierOperatorDataPathForDefaultPlatform = FileUtility.PathCombine(opDataFolderPath, ModifierOperatiorDataName(AssetBundleGraphSettings.PLATFORM_DEFAULT_NAME)); /* * create default platform ModifierOperatorData if not exist. * default ModifierOperatorData is the target platform for every platform by default. */ if (!File.Exists(modifierOperatorDataPathForDefaultPlatform)) { var operatorType = TypeUtility.SupportedModifierOperatorDefinition[modifierType]; var operatorInstance = Activator.CreateInstance(operatorType) as ModifierOperators.OperatorBase; var defaultRenderTextureOp = operatorInstance.DefaultSetting(); /* * generated json data is typed as supported ModifierOperation type. */ var jsonData = JsonUtility.ToJson(defaultRenderTextureOp); var prettified = AssetBundleGraphEditorWindow.PrettifyJson(jsonData); using (var sw = new StreamWriter(modifierOperatorDataPathForDefaultPlatform)) { sw.WriteLine(prettified); } } } // validate saved data. ValidateModifiyOperationData( nodeId, currentPlatformStr, () => { throw new NodeException("No ModifierOperatorData found. please Setup first.", nodeId); }, () => { /*do nothing.*/ } ); var outputSources = new List <Asset>(); /* * all assets types are same and do nothing to assets in setup. */ foreach (var asset in inputSources) { outputSources.Add(Asset.DuplicateAsset(asset)); } var outputDict = new Dictionary <string, List <Asset> >(); outputDict[groupMergeKey] = outputSources; Output(nodeId, connectionIdToNextNode, outputDict, new List <string>()); }