using System; using System.Collections.Generic; using BlockSpace.Voxels; using BlockSpace.Worlds; using UnityEngine; using UnityEngine.XR; [DisallowMultipleComponent] public sealed class NodeGraphManager : MonoBehaviour { private sealed class CircuitUndoStep { public readonly List undo = new List(); public readonly List redo = new List(); } private interface ICircuitMutation { void Apply(NodeGraphManager manager, bool broadcast); } private sealed class UpsertNodeMutation : ICircuitMutation { private readonly WorldGroupsCodec.WorldNodeRecord record; public UpsertNodeMutation(WorldGroupsCodec.WorldNodeRecord record) { this.record = record; } public void Apply(NodeGraphManager manager, bool broadcast) { manager.ApplyNodeRecord(record, broadcast); } } private sealed class DeleteNodeMutation : ICircuitMutation { private readonly int nodeId; public DeleteNodeMutation(int nodeId) { this.nodeId = nodeId; } public void Apply(NodeGraphManager manager, bool broadcast) { if (manager.nodesById.TryGetValue(nodeId, out var value) && value != null) { manager.DeleteNodeInternal(value, broadcast, recordUndo: false); } } } private sealed class ConnectWireMutation : ICircuitMutation { private readonly WorldGroupsCodec.WorldWireRecord record; public ConnectWireMutation(WorldGroupsCodec.WorldWireRecord record) { this.record = record; } public void Apply(NodeGraphManager manager, bool broadcast) { manager.ApplyWireRecord(record, broadcast); } } private sealed class DisconnectWireMutation : ICircuitMutation { private readonly WorldGroupsCodec.WorldWireRecord record; public DisconnectWireMutation(WorldGroupsCodec.WorldWireRecord record) { this.record = record; } public void Apply(NodeGraphManager manager, bool broadcast) { Node value; Node value2; if (WorldGroupsCodec.TryGetGroupRuntimeWireSourceId(record, out var groupId) || WorldGroupsCodec.TryGetGroupSavedWireIndex(record, out groupId)) { new DisconnectInputMutation(record.inputNodeId, record.inputPortName).Apply(manager, broadcast); } else if (manager.nodesById.TryGetValue(record.outputNodeId, out value) && manager.nodesById.TryGetValue(record.inputNodeId, out value2) && value != null && value2 != null) { manager.Disconnect(value, record.outputPortName, value2, record.inputPortName, broadcast); } } } private sealed class DisconnectInputMutation : ICircuitMutation { private readonly int inputNodeId; private readonly string inputPortName; public DisconnectInputMutation(int nodeId, string portName) { inputNodeId = nodeId; inputPortName = portName; } public void Apply(NodeGraphManager manager, bool broadcast) { if (!manager.nodesById.TryGetValue(inputNodeId, out var value) || !(value != null) || !value.TryGetPort(inputPortName, PortDirection.Input, out var port)) { return; } bool num = port.SourceGroupId > 0; manager.DisconnectInputPortInternal(port, broadcast); if (num) { manager.ClearGroupInputSourceInternal(port, clearValue: true, pulsePort: false); if (broadcast && !manager.suppressNetworkBroadcast) { manager.ResolveNetworkSync()?.BroadcastDisconnectInput(inputNodeId, inputPortName); } } } } private sealed class DisconnectOutputMutation : ICircuitMutation { private readonly int outputNodeId; private readonly string outputPortName; public DisconnectOutputMutation(int nodeId, string portName) { outputNodeId = nodeId; outputPortName = portName; } public void Apply(NodeGraphManager manager, bool broadcast) { if (manager.nodesById.TryGetValue(outputNodeId, out var value) && value != null && value.TryGetPort(outputPortName, PortDirection.Output, out var port)) { manager.DisconnectOutputPortInternal(port, broadcast); } } } private sealed class WireVisual { public string key; public int sourceGroupId; public NodePort output; public NodePort input; public PortKind Kind; public LineRenderer renderer; public float flashUntil; } private sealed class NodeGrabState { public Node node; public Vector3 startPosition; public Quaternion startRotation; public float lastPreviewSentAt = float.NegativeInfinity; } private sealed class PendingWirePress { public NodePort startPort; public VoxelGroup startGroup; public Vector3 startWorldPosition; public float pressedAt; } private sealed class ActiveValueEditContext { public NodePort port; public string buffer; public string originalBuffer; public float startedAt; } private sealed class ActiveWireContext { public NodePort startPort; public VoxelGroup startGroup; public Vector3 startWorldPosition; public PortKind Kind; } private const string NodePrefabResourcePath = "Node"; private static readonly Color NoneWireColor = NodeWirePalette.None; private static readonly Color BoolWireColor = NodeWirePalette.Bool; private static readonly Color NumberWireColor = NodeWirePalette.Number; private static readonly Color TextWireColor = NodeWirePalette.Text; private static readonly Color PlayerWireColor = NodeWirePalette.Player; private static readonly Color GroupWireColor = NodeWirePalette.Group; private static readonly Color RunWireColor = NodeWirePalette.Run; private static readonly Color WireFlashColor = new Color(1f, 1f, 0.7f, 1f); private static readonly Color AimRayColor = new Color(0.55f, 0.9f, 1f, 0.72f); private static readonly Color PreviewRunColor = new Color(1f, 0.55f, 0.28f, 0.92f); [SerializeField] private VoxelGroupManager groupManager; [SerializeField] private VRVoxelBrush brush; [SerializeField] private Transform trackingOrigin; [SerializeField] private Transform leftHandAnchor; [SerializeField] private Transform rightHandAnchor; [SerializeField] private List preloadedDefinitions = new List(); [SerializeField] private float portPickRadius = 0.08f; [SerializeField] private float nodePickRadius = 0.16f; [SerializeField] private float wireRayDistance = 2f; [SerializeField] private float wireTriggerThreshold = 0.7f; [SerializeField] private float rightGripThreshold = 0.85f; [SerializeField] private float quickTapThreshold = 0.16f; [SerializeField] private float movePreviewSendInterval = 0.05f; [SerializeField] private float nodeMovePositionSnap = 0.05f; [SerializeField] private float nodeMoveRotationSnapDegrees = 90f; [SerializeField] private float undoChordWindow = 0.12f; [SerializeField] private float undoTriggerThreshold = 0.7f; [SerializeField] private LayerMask wireRaycastMask = -5; [SerializeField] private KeyCode keyboardSelectKey = KeyCode.G; [SerializeField] private KeyCode keyboardDuplicateKey = KeyCode.D; [SerializeField] private KeyCode keyboardDeleteKey = KeyCode.Delete; [SerializeField] private KeyCode keyboardWireKey = KeyCode.Space; [SerializeField] private int maxInlineValueChars = 64; private readonly Dictionary nodesById = new Dictionary(); private readonly Dictionary definitionsByName = new Dictionary(StringComparer.Ordinal); private readonly HashSet selectedNodes = new HashSet(); private readonly List selectionBuffer = new List(); private readonly List grabbedNodes = new List(); private readonly Dictionary wireVisuals = new Dictionary(StringComparer.Ordinal); private readonly List wireCleanupBuffer = new List(); private readonly List pendingWireRecords = new List(); private readonly List groupPickBuffer = new List(); private readonly Stack undoStack = new Stack(); private readonly Stack redoStack = new Stack(); [SerializeField] private int maxUndoSteps = 64; private NodeGraphPhotonSync networkSync; private InputDevice leftHandDevice; private InputDevice rightHandDevice; private GameObject nodePrefab; private Material lineMaterial; private LineRenderer previewWireRenderer; private LineRenderer aimRayRenderer; private NodePort hoveredPort; private Node hoveredNode; private VoxelGroup hoveredGroup; private PendingWirePress pendingWirePress; private ActiveWireContext activeWire; private ActiveValueEditContext activeValueEdit; private bool wireInputPrevious; private bool selectInputPrevious; private bool duplicateInputPrevious; private bool nodeGrabActive; private bool nodeSelectLatchedThisHold; private bool editVisibilityApplied = true; private bool suppressNetworkBroadcast; private bool suppressUndoRecording; private bool undoChordActive; private bool suppressUndoUntilNeutral; private bool undoGripHeldPrevious; private bool undoTriggerHeldPrevious; private float lastUndoGripPressTime = float.NegativeInfinity; private float lastUndoTriggerPressTime = float.NegativeInfinity; private int nextNodeId = 1; private Vector3 grabStartReferencePosition; private Quaternion grabStartReferenceRotation; private Vector3 grabStartSelectionCenter; private Vector3 grabStartSelectionOffset; public static NodeGraphManager Instance { get; private set; } public bool HasSelection => selectedNodes.Count > 0; public bool BlocksRightHandActions => nodeGrabActive; public bool CanUndo => undoStack.Count > 0; public bool CanRedo => redoStack.Count > 0; public event Action PlayerJoined; public event Action PlayerLeft; public bool UndoLastCircuitEdit() { if (!CanEdit() || undoStack.Count == 0) { return false; } CircuitUndoStep circuitUndoStep = undoStack.Pop(); redoStack.Push(circuitUndoStep); bool flag = suppressUndoRecording; suppressUndoRecording = true; try { for (int i = 0; i < circuitUndoStep.undo.Count; i++) { circuitUndoStep.undo[i]?.Apply(this, broadcast: true); } } finally { suppressUndoRecording = flag; } return true; } public bool RedoLastCircuitEdit() { if (!CanEdit() || redoStack.Count == 0) { return false; } CircuitUndoStep circuitUndoStep = redoStack.Pop(); undoStack.Push(circuitUndoStep); bool flag = suppressUndoRecording; suppressUndoRecording = true; try { for (int i = 0; i < circuitUndoStep.redo.Count; i++) { circuitUndoStep.redo[i]?.Apply(this, broadcast: true); } } finally { suppressUndoRecording = flag; } return true; } private void PushUndoStep(CircuitUndoStep step) { if (step == null || suppressUndoRecording) { return; } undoStack.Push(step); redoStack.Clear(); int num = Mathf.Clamp(maxUndoSteps, 0, 256); if (num <= 0) { undoStack.Clear(); redoStack.Clear(); } else if (undoStack.Count > num) { CircuitUndoStep[] array = undoStack.ToArray(); undoStack.Clear(); for (int num2 = Mathf.Min(num - 1, array.Length - 1); num2 >= 0; num2--) { undoStack.Push(array[num2]); } } } public int SelectNodesWithinSphere(Vector3 worldCenter, float radius, bool additive) { if (!CanEdit()) { return 0; } if (!additive) { ClearSelectionInternal(keepSuppression: false); } float num = Mathf.Max(0.0001f, radius) * Mathf.Max(0.0001f, radius); int num2 = 0; foreach (KeyValuePair item in nodesById) { Node value = item.Value; if (!(value == null) && !((value.transform.position - worldCenter).sqrMagnitude > num)) { bool num3 = selectedNodes.Contains(value); AddSelection(value); if (!num3) { num2++; } } } return num2; } public int DeleteNodesWithinSphere(Vector3 worldCenter, float radius, bool broadcast) { if (!CanEdit()) { return 0; } float num = Mathf.Max(0.0001f, radius) * Mathf.Max(0.0001f, radius); selectionBuffer.Clear(); foreach (KeyValuePair item in nodesById) { Node value = item.Value; if (!(value == null) && (value.transform.position - worldCenter).sqrMagnitude <= num) { selectionBuffer.Add(value); } } int num2 = 0; CircuitUndoStep circuitUndoStep = null; if (!suppressUndoRecording) { circuitUndoStep = new CircuitUndoStep(); } for (int i = 0; i < selectionBuffer.Count; i++) { Node node = selectionBuffer[i]; if (node == null || !nodesById.ContainsKey(node.NodeId)) { continue; } if (circuitUndoStep != null) { WorldGroupsCodec.WorldNodeRecord record = BuildNetworkNodeRecord(node); List wiresTouchingNode = GetWiresTouchingNode(node.NodeId); circuitUndoStep.undo.Add(new UpsertNodeMutation(record)); for (int j = 0; j < wiresTouchingNode.Count; j++) { circuitUndoStep.undo.Add(new ConnectWireMutation(wiresTouchingNode[j])); } circuitUndoStep.redo.Add(new DeleteNodeMutation(node.NodeId)); } DeleteNodeInternal(node, broadcast, recordUndo: false); num2++; } if (circuitUndoStep != null && num2 > 0) { PushUndoStep(circuitUndoStep); } selectionBuffer.Clear(); return num2; } private List GetWiresTouchingNode(int nodeId) { List list = BuildWorldWireRecords(); List list2 = new List(); for (int i = 0; i < list.Count; i++) { WorldGroupsCodec.WorldWireRecord item = list[i]; if (item.inputNodeId == nodeId || item.outputNodeId == nodeId) { list2.Add(item); } } return list2; } private static List GetExistingWiresForPort(NodePort port) { List list = new List(); if (port == null || port.Owner == null) { return list; } int nodeId = port.Owner.NodeId; if (port.Direction == PortDirection.Input) { if (port.Kind == PortKind.Run) { for (int i = 0; i < port.Connections.Count; i++) { NodePort nodePort = port.Connections[i]; if (!(nodePort?.Owner == null)) { list.Add(new WorldGroupsCodec.WorldWireRecord { outputNodeId = nodePort.Owner.NodeId, outputPortName = nodePort.Name, inputNodeId = nodeId, inputPortName = port.Name }); } } return list; } if (port.SourceGroupId > 0) { list.Add(WorldGroupsCodec.CreateGroupRuntimeWireRecord(port.SourceGroupId, nodeId, port.Name)); } else if (port.IncomingConnection != null && port.IncomingConnection.Owner != null) { list.Add(new WorldGroupsCodec.WorldWireRecord { outputNodeId = port.IncomingConnection.Owner.NodeId, outputPortName = port.IncomingConnection.Name, inputNodeId = nodeId, inputPortName = port.Name }); } return list; } for (int j = 0; j < port.Connections.Count; j++) { NodePort nodePort2 = port.Connections[j]; if (!(nodePort2?.Owner == null)) { list.Add(new WorldGroupsCodec.WorldWireRecord { outputNodeId = nodeId, outputPortName = port.Name, inputNodeId = nodePort2.Owner.NodeId, inputPortName = nodePort2.Name }); } } return list; } internal void NotifyPlayerJoined(int actorNumber) { if (actorNumber > 0) { this.PlayerJoined?.Invoke(actorNumber); } } internal void NotifyPlayerLeft(int actorNumber) { if (actorNumber > 0) { this.PlayerLeft?.Invoke(actorNumber); } } private void Awake() { if (Instance != null && Instance != this) { Debug.LogError("Multiple NodeGraphManager instances found.", this); return; } Instance = this; ResolveReferences(); CachePreloadedDefinitions(); CacheLoadedDefinitions(); EnsureLineResources(); } private void OnEnable() { RefreshDevices(); } private void Start() { RefreshSceneNodesFromHierarchy(); } private void OnDestroy() { if (Instance == this) { Instance = null; } if (lineMaterial != null) { UnityEngine.Object.Destroy(lineMaterial); } } private void Update() { ResolveReferences(); RefreshDevices(); CleanupDestroyedNodes(); TickNodeBehaviors(); bool flag = CanEdit(); ApplyEditVisibility(flag); if (!flag) { CancelEditingInteractions(clearSelection: true); return; } UpdateHoverTargets(); if (!HandleUndoChord() && !HandleInlineValueEdit()) { HandleSelectionInput(); HandleDuplicateInput(); HandleDeleteInput(); HandleGrabInput(); HandleWireInput(); } } private bool HandleInlineValueEdit() { if (UseXRInput()) { activeValueEdit = null; return false; } if (activeValueEdit != null) { if (activeValueEdit.port == null || activeValueEdit.port.Owner == null) { activeValueEdit = null; return false; } if (Input.GetKeyDown(KeyCode.Escape)) { RestoreInlineEditLabel(activeValueEdit.port); activeValueEdit = null; return true; } string inputString = Input.inputString; if (!string.IsNullOrEmpty(inputString)) { for (int i = 0; i < inputString.Length; i++) { char c = inputString[i]; switch (c) { case '\b': if (!string.IsNullOrEmpty(activeValueEdit.buffer)) { activeValueEdit.buffer = activeValueEdit.buffer.Substring(0, activeValueEdit.buffer.Length - 1); } continue; case '\n': case '\r': CommitInlineValueEdit(); return true; } if (!char.IsControl(c)) { if (activeValueEdit.buffer == null) { activeValueEdit.buffer = string.Empty; } if (activeValueEdit.buffer.Length < Mathf.Clamp(maxInlineValueChars, 1, 256)) { activeValueEdit.buffer += c; } } } } UpdateInlineEditLabel(activeValueEdit.port, activeValueEdit.buffer); return true; } if ((!Input.GetKeyDown(KeyCode.Return) && !Input.GetKeyDown(KeyCode.KeypadEnter)) || hoveredPort == null) { return false; } if (hoveredPort.Direction != PortDirection.Input || hoveredPort.Kind != PortKind.Value) { return false; } if (hoveredPort.IncomingConnection != null || hoveredPort.SourceGroupId > 0) { return false; } activeValueEdit = new ActiveValueEditContext { port = hoveredPort, originalBuffer = hoveredPort.Value.AsText(), buffer = hoveredPort.Value.AsText(), startedAt = Time.unscaledTime }; UpdateInlineEditLabel(hoveredPort, activeValueEdit.buffer); return true; } private void UpdateInlineEditLabel(NodePort port, string buffer) { if (port != null && !(port.LabelText == null)) { port.LabelText.text = port.Name + ": " + buffer + "_"; } } private void RestoreInlineEditLabel(NodePort port) { if (port != null && !(port.Owner == null)) { port.Owner.PulsePort(port); } } private void CommitInlineValueEdit() { if (activeValueEdit == null || activeValueEdit.port == null || activeValueEdit.port.Owner == null) { activeValueEdit = null; return; } NodePort port = activeValueEdit.port; Node owner = port.Owner; string value = activeValueEdit.buffer ?? string.Empty; activeValueEdit = null; RestoreInlineEditLabel(port); AssignInputValue(owner, port, BSValue.FromText(value), broadcast: true); } private bool HandleUndoChord() { if (!UseXRInput()) { undoChordActive = false; suppressUndoUntilNeutral = false; undoGripHeldPrevious = false; undoTriggerHeldPrevious = false; return false; } bool flag = ReadGrabHeld(rightHandDevice); bool flag2 = ReadFloat(rightHandDevice, CommonUsages.trigger) >= undoTriggerThreshold; if (!flag && undoGripHeldPrevious) { lastUndoGripPressTime = float.NegativeInfinity; } if (!flag2 && undoTriggerHeldPrevious) { lastUndoTriggerPressTime = float.NegativeInfinity; } if (flag && !undoGripHeldPrevious) { lastUndoGripPressTime = Time.unscaledTime; } if (flag2 && !undoTriggerHeldPrevious) { lastUndoTriggerPressTime = Time.unscaledTime; } if (suppressUndoUntilNeutral) { if (!flag && !flag2) { suppressUndoUntilNeutral = false; undoChordActive = false; } undoGripHeldPrevious = flag; undoTriggerHeldPrevious = flag2; return suppressUndoUntilNeutral; } if (!undoChordActive) { float num = Mathf.Max(lastUndoGripPressTime, lastUndoTriggerPressTime); if (flag && flag2 && num > 0f && Math.Abs(lastUndoGripPressTime - lastUndoTriggerPressTime) <= undoChordWindow && Time.unscaledTime - num <= undoChordWindow) { if (nodeGrabActive) { EndNodeGrab(broadcastFinal: true); } UndoLastCircuitEdit(); undoChordActive = true; suppressUndoUntilNeutral = true; } } else if (!flag || !flag2) { undoChordActive = false; } undoGripHeldPrevious = flag; undoTriggerHeldPrevious = flag2; return suppressUndoUntilNeutral; } private void TickNodeBehaviors() { if (nodesById.Count == 0) { return; } selectionBuffer.Clear(); foreach (KeyValuePair item in nodesById) { if (item.Value != null) { selectionBuffer.Add(item.Value); } } for (int i = 0; i < selectionBuffer.Count; i++) { Node node = selectionBuffer[i]; if (!(node == null)) { node.TickBehavior(); } } } private void LateUpdate() { UpdateWireVisuals(); UpdateTransientLines(); } public void Configure(VoxelGroupManager manager, VRVoxelBrush voxelBrush, Transform origin, Transform leftAnchor, Transform rightAnchor) { if (manager != null) { groupManager = manager; } if (voxelBrush != null) { brush = voxelBrush; } if (origin != null) { trackingOrigin = origin; } if (leftAnchor != null) { leftHandAnchor = leftAnchor; } if (rightAnchor != null) { rightHandAnchor = rightAnchor; } } public void RegisterNetworkSync(NodeGraphPhotonSync sync) { networkSync = sync; } public void RegisterNode(Node node) { if (!(node == null)) { CacheDefinition(node.Definition); if (node.NodeId <= 0 || (nodesById.TryGetValue(node.NodeId, out var value) && value != null && value != node)) { node.SetNodeId(AllocateNodeId()); } else { nextNodeId = Mathf.Max(nextNodeId, node.NodeId + 1); } node.AssignGraph(this); nodesById[node.NodeId] = node; node.SetVisibleInEditMode(CanEdit()); ResolvePendingWireRecords(); } } public List GetSelectedNodesSnapshot() { selectionBuffer.Clear(); foreach (Node selectedNode in selectedNodes) { if (selectedNode != null) { selectionBuffer.Add(selectedNode); } } return new List(selectionBuffer); } public List GetAllNodesSnapshot() { selectionBuffer.Clear(); foreach (KeyValuePair item in nodesById) { if (item.Value != null) { selectionBuffer.Add(item.Value); } } return new List(selectionBuffer); } public List GetAllDefinitionsSnapshot() { CacheLoadedDefinitions(); List list = new List(definitionsByName.Count); foreach (KeyValuePair item in definitionsByName) { if (item.Value != null) { list.Add(item.Value); } } return list; } public void ClearSelectionForToolUse() { ClearSelectionInternal(keepSuppression: false); } public void CancelEditingInteractions(bool clearSelection) { CancelPendingWirePress(); ClearActiveWire(); EndNodeGrab(broadcastFinal: true); ClearHoverState(); if (clearSelection) { ClearSelectionInternal(keepSuppression: false); } } public bool SelectNode(Node node, bool additive) { if (!CanEdit() || node == null || !nodesById.ContainsKey(node.NodeId)) { return false; } if (!additive) { ClearSelectionInternal(keepSuppression: false); } AddSelection(node); return true; } public bool MoveNode(Node node, Vector3 position, Quaternion rotation, bool broadcast) { if (!CanEdit() || node == null || !nodesById.ContainsKey(node.NodeId)) { return false; } node.transform.SetPositionAndRotation(position, rotation); if (broadcast && !suppressNetworkBroadcast) { ResolveNetworkSync()?.BroadcastUpsertNode(node); } return true; } public bool DeleteNode(Node node, bool broadcast) { if (!CanEdit() || node == null || !nodesById.ContainsKey(node.NodeId)) { return false; } DeleteNodeInternal(node, broadcast, recordUndo: true); return true; } public Node SpawnNode(NodeDefinition definition, Vector3 position, Quaternion rotation, bool broadcast) { if (!CanEdit() || definition == null) { return null; } Node node = SpawnNodeInternal(definition, position, rotation, 0); if (node != null && broadcast && !suppressNetworkBroadcast) { ResolveNetworkSync()?.BroadcastUpsertNode(node); } if (node != null && !suppressUndoRecording) { CircuitUndoStep circuitUndoStep = new CircuitUndoStep(); WorldGroupsCodec.WorldNodeRecord record = BuildNetworkNodeRecord(node); circuitUndoStep.undo.Add(new DeleteNodeMutation(node.NodeId)); circuitUndoStep.redo.Add(new UpsertNodeMutation(record)); PushUndoStep(circuitUndoStep); } return node; } public Node SpawnNodeSystem(NodeDefinition definition, Vector3 position, Quaternion rotation, bool broadcast) { if (definition == null) { return null; } Node node = SpawnNodeInternal(definition, position, rotation, 0); if (node != null && broadcast && !suppressNetworkBroadcast) { ResolveNetworkSync()?.BroadcastUpsertNode(node); } return node; } public Node SpawnNode(string definitionName, Vector3 position, Quaternion rotation, bool broadcast) { NodeDefinition definition = ResolveDefinition(definitionName); return SpawnNode(definition, position, rotation, broadcast); } public void AssignInputValue(Node node, NodePort inputPort, BSValue value, bool broadcast) { if (node == null || inputPort == null || inputPort.Direction != PortDirection.Input || inputPort.Kind != PortKind.Value) { return; } bool flag = ClearGroupInputSourceInternal(inputPort, clearValue: false, pulsePort: false); DisconnectInputPortInternal(inputPort, broadcast: false); node.SetInputValueLocal(inputPort, value, pulsePort: true); if (broadcast && !suppressNetworkBroadcast) { if (flag) { ResolveNetworkSync()?.BroadcastDisconnectInput(node.NodeId, inputPort.Name); } ResolveNetworkSync()?.BroadcastUpsertNode(node); } } public void ApplyOutputValue(Node node, NodePort outputPort, BSValue value, bool broadcast) { if (!(node == null) && outputPort != null && outputPort.Direction == PortDirection.Output && outputPort.Kind == PortKind.Value) { value = value.Normalize(); outputPort.Value = value; node.PulsePort(outputPort); PropagateOutputValue(outputPort, value, flashWire: true, pulseInputPorts: true); } } public void TriggerOutputRun(Node node, NodePort outputPort, bool broadcast) { if (node == null || outputPort == null || outputPort.Direction != PortDirection.Output || outputPort.Kind != PortKind.Run) { return; } node.PulsePort(outputPort); for (int i = 0; i < outputPort.Connections.Count; i++) { NodePort nodePort = outputPort.Connections[i]; if (nodePort != null && !(nodePort.Owner == null)) { nodePort.Owner.PulsePort(nodePort); FlashWire(outputPort, nodePort); nodePort.Owner.ReceiveRun(nodePort.Name); } } } public bool Connect(Node outputNode, string outputPortName, Node inputNode, string inputPortName, bool broadcast) { if (outputNode == null || inputNode == null) { return false; } if (!outputNode.TryGetPort(outputPortName, PortDirection.Output, out var port) || !inputNode.TryGetPort(inputPortName, PortDirection.Input, out var port2)) { return false; } return ConnectPorts(port, port2, broadcast); } public bool Disconnect(Node outputNode, string outputPortName, Node inputNode, string inputPortName, bool broadcast) { if (outputNode == null || inputNode == null) { return false; } if (!outputNode.TryGetPort(outputPortName, PortDirection.Output, out var port) || !inputNode.TryGetPort(inputPortName, PortDirection.Input, out var port2)) { return false; } return DisconnectPortsInternal(port, port2, broadcast); } public void ClearAllNodesForNetworkSync() { bool flag = suppressNetworkBroadcast; suppressNetworkBroadcast = true; try { CancelEditingInteractions(clearSelection: true); selectionBuffer.Clear(); foreach (KeyValuePair item in nodesById) { if (item.Value != null) { selectionBuffer.Add(item.Value); } } for (int i = 0; i < selectionBuffer.Count; i++) { DeleteNodeInternal(selectionBuffer[i], broadcast: false, recordUndo: false); } nodesById.Clear(); pendingWireRecords.Clear(); nextNodeId = 1; } finally { suppressNetworkBroadcast = flag; } } public List BuildWorldNodeRecords(HashSet filterNodeIds = null) { List list = new List(); foreach (KeyValuePair item in nodesById) { Node value = item.Value; if (!(value == null) && !(value.Definition == null) && (filterNodeIds == null || filterNodeIds.Contains(value.NodeId))) { list.Add(BuildNodeRecord(value)); } } return list; } public WorldGroupsCodec.WorldNodeRecord BuildWorldNodeRecord(Node node) { return BuildNodeRecord(node); } public WorldGroupsCodec.WorldNodeRecord BuildNetworkNodeRecord(Node node) { if (node == null) { return default(WorldGroupsCodec.WorldNodeRecord); } return new WorldGroupsCodec.WorldNodeRecord { nodeId = node.NodeId, definitionName = ((node.Definition != null) ? node.Definition.name : string.Empty), position = node.transform.position, rotation = node.transform.rotation, inputValues = BuildNetworkInputValueRecords(node.InputPorts), outputValues = null }; } private static WorldGroupsCodec.NodePortValueRecord[] BuildNetworkInputValueRecords(List ports) { List list = new List(); if (ports == null) { return list.ToArray(); } for (int i = 0; i < ports.Count; i++) { NodePort nodePort = ports[i]; if (nodePort != null && nodePort.Kind == PortKind.Value && nodePort.Direction == PortDirection.Input && nodePort.IncomingConnection == null && nodePort.SourceGroupId <= 0) { list.Add(new WorldGroupsCodec.NodePortValueRecord { portName = nodePort.Name, value = nodePort.Value.Normalize() }); } } return list.ToArray(); } public List BuildWorldWireRecords(HashSet filterNodeIds = null, Dictionary sourceGroupSaveIndices = null) { List list = new List(); foreach (KeyValuePair item in nodesById) { Node value = item.Value; if (value == null) { continue; } for (int i = 0; i < value.OutputPorts.Count; i++) { NodePort nodePort = value.OutputPorts[i]; if (nodePort == null) { continue; } for (int j = 0; j < nodePort.Connections.Count; j++) { NodePort nodePort2 = nodePort.Connections[j]; if (nodePort2 != null && !(nodePort2.Owner == null) && (filterNodeIds == null || (filterNodeIds.Contains(value.NodeId) && filterNodeIds.Contains(nodePort2.Owner.NodeId)))) { list.Add(new WorldGroupsCodec.WorldWireRecord { outputNodeId = value.NodeId, outputPortName = nodePort.Name, inputNodeId = nodePort2.Owner.NodeId, inputPortName = nodePort2.Name }); } } } for (int k = 0; k < value.InputPorts.Count; k++) { NodePort nodePort3 = value.InputPorts[k]; if (nodePort3 == null || nodePort3.Kind != PortKind.Value || nodePort3.Direction != PortDirection.Input || nodePort3.SourceGroupId <= 0 || (filterNodeIds != null && !filterNodeIds.Contains(value.NodeId))) { continue; } if (sourceGroupSaveIndices != null) { if (sourceGroupSaveIndices.TryGetValue(nodePort3.SourceGroupId, out var value2)) { list.Add(WorldGroupsCodec.CreateGroupSavedWireRecord(value2, value.NodeId, nodePort3.Name)); } } else { list.Add(WorldGroupsCodec.CreateGroupRuntimeWireRecord(nodePort3.SourceGroupId, value.NodeId, nodePort3.Name)); } } } return list; } public void ApplySavedGraph(List nodeRecords, List wireRecords, bool clearExisting, bool broadcast, Vector3 positionOffset, Dictionary savedGroupIndexToRuntimeId = null) { bool flag = suppressNetworkBroadcast; suppressNetworkBroadcast = suppressNetworkBroadcast || !broadcast; List list = ((broadcast && clearExisting && !flag) ? new List(nodesById.Keys) : null); List list2 = ((broadcast && nodeRecords != null) ? new List(nodeRecords.Count) : null); List list3 = ((broadcast && wireRecords != null) ? new List(wireRecords.Count) : null); try { if (clearExisting) { ClearAllNodesForNetworkSync(); } if (nodeRecords != null) { for (int i = 0; i < nodeRecords.Count; i++) { WorldGroupsCodec.WorldNodeRecord worldNodeRecord = nodeRecords[i]; worldNodeRecord.position += positionOffset; list2?.Add(worldNodeRecord); ApplyNodeRecord(worldNodeRecord, broadcast: false); } } if (wireRecords != null) { for (int j = 0; j < wireRecords.Count; j++) { if (TryResolveSavedGroupWireRecord(wireRecords[j], savedGroupIndexToRuntimeId, out var resolvedRecord)) { list3?.Add(resolvedRecord); ApplyWireRecord(resolvedRecord, broadcast: false); } } } ResolvePendingWireRecords(); if (!broadcast || flag) { return; } if (list != null) { for (int k = 0; k < list.Count; k++) { ResolveNetworkSync()?.BroadcastDeleteNode(list[k]); } } if (list2 != null) { for (int l = 0; l < list2.Count; l++) { if (nodesById.TryGetValue(list2[l].nodeId, out var value) && value != null) { ResolveNetworkSync()?.BroadcastUpsertNode(value); } } } if (list3 != null) { for (int m = 0; m < list3.Count; m++) { ResolveNetworkSync()?.BroadcastConnectWire(list3[m]); } } } finally { suppressNetworkBroadcast = flag; } } public void ApplyNetworkNodeRecord(WorldGroupsCodec.WorldNodeRecord record) { bool flag = suppressNetworkBroadcast; suppressNetworkBroadcast = true; bool flag2 = suppressUndoRecording; suppressUndoRecording = true; try { ApplyNodeRecord(record, broadcast: false); } finally { suppressNetworkBroadcast = flag; suppressUndoRecording = flag2; } } public void ApplyNetworkDelete(int nodeId) { bool flag = suppressNetworkBroadcast; suppressNetworkBroadcast = true; bool flag2 = suppressUndoRecording; suppressUndoRecording = true; try { if (nodesById.TryGetValue(nodeId, out var value)) { DeleteNodeInternal(value, broadcast: false, recordUndo: false); } } finally { suppressNetworkBroadcast = flag; suppressUndoRecording = flag2; } } public void ApplyNetworkWireRecord(WorldGroupsCodec.WorldWireRecord record) { bool flag = suppressNetworkBroadcast; suppressNetworkBroadcast = true; bool flag2 = suppressUndoRecording; suppressUndoRecording = true; try { ApplyWireRecord(record, broadcast: false); } finally { suppressNetworkBroadcast = flag; suppressUndoRecording = flag2; } } public void ApplyNetworkDisconnectInput(int inputNodeId, string inputPortName) { bool flag = suppressNetworkBroadcast; suppressNetworkBroadcast = true; bool flag2 = suppressUndoRecording; suppressUndoRecording = true; try { if (nodesById.TryGetValue(inputNodeId, out var value) && value != null && value.TryGetPort(inputPortName, PortDirection.Input, out var port)) { bool num = port.SourceGroupId > 0; DisconnectInputPortInternal(port, broadcast: false); if (num) { ClearGroupInputSourceInternal(port, clearValue: true, pulsePort: false); } } } finally { suppressNetworkBroadcast = flag; suppressUndoRecording = flag2; } } public void ApplyNetworkDisconnectOutput(int outputNodeId, string outputPortName) { bool flag = suppressNetworkBroadcast; suppressNetworkBroadcast = true; bool flag2 = suppressUndoRecording; suppressUndoRecording = true; try { if (nodesById.TryGetValue(outputNodeId, out var value) && value != null && value.TryGetPort(outputPortName, PortDirection.Output, out var port)) { DisconnectOutputPortInternal(port, broadcast: false); } } finally { suppressNetworkBroadcast = flag; suppressUndoRecording = flag2; } } public void ApplyNetworkTriggerRun(int outputNodeId, string outputPortName) { bool flag = suppressNetworkBroadcast; suppressNetworkBroadcast = true; try { if (nodesById.TryGetValue(outputNodeId, out var value) && value != null && value.TryGetPort(outputPortName, PortDirection.Output, PortKind.Run, out var port)) { TriggerOutputRun(value, port, broadcast: false); } } finally { suppressNetworkBroadcast = flag; } } private void ResolveReferences() { if (groupManager == null) { groupManager = ((VoxelGroupManager.Instance != null) ? VoxelGroupManager.Instance : UnityEngine.Object.FindAnyObjectByType()); } if (brush == null) { brush = ((VRVoxelBrush.Instance != null) ? VRVoxelBrush.Instance : GetComponent()); } if (trackingOrigin == null && brush != null) { trackingOrigin = brush.TrackingOrigin; } if (leftHandAnchor == null && brush != null) { leftHandAnchor = brush.LeftHandAnchor; } if (rightHandAnchor == null && brush != null) { rightHandAnchor = brush.RightHandAnchor; } if (networkSync == null) { networkSync = GetComponent(); } } private void RefreshDevices() { if (!leftHandDevice.isValid) { leftHandDevice = InputDevices.GetDeviceAtXRNode(XRNode.LeftHand); } if (!rightHandDevice.isValid) { rightHandDevice = InputDevices.GetDeviceAtXRNode(XRNode.RightHand); } } private void RefreshSceneNodesFromHierarchy() { Node[] array = UnityEngine.Object.FindObjectsByType(FindObjectsInactive.Include); Array.Sort(array, CompareNodesByHierarchyPath); nodesById.Clear(); nextNodeId = 1; foreach (Node node in array) { if (!(node == null)) { CacheDefinition(node.Definition); if (node.NodeId <= 0 || nodesById.ContainsKey(node.NodeId)) { node.SetNodeId(nextNodeId++); } else { nextNodeId = Mathf.Max(nextNodeId, node.NodeId + 1); } node.AssignGraph(this); node.SetVisibleInEditMode(CanEdit()); nodesById[node.NodeId] = node; } } foreach (Node node2 in array) { if (node2 == null) { continue; } List list = node2.ConsumePendingSerializedConnections(); for (int k = 0; k < list.Count; k++) { NodeSerializedConnection nodeSerializedConnection = list[k]; if (nodeSerializedConnection != null && !(nodeSerializedConnection.inputNode == null) && node2.TryGetPort(nodeSerializedConnection.outputPortName, PortDirection.Output, out var port) && nodeSerializedConnection.inputNode.TryGetPort(nodeSerializedConnection.inputPortName, PortDirection.Input, out var port2)) { ConnectPorts(port, port2, broadcast: false); } } } } private void CleanupDestroyedNodes() { bool flag = false; foreach (KeyValuePair item in nodesById) { if (item.Value == null) { flag = true; break; } } if (!flag) { return; } List list = new List(); foreach (KeyValuePair item2 in nodesById) { if (item2.Value == null) { list.Add(item2.Key); } } for (int i = 0; i < list.Count; i++) { nodesById.Remove(list[i]); } selectedNodes.RemoveWhere((Node node) => node == null); grabbedNodes.RemoveAll((NodeGrabState state) => state == null || state.node == null); } private void ApplyEditVisibility(bool visible) { if (editVisibilityApplied == visible) { return; } editVisibilityApplied = visible; foreach (KeyValuePair item in nodesById) { if (item.Value != null) { item.Value.SetVisibleInEditMode(visible); } } } private void UpdateHoverTargets() { ClearHoverState(); if (TryGetLeftRay(out var ray)) { hoveredPort = TryPickPort(ray, wireRayDistance, GetExpectedWireTargetKind(), GetExpectedWireTargetDirection()); if (hoveredPort != null) { hoveredPort.Owner?.SetPortHighlight(hoveredPort, highlighted: true); } if (hoveredPort == null && activeWire == null) { hoveredGroup = TryPickGroup(ray); } } if (TryGetRightRay(out var ray2)) { hoveredNode = TryPickNode(ray2); } } private void ClearHoverState() { if (hoveredPort != null && hoveredPort.Owner != null) { hoveredPort.Owner.SetPortHighlight(hoveredPort, highlighted: false); } hoveredPort = null; hoveredNode = null; hoveredGroup = null; } private void HandleSelectionInput() { bool flag = ((!UseXRInput()) ? (Input.GetMouseButton(2) || Input.GetKey(keyboardSelectKey)) : (ReadBool(rightHandDevice, CommonUsages.primary2DAxisClick) || Input.GetKey(keyboardSelectKey))); if (flag && !selectInputPrevious) { nodeSelectLatchedThisHold = false; } if (!flag && selectInputPrevious) { nodeSelectLatchedThisHold = false; } if (flag && hoveredNode != null) { nodeSelectLatchedThisHold = true; } if (flag && nodeSelectLatchedThisHold && hoveredNode != null) { AddSelection(hoveredNode); } selectInputPrevious = flag; } private void HandleDuplicateInput() { bool flag = UseXRInput() && ReadBool(leftHandDevice, CommonUsages.primaryButton); bool flag2 = Input.GetKeyDown(keyboardDuplicateKey) && (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)); if (!HasSelection) { duplicateInputPrevious = flag; return; } if ((flag && !duplicateInputPrevious) || flag2) { DuplicateSelectedNodes(); } duplicateInputPrevious = flag; } private void HandleDeleteInput() { if (HasSelection && Input.GetKeyDown(keyboardDeleteKey)) { DeleteSelectedNodes(); } } private void HandleGrabInput() { bool flag = (UseXRInput() ? ReadGrabHeld(rightHandDevice) : Input.GetMouseButton(1)); if (HasSelection && flag) { if (!nodeGrabActive) { BeginNodeGrab(); } UpdateNodeGrab(); } else if (nodeGrabActive) { EndNodeGrab(broadcastFinal: true); } } private void HandleWireInput() { bool num = UseXRInput(); bool flag = num && ReadGrabHeld(leftHandDevice); bool flag2 = (num ? (ReadFloat(leftHandDevice, CommonUsages.trigger) >= wireTriggerThreshold) : Input.GetKey(keyboardWireKey)); bool flag3 = flag2 && !wireInputPrevious; bool flag4 = !flag2 && wireInputPrevious; if (flag) { wireInputPrevious = flag2; return; } if (activeWire != null && (flag3 || flag4)) { if (hoveredPort == null || !IsWireTargetCompatible(hoveredPort)) { ClearActiveWire(); } else { CompleteActiveWire(hoveredPort); } wireInputPrevious = flag2; return; } if (pendingWirePress != null) { if (!flag2) { if (Time.unscaledTime - pendingWirePress.pressedAt <= quickTapThreshold) { HandleQuickWireTap(pendingWirePress); } else { StartActiveWire(pendingWirePress.startPort, pendingWirePress.startGroup, pendingWirePress.startWorldPosition); } pendingWirePress = null; } else if (Time.unscaledTime - pendingWirePress.pressedAt > quickTapThreshold) { StartActiveWire(pendingWirePress.startPort, pendingWirePress.startGroup, pendingWirePress.startWorldPosition); pendingWirePress = null; } } else if (flag3) { if (hoveredPort != null) { pendingWirePress = new PendingWirePress { startPort = hoveredPort, startWorldPosition = ((hoveredPort.Owner != null) ? hoveredPort.Owner.GetPortWorldPosition(hoveredPort) : base.transform.position), pressedAt = Time.unscaledTime }; } else if (hoveredGroup != null) { pendingWirePress = new PendingWirePress { startGroup = hoveredGroup, startWorldPosition = hoveredGroup.GetWorldBounds().center, pressedAt = Time.unscaledTime }; } } if (flag4 && activeWire == null) { CancelPendingWirePress(); } wireInputPrevious = flag2; } private void BeginNodeGrab() { if (!TryGetGrabReferencePose(out grabStartReferencePosition, out grabStartReferenceRotation) || !TryGetSelectionBounds(out var bounds)) { return; } grabbedNodes.Clear(); foreach (Node selectedNode in selectedNodes) { if (!(selectedNode == null)) { grabbedNodes.Add(new NodeGrabState { node = selectedNode, startPosition = selectedNode.transform.position, startRotation = selectedNode.transform.rotation }); } } if (grabbedNodes.Count != 0) { nodeGrabActive = true; grabStartSelectionCenter = bounds.center; grabStartSelectionOffset = bounds.center - grabStartReferencePosition; } } private void UpdateNodeGrab() { if (!nodeGrabActive || !TryGetGrabReferencePose(out var worldPosition, out var worldRotation)) { return; } Quaternion quaternion = SnapRotation(worldRotation * Quaternion.Inverse(grabStartReferenceRotation), nodeMoveRotationSnapDegrees); Vector3 vector = SnapVector(worldPosition + grabStartSelectionOffset, nodeMovePositionSnap); for (int num = grabbedNodes.Count - 1; num >= 0; num--) { NodeGrabState nodeGrabState = grabbedNodes[num]; if (nodeGrabState == null || nodeGrabState.node == null || !selectedNodes.Contains(nodeGrabState.node)) { grabbedNodes.RemoveAt(num); } else { Vector3 vector2 = nodeGrabState.startPosition - grabStartSelectionCenter; Vector3 position = vector + quaternion * vector2; Quaternion rotation = quaternion * nodeGrabState.startRotation; nodeGrabState.node.transform.SetPositionAndRotation(position, rotation); nodeGrabState.node.SetSelected(isSelected: true); if (!suppressNetworkBroadcast && Time.unscaledTime - nodeGrabState.lastPreviewSentAt >= Mathf.Max(0.01f, movePreviewSendInterval)) { ResolveNetworkSync()?.BroadcastUpsertNode(nodeGrabState.node); nodeGrabState.lastPreviewSentAt = Time.unscaledTime; } } } } private void EndNodeGrab(bool broadcastFinal) { if (!nodeGrabActive) { grabbedNodes.Clear(); return; } nodeGrabActive = false; if (!suppressUndoRecording && grabbedNodes.Count > 0) { CircuitUndoStep circuitUndoStep = new CircuitUndoStep(); int num = 0; for (int i = 0; i < grabbedNodes.Count; i++) { NodeGrabState nodeGrabState = grabbedNodes[i]; if (nodeGrabState != null && !(nodeGrabState.node == null) && nodesById.ContainsKey(nodeGrabState.node.NodeId)) { Vector3 position = nodeGrabState.node.transform.position; Quaternion rotation = nodeGrabState.node.transform.rotation; if (!((position - nodeGrabState.startPosition).sqrMagnitude <= 1E-06f) || !(Quaternion.Angle(rotation, nodeGrabState.startRotation) <= 0.01f)) { WorldGroupsCodec.WorldNodeRecord record = BuildNetworkNodeRecord(nodeGrabState.node); record.position = nodeGrabState.startPosition; record.rotation = nodeGrabState.startRotation; WorldGroupsCodec.WorldNodeRecord record2 = BuildNetworkNodeRecord(nodeGrabState.node); circuitUndoStep.undo.Add(new UpsertNodeMutation(record)); circuitUndoStep.redo.Add(new UpsertNodeMutation(record2)); num++; } } } if (num > 0) { PushUndoStep(circuitUndoStep); } } if (broadcastFinal && !suppressNetworkBroadcast) { for (int j = 0; j < grabbedNodes.Count; j++) { if (grabbedNodes[j] != null && grabbedNodes[j].node != null) { ResolveNetworkSync()?.BroadcastUpsertNode(grabbedNodes[j].node); } } } grabbedNodes.Clear(); } private void StartActiveWire(NodePort startPort, VoxelGroup startGroup, Vector3 startWorldPosition) { PortKind kind = startPort?.Kind ?? PortKind.Value; activeWire = new ActiveWireContext { startPort = startPort, startGroup = startGroup, startWorldPosition = startWorldPosition, Kind = kind }; } private void CompleteActiveWire(NodePort targetPort) { if (activeWire == null || targetPort == null) { ClearActiveWire(); return; } if (activeWire.startGroup != null) { if (targetPort.Direction == PortDirection.Input && targetPort.Kind == PortKind.Value) { List list = (suppressUndoRecording ? null : GetExistingWiresForPort(targetPort)); if (ConnectGroupToInputPort(activeWire.startGroup.id, targetPort, broadcast: true) && !suppressUndoRecording) { CircuitUndoStep circuitUndoStep = new CircuitUndoStep(); WorldGroupsCodec.WorldWireRecord record = WorldGroupsCodec.CreateGroupRuntimeWireRecord(activeWire.startGroup.id, (targetPort.Owner != null) ? targetPort.Owner.NodeId : 0, targetPort.Name); circuitUndoStep.undo.Add(new DisconnectWireMutation(record)); if (list != null) { for (int i = 0; i < list.Count; i++) { circuitUndoStep.undo.Add(new ConnectWireMutation(list[i])); } } circuitUndoStep.redo.Add(new ConnectWireMutation(record)); PushUndoStep(circuitUndoStep); } } ClearActiveWire(); return; } if (activeWire.startPort == null) { ClearActiveWire(); return; } if (activeWire.startPort == targetPort) { ClearActiveWire(); return; } NodePort startPort = activeWire.startPort; List list2 = (suppressUndoRecording ? null : GetExistingWiresForPort(startPort)); List list3 = (suppressUndoRecording ? null : GetExistingWiresForPort(targetPort)); if (ConnectPortsDirectional(startPort, targetPort, broadcast: true) && !suppressUndoRecording) { NodePort nodePort = ((startPort.Direction == PortDirection.Output) ? startPort : targetPort); NodePort nodePort2 = ((startPort.Direction == PortDirection.Input) ? startPort : targetPort); WorldGroupsCodec.WorldWireRecord record2 = new WorldGroupsCodec.WorldWireRecord { outputNodeId = ((nodePort.Owner != null) ? nodePort.Owner.NodeId : 0), outputPortName = nodePort.Name, inputNodeId = ((nodePort2.Owner != null) ? nodePort2.Owner.NodeId : 0), inputPortName = nodePort2.Name }; CircuitUndoStep circuitUndoStep2 = new CircuitUndoStep(); circuitUndoStep2.undo.Add(new DisconnectWireMutation(record2)); if (list2 != null) { for (int j = 0; j < list2.Count; j++) { circuitUndoStep2.undo.Add(new ConnectWireMutation(list2[j])); } } if (list3 != null) { for (int k = 0; k < list3.Count; k++) { circuitUndoStep2.undo.Add(new ConnectWireMutation(list3[k])); } } circuitUndoStep2.redo.Add(new ConnectWireMutation(record2)); PushUndoStep(circuitUndoStep2); } ClearActiveWire(); } private void HandleQuickWireTap(PendingWirePress pending) { if (pending == null || pending.startGroup != null) { return; } NodePort startPort = pending.startPort; if (startPort == null) { return; } if (startPort.Direction == PortDirection.Input) { bool num = startPort.Kind == PortKind.Run && startPort.Connections.Count > 0; bool flag = startPort.Kind != PortKind.Run && (startPort.IncomingConnection != null || startPort.SourceGroupId > 0); if (num || flag) { List list = (suppressUndoRecording ? null : GetExistingWiresForPort(startPort)); bool num2 = startPort.SourceGroupId > 0; DisconnectInputPortInternal(startPort, broadcast: true); if (num2) { ClearGroupInputSourceInternal(startPort, clearValue: true, pulsePort: false); if (!suppressNetworkBroadcast) { ResolveNetworkSync()?.BroadcastDisconnectInput((startPort.Owner != null) ? startPort.Owner.NodeId : 0, startPort.Name); } } if (!suppressUndoRecording && list != null && list.Count > 0) { CircuitUndoStep circuitUndoStep = new CircuitUndoStep(); circuitUndoStep.redo.Add(new DisconnectInputMutation((startPort.Owner != null) ? startPort.Owner.NodeId : 0, startPort.Name)); for (int i = 0; i < list.Count; i++) { circuitUndoStep.undo.Add(new ConnectWireMutation(list[i])); } PushUndoStep(circuitUndoStep); } } else { if (startPort.Kind != PortKind.Value || !(startPort.Owner != null)) { return; } if (!suppressUndoRecording) { BSValue a = startPort.Value.Normalize(); WorldGroupsCodec.WorldNodeRecord record = BuildNetworkNodeRecord(startPort.Owner); AssignInputValue(startPort.Owner, startPort, BSValue.None, broadcast: true); WorldGroupsCodec.WorldNodeRecord record2 = BuildNetworkNodeRecord(startPort.Owner); BSValue b = startPort.Value.Normalize(); if (!BSValueUtility.Equals(a, b)) { CircuitUndoStep circuitUndoStep2 = new CircuitUndoStep(); circuitUndoStep2.undo.Add(new UpsertNodeMutation(record)); circuitUndoStep2.redo.Add(new UpsertNodeMutation(record2)); PushUndoStep(circuitUndoStep2); } } else { AssignInputValue(startPort.Owner, startPort, BSValue.None, broadcast: true); } } return; } List list2 = (suppressUndoRecording ? null : GetExistingWiresForPort(startPort)); DisconnectOutputPortInternal(startPort, broadcast: true); if (!suppressUndoRecording && list2 != null && list2.Count > 0) { CircuitUndoStep circuitUndoStep3 = new CircuitUndoStep(); circuitUndoStep3.redo.Add(new DisconnectOutputMutation((startPort.Owner != null) ? startPort.Owner.NodeId : 0, startPort.Name)); for (int j = 0; j < list2.Count; j++) { circuitUndoStep3.undo.Add(new ConnectWireMutation(list2[j])); } PushUndoStep(circuitUndoStep3); } } private void CancelPendingWirePress() { pendingWirePress = null; } private void ClearActiveWire() { activeWire = null; } private void AddSelection(Node node) { if (!(node == null) && selectedNodes.Add(node)) { node.SetSelected(isSelected: true); } } private void ClearSelectionInternal(bool keepSuppression) { selectionBuffer.Clear(); foreach (Node selectedNode in selectedNodes) { if (selectedNode != null) { selectionBuffer.Add(selectedNode); } } for (int i = 0; i < selectionBuffer.Count; i++) { selectionBuffer[i].SetSelected(isSelected: false); } selectedNodes.Clear(); if (!keepSuppression) { grabbedNodes.Clear(); } } private void DeleteSelectedNodes() { CircuitUndoStep circuitUndoStep = (suppressUndoRecording ? null : new CircuitUndoStep()); selectionBuffer.Clear(); foreach (Node selectedNode in selectedNodes) { if (selectedNode != null) { selectionBuffer.Add(selectedNode); } } ClearSelectionInternal(keepSuppression: false); for (int i = 0; i < selectionBuffer.Count; i++) { Node node = selectionBuffer[i]; if (node == null) { continue; } if (circuitUndoStep != null && nodesById.ContainsKey(node.NodeId)) { WorldGroupsCodec.WorldNodeRecord record = BuildNetworkNodeRecord(node); List wiresTouchingNode = GetWiresTouchingNode(node.NodeId); circuitUndoStep.undo.Add(new UpsertNodeMutation(record)); for (int j = 0; j < wiresTouchingNode.Count; j++) { circuitUndoStep.undo.Add(new ConnectWireMutation(wiresTouchingNode[j])); } circuitUndoStep.redo.Add(new DeleteNodeMutation(node.NodeId)); } DeleteNodeInternal(node, broadcast: true, recordUndo: false); } if (circuitUndoStep != null && circuitUndoStep.redo.Count > 0) { PushUndoStep(circuitUndoStep); } } private void DuplicateSelectedNodes() { if (selectedNodes.Count == 0) { return; } CircuitUndoStep circuitUndoStep = (suppressUndoRecording ? null : new CircuitUndoStep()); Dictionary dictionary = new Dictionary(); List list = BuildWorldWireRecords(); List selectedNodesSnapshot = GetSelectedNodesSnapshot(); HashSet hashSet = new HashSet(); for (int i = 0; i < selectedNodesSnapshot.Count; i++) { if (selectedNodesSnapshot[i] != null) { hashSet.Add(selectedNodesSnapshot[i].NodeId); } } ClearSelectionInternal(keepSuppression: false); Vector3 vector = new Vector3(0.25f, 0.1f, 0.25f); for (int j = 0; j < selectedNodesSnapshot.Count; j++) { Node node = selectedNodesSnapshot[j]; if (node == null || node.Definition == null) { continue; } Node node2 = SpawnNodeInternal(node.Definition, node.transform.position + vector, node.transform.rotation, 0); if (!(node2 == null)) { CopyNodeValues(node, node2); dictionary[node.NodeId] = node2; AddSelection(node2); if (!suppressNetworkBroadcast) { ResolveNetworkSync()?.BroadcastUpsertNode(node2); } } } for (int k = 0; k < list.Count; k++) { WorldGroupsCodec.WorldWireRecord record = list[k]; Node value2; Node value3; if (WorldGroupsCodec.TryGetGroupRuntimeWireSourceId(record, out var groupId)) { if (hashSet.Contains(record.inputNodeId) && dictionary.TryGetValue(record.inputNodeId, out var value)) { ConnectGroupToInput(groupId, value, record.inputPortName, broadcast: true); } } else if (hashSet.Contains(record.outputNodeId) && hashSet.Contains(record.inputNodeId) && dictionary.TryGetValue(record.outputNodeId, out value2) && dictionary.TryGetValue(record.inputNodeId, out value3)) { Connect(value2, record.outputPortName, value3, record.inputPortName, broadcast: true); } } if (circuitUndoStep == null || dictionary.Count <= 0) { return; } HashSet hashSet2 = new HashSet(); foreach (KeyValuePair item2 in dictionary) { if (item2.Value != null) { hashSet2.Add(item2.Value.NodeId); } } List list2 = new List(); foreach (int item3 in hashSet2) { if (nodesById.TryGetValue(item3, out var value4) && value4 != null) { list2.Add(BuildNetworkNodeRecord(value4)); } } List list3 = BuildWorldWireRecords(); List list4 = new List(); for (int l = 0; l < list3.Count; l++) { WorldGroupsCodec.WorldWireRecord item = list3[l]; if (hashSet2.Contains(item.inputNodeId) || hashSet2.Contains(item.outputNodeId)) { list4.Add(item); } } foreach (int item4 in hashSet2) { circuitUndoStep.undo.Add(new DeleteNodeMutation(item4)); } for (int m = 0; m < list2.Count; m++) { circuitUndoStep.redo.Add(new UpsertNodeMutation(list2[m])); } for (int n = 0; n < list4.Count; n++) { circuitUndoStep.redo.Add(new ConnectWireMutation(list4[n])); } PushUndoStep(circuitUndoStep); } private void CopyNodeValues(Node source, Node target) { if (source == null || target == null) { return; } for (int i = 0; i < source.InputPorts.Count; i++) { NodePort nodePort = source.InputPorts[i]; if (nodePort != null && nodePort.Kind == PortKind.Value && target.TryGetPort(nodePort.Name, PortDirection.Input, PortKind.Value, out var port)) { target.SetInputValueLocal(port, nodePort.Value, pulsePort: false); } } for (int j = 0; j < source.OutputPorts.Count; j++) { NodePort nodePort2 = source.OutputPorts[j]; if (nodePort2 != null && nodePort2.Kind == PortKind.Value && target.TryGetPort(nodePort2.Name, PortDirection.Output, PortKind.Value, out var port2)) { port2.Value = nodePort2.Value.Normalize(); } } } private void DeleteNodeInternal(Node node, bool broadcast, bool recordUndo) { if (node == null) { return; } if (recordUndo && !suppressUndoRecording && nodesById.ContainsKey(node.NodeId)) { CircuitUndoStep circuitUndoStep = new CircuitUndoStep(); WorldGroupsCodec.WorldNodeRecord record = BuildNetworkNodeRecord(node); List wiresTouchingNode = GetWiresTouchingNode(node.NodeId); circuitUndoStep.undo.Add(new UpsertNodeMutation(record)); for (int i = 0; i < wiresTouchingNode.Count; i++) { circuitUndoStep.undo.Add(new ConnectWireMutation(wiresTouchingNode[i])); } circuitUndoStep.redo.Add(new DeleteNodeMutation(node.NodeId)); PushUndoStep(circuitUndoStep); } DisconnectAllForNode(node, broadcast: false); selectedNodes.Remove(node); grabbedNodes.RemoveAll((NodeGrabState state) => state == null || state.node == node); nodesById.Remove(node.NodeId); if (broadcast && !suppressNetworkBroadcast) { ResolveNetworkSync()?.BroadcastDeleteNode(node.NodeId); } if (Application.isPlaying) { UnityEngine.Object.Destroy(node.gameObject); } else { UnityEngine.Object.DestroyImmediate(node.gameObject); } } private void DisconnectAllForNode(Node node, bool broadcast) { if (!(node == null)) { for (int i = 0; i < node.InputPorts.Count; i++) { DisconnectInputPortInternal(node.InputPorts[i], broadcast: false); ClearGroupInputSourceInternal(node.InputPorts[i], clearValue: false, pulsePort: false); } for (int j = 0; j < node.OutputPorts.Count; j++) { DisconnectOutputPortInternal(node.OutputPorts[j], broadcast: false); } if (broadcast && !suppressNetworkBroadcast) { ResolveNetworkSync()?.BroadcastUpsertNode(node); } } } private bool ConnectPortsDirectional(NodePort firstPort, NodePort secondPort, bool broadcast) { if (!TryResolveDirectionalPorts(firstPort, secondPort, out var outputPort, out var inputPort)) { return false; } return ConnectPorts(outputPort, inputPort, broadcast); } private bool ConnectPorts(NodePort outputPort, NodePort inputPort, bool broadcast) { if (outputPort == null || inputPort == null) { return false; } if (outputPort.Direction != PortDirection.Output || inputPort.Direction != PortDirection.Input || outputPort.Kind != inputPort.Kind) { return false; } if (outputPort.Kind == PortKind.Run) { if (outputPort.Connections.Count > 0 && !outputPort.Connections.Contains(inputPort)) { DisconnectOutputPortInternal(outputPort, broadcast: false); } ClearGroupInputSourceInternal(inputPort, clearValue: false, pulsePort: false); inputPort.IncomingConnection = null; if (!outputPort.Connections.Contains(inputPort)) { outputPort.Connections.Add(inputPort); } if (!inputPort.Connections.Contains(outputPort)) { inputPort.Connections.Add(outputPort); } EnsureWireVisual(outputPort, inputPort); outputPort.Owner?.PulsePort(outputPort); inputPort.Owner?.PulsePort(inputPort); if (broadcast && !suppressNetworkBroadcast) { ResolveNetworkSync()?.BroadcastConnectWire(new WorldGroupsCodec.WorldWireRecord { outputNodeId = ((outputPort.Owner != null) ? outputPort.Owner.NodeId : 0), outputPortName = outputPort.Name, inputNodeId = ((inputPort.Owner != null) ? inputPort.Owner.NodeId : 0), inputPortName = inputPort.Name }); } return true; } if (inputPort.IncomingConnection != null && inputPort.IncomingConnection != outputPort) { DisconnectInputPortInternal(inputPort, broadcast: false); } ClearGroupInputSourceInternal(inputPort, clearValue: false, pulsePort: false); if (!outputPort.Connections.Contains(inputPort)) { outputPort.Connections.Add(inputPort); } inputPort.IncomingConnection = outputPort; EnsureWireVisual(outputPort, inputPort); outputPort.Owner?.PulsePort(outputPort); inputPort.Owner?.PulsePort(inputPort); if (outputPort.Kind == PortKind.Value) { inputPort.Owner?.SetInputValueLocal(inputPort, outputPort.Value, pulsePort: true); FlashWire(outputPort, inputPort); } if (broadcast && !suppressNetworkBroadcast) { ResolveNetworkSync()?.BroadcastConnectWire(new WorldGroupsCodec.WorldWireRecord { outputNodeId = ((outputPort.Owner != null) ? outputPort.Owner.NodeId : 0), outputPortName = outputPort.Name, inputNodeId = ((inputPort.Owner != null) ? inputPort.Owner.NodeId : 0), inputPortName = inputPort.Name }); } return true; } private bool DisconnectPortsInternal(NodePort outputPort, NodePort inputPort, bool broadcast) { if (outputPort == null || inputPort == null) { return false; } bool num = outputPort.Connections.Remove(inputPort); if (outputPort.Kind == PortKind.Run) { inputPort.Connections.Remove(outputPort); if (inputPort.IncomingConnection == outputPort) { inputPort.IncomingConnection = null; } } else if (inputPort.IncomingConnection == outputPort) { inputPort.IncomingConnection = null; } RemoveWireVisual(outputPort, inputPort); if (num && broadcast && !suppressNetworkBroadcast) { NodeGraphPhotonSync nodeGraphPhotonSync = ResolveNetworkSync(); if ((object)nodeGraphPhotonSync == null) { return num; } nodeGraphPhotonSync.BroadcastDisconnectInput((inputPort.Owner != null) ? inputPort.Owner.NodeId : 0, inputPort.Name); } return num; } private void DisconnectInputPortInternal(NodePort inputPort, bool broadcast) { if (inputPort == null) { return; } if (inputPort.Kind == PortKind.Run) { if (inputPort.Connections.Count == 0) { return; } List list = new List(inputPort.Connections); for (int i = 0; i < list.Count; i++) { if (list[i] != null) { DisconnectPortsInternal(list[i], inputPort, broadcast: false); } } if (broadcast && !suppressNetworkBroadcast) { ResolveNetworkSync()?.BroadcastDisconnectInput((inputPort.Owner != null) ? inputPort.Owner.NodeId : 0, inputPort.Name); } } else if (inputPort.IncomingConnection != null) { NodePort incomingConnection = inputPort.IncomingConnection; DisconnectPortsInternal(incomingConnection, inputPort, broadcast); } } private void DisconnectOutputPortInternal(NodePort outputPort, bool broadcast) { if (outputPort != null && outputPort.Connections.Count != 0) { List list = new List(outputPort.Connections); for (int i = 0; i < list.Count; i++) { DisconnectPortsInternal(outputPort, list[i], broadcast: false); } if (broadcast && !suppressNetworkBroadcast) { ResolveNetworkSync()?.BroadcastDisconnectOutput((outputPort.Owner != null) ? outputPort.Owner.NodeId : 0, outputPort.Name); } } } private void PropagateOutputValue(NodePort outputPort, BSValue value, bool flashWire, bool pulseInputPorts) { for (int i = 0; i < outputPort.Connections.Count; i++) { NodePort nodePort = outputPort.Connections[i]; if (nodePort != null && !(nodePort.Owner == null)) { nodePort.Owner.SetInputValueLocal(nodePort, value, pulseInputPorts); if (flashWire) { FlashWire(outputPort, nodePort); } } } } private void ApplyNodeRecord(WorldGroupsCodec.WorldNodeRecord record, bool broadcast) { if (string.IsNullOrEmpty(record.definitionName)) { return; } NodeDefinition nodeDefinition = ResolveDefinition(record.definitionName); if (nodeDefinition == null) { Debug.LogWarning("NodeGraphManager could not resolve node definition '" + record.definitionName + "'."); return; } Node value; Node node = (nodesById.TryGetValue(record.nodeId, out value) ? value : null); bool flag = node != null && node.Definition != nodeDefinition; if (node == null) { node = SpawnNodeInternal(nodeDefinition, record.position, record.rotation, record.nodeId); } else { if (flag) { DisconnectAllForNode(node, broadcast: false); node.Definition = nodeDefinition; node.ReinitializeFromDefinition(); node.AssignGraph(this); } node.SetNodeId(record.nodeId); node.transform.SetPositionAndRotation(record.position, record.rotation); } if (node == null) { return; } List list = new List(); ApplyPortValues(node, record.inputValues, PortDirection.Input, null); ApplyPortValues(node, record.outputValues, PortDirection.Output, list); for (int i = 0; i < node.OutputPorts.Count; i++) { NodePort nodePort = node.OutputPorts[i]; if (nodePort != null && nodePort.Kind == PortKind.Value) { PropagateOutputValue(nodePort, nodePort.Value, flashWire: false, pulseInputPorts: false); if (list.Contains(nodePort)) { FlashAllConnections(nodePort); } } } if (broadcast && !suppressNetworkBroadcast) { ResolveNetworkSync()?.BroadcastUpsertNode(node); } } private void ApplyPortValues(Node node, WorldGroupsCodec.NodePortValueRecord[] values, PortDirection direction, List changedOutputPorts) { if (node == null || values == null) { return; } for (int i = 0; i < values.Length; i++) { WorldGroupsCodec.NodePortValueRecord nodePortValueRecord = values[i]; if (!node.TryGetPort(nodePortValueRecord.portName, direction, PortKind.Value, out var port)) { continue; } if (direction == PortDirection.Input) { node.SetInputValueLocal(port, nodePortValueRecord.value, pulsePort: false); continue; } if (!BSValueEquals(port.Value, nodePortValueRecord.value)) { changedOutputPorts?.Add(port); } port.Value = nodePortValueRecord.value.Normalize(); } } private void FlashAllConnections(NodePort outputPort) { if (outputPort == null) { return; } for (int i = 0; i < outputPort.Connections.Count; i++) { NodePort nodePort = outputPort.Connections[i]; if (nodePort != null) { FlashWire(outputPort, nodePort); } } } private bool ConnectGroupToInput(int groupId, Node inputNode, string inputPortName, bool broadcast) { if (groupId <= 0 || inputNode == null || string.IsNullOrEmpty(inputPortName)) { return false; } if (inputNode.TryGetPort(inputPortName, PortDirection.Input, PortKind.Value, out var port)) { return ConnectGroupToInputPort(groupId, port, broadcast); } return false; } private bool ConnectGroupToInputPort(int groupId, NodePort inputPort, bool broadcast) { if (groupId <= 0 || inputPort == null || inputPort.Owner == null || inputPort.Direction != PortDirection.Input || inputPort.Kind != PortKind.Value) { return false; } DisconnectInputPortInternal(inputPort, broadcast: false); ClearGroupInputSourceInternal(inputPort, clearValue: false, pulsePort: false); inputPort.SourceGroupId = groupId; EnsureGroupWireVisual(groupId, inputPort); inputPort.Owner.SetInputValueLocal(inputPort, BSValue.FromGroup(groupId), pulsePort: true); FlashGroupWire(groupId, inputPort); if (broadcast && !suppressNetworkBroadcast) { ResolveNetworkSync()?.BroadcastConnectWire(WorldGroupsCodec.CreateGroupRuntimeWireRecord(groupId, inputPort.Owner.NodeId, inputPort.Name)); } return true; } private bool ClearGroupInputSourceInternal(NodePort inputPort, bool clearValue, bool pulsePort) { if (inputPort == null || inputPort.Direction != PortDirection.Input || inputPort.Kind != PortKind.Value) { return false; } int sourceGroupId = inputPort.SourceGroupId; if (sourceGroupId <= 0) { return false; } inputPort.SourceGroupId = 0; RemoveGroupWireVisual(sourceGroupId, inputPort); if (clearValue && inputPort.Owner != null) { inputPort.Owner.SetInputValueLocal(inputPort, BSValue.None, pulsePort); } return true; } private void ApplyWireRecord(WorldGroupsCodec.WorldWireRecord record, bool broadcast) { Node value2; Node value3; if (WorldGroupsCodec.TryGetGroupRuntimeWireSourceId(record, out var groupId)) { if (!nodesById.TryGetValue(record.inputNodeId, out var value) || value == null) { pendingWireRecords.Add(record); } else { ConnectGroupToInput(groupId, value, record.inputPortName, broadcast); } } else if (!nodesById.TryGetValue(record.outputNodeId, out value2) || !nodesById.TryGetValue(record.inputNodeId, out value3) || value2 == null || value3 == null) { pendingWireRecords.Add(record); } else { Connect(value2, record.outputPortName, value3, record.inputPortName, broadcast); } } private void ResolvePendingWireRecords() { if (pendingWireRecords.Count == 0) { return; } for (int num = pendingWireRecords.Count - 1; num >= 0; num--) { WorldGroupsCodec.WorldWireRecord record = pendingWireRecords[num]; Node value2; Node value3; if (WorldGroupsCodec.TryGetGroupRuntimeWireSourceId(record, out var groupId)) { if (nodesById.TryGetValue(record.inputNodeId, out var value) && value != null) { ConnectGroupToInput(groupId, value, record.inputPortName, broadcast: false); pendingWireRecords.RemoveAt(num); } } else if (nodesById.TryGetValue(record.outputNodeId, out value2) && nodesById.TryGetValue(record.inputNodeId, out value3) && value2 != null && value3 != null) { Connect(value2, record.outputPortName, value3, record.inputPortName, broadcast: false); pendingWireRecords.RemoveAt(num); } } } private static bool TryResolveSavedGroupWireRecord(WorldGroupsCodec.WorldWireRecord record, Dictionary savedGroupIndexToRuntimeId, out WorldGroupsCodec.WorldWireRecord resolvedRecord) { resolvedRecord = record; if (!WorldGroupsCodec.TryGetGroupSavedWireIndex(record, out var savedGroupIndex)) { return true; } if (savedGroupIndexToRuntimeId == null || !savedGroupIndexToRuntimeId.TryGetValue(savedGroupIndex, out var value) || value <= 0) { return false; } resolvedRecord = WorldGroupsCodec.CreateGroupRuntimeWireRecord(value, record.inputNodeId, record.inputPortName); return true; } private WorldGroupsCodec.WorldNodeRecord BuildNodeRecord(Node node) { return new WorldGroupsCodec.WorldNodeRecord { nodeId = node.NodeId, definitionName = ((node.Definition != null) ? node.Definition.name : string.Empty), position = node.transform.position, rotation = node.transform.rotation, inputValues = BuildPortValueRecords(node.InputPorts), outputValues = BuildPortValueRecords(node.OutputPorts) }; } private static WorldGroupsCodec.NodePortValueRecord[] BuildPortValueRecords(List ports) { List list = new List(); if (ports == null) { return list.ToArray(); } for (int i = 0; i < ports.Count; i++) { NodePort nodePort = ports[i]; if (nodePort != null && nodePort.Kind == PortKind.Value) { list.Add(new WorldGroupsCodec.NodePortValueRecord { portName = nodePort.Name, value = nodePort.Value.Normalize() }); } } return list.ToArray(); } private Node SpawnNodeInternal(NodeDefinition definition, Vector3 position, Quaternion rotation, int forcedNodeId) { if (definition == null) { return null; } EnsureNodePrefabLoaded(); if (nodePrefab == null) { return null; } GameObject gameObject = UnityEngine.Object.Instantiate(nodePrefab, position, rotation); Node component = gameObject.GetComponent(); if (component == null) { UnityEngine.Object.Destroy(gameObject); return null; } component.Definition = definition; component.ReinitializeFromDefinition(); component.SetNodeId((forcedNodeId > 0) ? forcedNodeId : AllocateNodeId()); component.AssignGraph(this); component.SetVisibleInEditMode(CanEdit()); CacheDefinition(definition); nodesById[component.NodeId] = component; nextNodeId = Mathf.Max(nextNodeId, component.NodeId + 1); return component; } private void EnsureNodePrefabLoaded() { if (nodePrefab == null) { nodePrefab = Resources.Load("Node"); } } private void CacheDefinition(NodeDefinition definition) { if (definition != null && !definitionsByName.ContainsKey(definition.name)) { definitionsByName.Add(definition.name, definition); } } private void CachePreloadedDefinitions() { if (preloadedDefinitions != null && preloadedDefinitions.Count != 0) { for (int i = 0; i < preloadedDefinitions.Count; i++) { CacheDefinition(preloadedDefinitions[i]); } } } private void CacheLoadedDefinitions() { CachePreloadedDefinitions(); NodeDefinitionCatalog nodeDefinitionCatalog = Resources.Load("NodeDefinitionCatalog"); if (nodeDefinitionCatalog != null && nodeDefinitionCatalog.Definitions != null) { for (int i = 0; i < nodeDefinitionCatalog.Definitions.Count; i++) { CacheDefinition(nodeDefinitionCatalog.Definitions[i]); } } NodeDefinition[] array = Resources.FindObjectsOfTypeAll(); for (int j = 0; j < array.Length; j++) { CacheDefinition(array[j]); } } private NodeDefinition ResolveDefinition(string definitionName) { if (string.IsNullOrEmpty(definitionName)) { return null; } if (definitionsByName.TryGetValue(definitionName, out var value) && value != null) { return value; } CacheLoadedDefinitions(); if (definitionsByName.TryGetValue(definitionName, out value) && value != null) { return value; } foreach (KeyValuePair item in nodesById) { if (item.Value != null && item.Value.Definition != null && item.Value.Definition.name == definitionName) { CacheDefinition(item.Value.Definition); return item.Value.Definition; } } return null; } private int AllocateNodeId() { while (nodesById.ContainsKey(nextNodeId)) { nextNodeId++; } return nextNodeId++; } private void EnsureLineResources() { if (lineMaterial == null) { Shader shader = Shader.Find("Sprites/Default"); if (shader != null) { lineMaterial = new Material(shader); } } if (previewWireRenderer == null) { previewWireRenderer = CreateLineRenderer("Node Preview Wire", 0.018f); } if (aimRayRenderer == null) { aimRayRenderer = CreateLineRenderer("Node Aim Ray", 0.0085f); } } private LineRenderer CreateLineRenderer(string name, float width) { GameObject obj = new GameObject(name); obj.transform.SetParent(base.transform, worldPositionStays: false); LineRenderer lineRenderer = obj.AddComponent(); lineRenderer.positionCount = 5; lineRenderer.loop = false; lineRenderer.useWorldSpace = true; lineRenderer.textureMode = LineTextureMode.Stretch; lineRenderer.numCapVertices = 4; lineRenderer.alignment = LineAlignment.View; lineRenderer.widthMultiplier = width; lineRenderer.material = lineMaterial; lineRenderer.enabled = false; return lineRenderer; } private void EnsureWireVisual(NodePort outputPort, NodePort inputPort) { string wireKey = GetWireKey(outputPort, inputPort); if (!wireVisuals.ContainsKey(wireKey)) { WireVisual wireVisual = new WireVisual { key = wireKey, output = outputPort, input = inputPort, Kind = (outputPort?.Kind ?? PortKind.Value), renderer = CreateLineRenderer("Wire " + wireKey, 0.015f) }; wireVisuals.Add(wireKey, wireVisual); UpdateWireVisual(wireVisual); } } private void EnsureGroupWireVisual(int groupId, NodePort inputPort) { if (groupId > 0 && inputPort != null) { string groupWireKey = GetGroupWireKey(groupId, inputPort); if (!wireVisuals.ContainsKey(groupWireKey)) { WireVisual wireVisual = new WireVisual { key = groupWireKey, sourceGroupId = groupId, input = inputPort, Kind = PortKind.Value, renderer = CreateLineRenderer("Wire " + groupWireKey, 0.015f) }; wireVisuals.Add(groupWireKey, wireVisual); UpdateWireVisual(wireVisual); } } } private void RemoveWireVisual(NodePort outputPort, NodePort inputPort) { string wireKey = GetWireKey(outputPort, inputPort); if (wireVisuals.TryGetValue(wireKey, out var value)) { DestroyWireVisualRenderer(value); wireVisuals.Remove(wireKey); } } private void RemoveGroupWireVisual(int groupId, NodePort inputPort) { string groupWireKey = GetGroupWireKey(groupId, inputPort); if (wireVisuals.TryGetValue(groupWireKey, out var value)) { DestroyWireVisualRenderer(value); wireVisuals.Remove(groupWireKey); } } private void FlashWire(NodePort outputPort, NodePort inputPort) { string wireKey = GetWireKey(outputPort, inputPort); if (wireVisuals.TryGetValue(wireKey, out var value)) { value.flashUntil = Time.unscaledTime + 0.18f; } } private void FlashGroupWire(int groupId, NodePort inputPort) { string groupWireKey = GetGroupWireKey(groupId, inputPort); if (wireVisuals.TryGetValue(groupWireKey, out var value)) { value.flashUntil = Time.unscaledTime + 0.18f; } } private void UpdateWireVisuals() { wireCleanupBuffer.Clear(); foreach (KeyValuePair wireVisual in wireVisuals) { WireVisual value = wireVisual.Value; bool flag = value != null && value.sourceGroupId > 0; if (value == null || value.renderer == null || value.input == null || value.input.Owner == null || (!flag && (value.output == null || value.output.Owner == null))) { wireCleanupBuffer.Add(wireVisual.Key); } else { UpdateWireVisual(value); } } for (int i = 0; i < wireCleanupBuffer.Count; i++) { if (wireVisuals.TryGetValue(wireCleanupBuffer[i], out var value2)) { DestroyWireVisualRenderer(value2); wireVisuals.Remove(wireCleanupBuffer[i]); } } } private void UpdateWireVisual(WireVisual visual) { if (visual == null || visual.renderer == null || visual.input == null || visual.input.Owner == null) { return; } Vector3 portWorldPosition = visual.input.Owner.GetPortWorldPosition(visual.input); Vector3 normalized = visual.input.Owner.GetPortWorldDirection(visual.input).normalized; Color color; if (visual.sourceGroupId > 0) { if (!TryResolveGroupSource(visual.sourceGroupId, out var group) || group == null) { visual.renderer.enabled = false; return; } Bounds worldBounds = group.GetWorldBounds(); Vector3 vector = worldBounds.ClosestPoint(portWorldPosition); if ((vector - portWorldPosition).sqrMagnitude < 0.0001f) { vector = worldBounds.center; } Vector3 startDirection = (((portWorldPosition - vector).sqrMagnitude > 0.0001f) ? (portWorldPosition - vector).normalized : Vector3.up); SetCurvedLinePositions(visual.renderer, vector, startDirection, portWorldPosition, normalized, 12); color = GroupWireColor; } else { if (visual.output == null || visual.output.Owner == null) { visual.renderer.enabled = false; return; } Vector3 portWorldPosition2 = visual.output.Owner.GetPortWorldPosition(visual.output); Vector3 normalized2 = visual.output.Owner.GetPortWorldDirection(visual.output).normalized; SetCurvedLinePositions(visual.renderer, portWorldPosition2, normalized2, portWorldPosition, normalized, 12); color = ((visual.Kind != PortKind.Run) ? (visual.output.Value.Normalize().Kind switch { BSValueKind.Bool => BoolWireColor, BSValueKind.Number => NumberWireColor, BSValueKind.Text => TextWireColor, BSValueKind.Player => PlayerWireColor, BSValueKind.Group => GroupWireColor, _ => NoneWireColor, }) : RunWireColor); } if (visual.flashUntil > Time.unscaledTime) { float t = Mathf.Clamp01((visual.flashUntil - Time.unscaledTime) / 0.18f); color = Color.Lerp(color, WireFlashColor, t); visual.renderer.widthMultiplier = Mathf.Lerp(0.015f, 0.024f, t); } else { visual.renderer.widthMultiplier = 0.015f; } visual.renderer.startColor = color; visual.renderer.endColor = color; visual.renderer.enabled = editVisibilityApplied; } private void DestroyWireVisualRenderer(WireVisual visual) { if (visual != null && !(visual.renderer == null)) { if (Application.isPlaying) { UnityEngine.Object.Destroy(visual.renderer.gameObject); } else { UnityEngine.Object.DestroyImmediate(visual.renderer.gameObject); } } } private void UpdateTransientLines() { bool num = CanEdit(); bool flag = UseXRInput(); Ray ray; if (!num) { if (previewWireRenderer != null) { previewWireRenderer.enabled = false; } if (aimRayRenderer != null) { aimRayRenderer.enabled = false; } } else if (TryGetLeftRay(out ray)) { Vector3 vector = GetPreviewRayEndpoint(ray); if (hoveredPort != null && hoveredPort.Owner != null) { vector = hoveredPort.Owner.GetPortWorldPosition(hoveredPort); } if (aimRayRenderer != null) { aimRayRenderer.enabled = flag; if (flag) { aimRayRenderer.positionCount = 2; aimRayRenderer.SetPosition(0, ray.origin); aimRayRenderer.SetPosition(1, vector); aimRayRenderer.startColor = AimRayColor; aimRayRenderer.endColor = AimRayColor; } } if (activeWire != null && previewWireRenderer != null) { previewWireRenderer.enabled = true; Vector3 end = ((hoveredPort != null && IsWireTargetCompatible(hoveredPort)) ? hoveredPort.Owner.GetPortWorldPosition(hoveredPort) : vector); Color color = ((activeWire.Kind == PortKind.Run) ? RunWireColor : ((activeWire.startGroup != null) ? GroupWireColor : ((activeWire.startPort == null) ? NoneWireColor : (activeWire.startPort.Value.Normalize().Kind switch { BSValueKind.Bool => BoolWireColor, BSValueKind.Number => NumberWireColor, BSValueKind.Text => TextWireColor, BSValueKind.Player => PlayerWireColor, BSValueKind.Group => GroupWireColor, _ => NoneWireColor, })))); previewWireRenderer.startColor = color; previewWireRenderer.endColor = color; SetCurvedLinePositions(previewWireRenderer, activeWire.startWorldPosition, ray.direction, end, -ray.direction, 30); } else if (previewWireRenderer != null) { previewWireRenderer.enabled = false; } } else { if (previewWireRenderer != null) { previewWireRenderer.enabled = false; } if (aimRayRenderer != null) { aimRayRenderer.enabled = false; } } } private Vector3 GetPreviewRayEndpoint(Ray ray) { if (Physics.Raycast(ray, out var hitInfo, wireRayDistance, wireRaycastMask, QueryTriggerInteraction.Ignore)) { return hitInfo.point; } return ray.origin + ray.direction.normalized * wireRayDistance; } private void SetCurvedLinePositions(LineRenderer renderer, Vector3 start, Vector3 startDirection, Vector3 end, Vector3 endDirection, int vertexCount) { if (!(renderer == null)) { float magnitude = (end - start).magnitude; Vector3 vector = Vector3.up * Mathf.Clamp(magnitude * 0.12f, 0.04f, 0.18f); float num = Mathf.Clamp(magnitude * 0.22f, 0.06f, 0.28f); Vector3 p = start + startDirection.normalized * num + vector; Vector3 p2 = end + endDirection.normalized * num + vector; int num2 = (renderer.positionCount = Mathf.Max(2, vertexCount)); for (int i = 0; i < num2; i++) { float t = ((num2 == 1) ? 1f : ((float)i / ((float)num2 - 1f))); renderer.SetPosition(i, EvaluateCubicBezier(start, p, p2, end, t)); } } } private static Vector3 EvaluateCubicBezier(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) { float num = 1f - t; float num2 = num * num; float num3 = t * t; return num2 * num * p0 + 3f * num2 * t * p1 + 3f * num * num3 * p2 + num3 * t * p3; } private bool TryGetLeftRay(out Ray ray) { ray = default(Ray); if (UseXRInput()) { if (leftHandAnchor == null) { return false; } ray = new Ray(leftHandAnchor.position, leftHandAnchor.forward); return true; } Camera main = Camera.main; if (main == null) { return false; } ray = new Ray(main.transform.position, main.transform.forward); return true; } private bool TryGetRightRay(out Ray ray) { ray = default(Ray); if (UseXRInput()) { if (rightHandAnchor == null) { return false; } ray = new Ray(rightHandAnchor.position, rightHandAnchor.forward); return true; } Camera main = Camera.main; if (main == null) { return false; } ray = new Ray(main.transform.position, main.transform.forward); return true; } private Node TryPickNode(Ray ray) { Node result = null; float num = float.PositiveInfinity; foreach (KeyValuePair item in nodesById) { Node value = item.Value; if (value == null) { continue; } Vector3 position = value.transform.position; float num2 = Vector3.Dot(position - ray.origin, ray.direction); if (!(num2 < 0f) && !(num2 > wireRayDistance)) { Vector3 vector = ray.origin + ray.direction * num2; if (!((position - vector).sqrMagnitude > nodePickRadius * nodePickRadius) && num2 < num) { num = num2; result = value; } } } return result; } private NodePort TryPickPort(Ray ray, float maxDistance, PortKind? requiredKind, PortDirection? requiredDirection) { NodePort nodePort = null; float bestDistance = float.PositiveInfinity; foreach (KeyValuePair item in nodesById) { Node value = item.Value; if (!(value == null)) { nodePort = EvaluatePorts(value.InputPorts, ray, maxDistance, requiredKind, requiredDirection, nodePort, ref bestDistance); nodePort = EvaluatePorts(value.OutputPorts, ray, maxDistance, requiredKind, requiredDirection, nodePort, ref bestDistance); } } return nodePort; } private NodePort EvaluatePorts(List ports, Ray ray, float maxDistance, PortKind? requiredKind, PortDirection? requiredDirection, NodePort bestPort, ref float bestDistance) { for (int i = 0; i < ports.Count; i++) { NodePort nodePort = ports[i]; if (nodePort == null || nodePort.Owner == null || nodePort.VisualTransform == null || (requiredKind.HasValue && nodePort.Kind != requiredKind.Value) || (requiredDirection.HasValue && nodePort.Direction != requiredDirection.Value) || !IsWireTargetCompatible(nodePort)) { continue; } Vector3 portWorldPosition = nodePort.Owner.GetPortWorldPosition(nodePort); float num = Vector3.Dot(portWorldPosition - ray.origin, ray.direction); if (!(num < 0f) && !(num > maxDistance) && !(num > bestDistance)) { Vector3 vector = ray.origin + ray.direction * num; if (!((portWorldPosition - vector).sqrMagnitude > portPickRadius * portPickRadius)) { bestDistance = num; bestPort = nodePort; } } } return bestPort; } private VoxelGroup TryPickGroup(Ray ray) { VoxelGroup voxelGroup = TryPickGroupFromList(ray, (VRGroupSelector.Instance != null && VRGroupSelector.Instance.HasSelection) ? VRGroupSelector.Instance.GetSelectedGroupsSnapshot() : null); if (voxelGroup != null) { return voxelGroup; } if (groupManager == null) { return null; } groupManager.GetAllGroups(groupPickBuffer); return TryPickGroupFromList(ray, groupPickBuffer); } private VoxelGroup TryPickGroupFromList(Ray ray, List groups) { if (groups == null || groups.Count == 0) { return null; } VoxelGroup result = null; float num = float.PositiveInfinity; for (int i = 0; i < groups.Count; i++) { VoxelGroup voxelGroup = groups[i]; if (!(voxelGroup == null) && voxelGroup.GetWorldBounds().IntersectRay(ray, out var distance) && distance < num && distance <= wireRayDistance) { num = distance; result = voxelGroup; } } return result; } private bool TryResolveDirectionalPorts(NodePort firstPort, NodePort secondPort, out NodePort outputPort, out NodePort inputPort) { outputPort = null; inputPort = null; if (firstPort == null || secondPort == null || firstPort.Kind != secondPort.Kind) { return false; } if (firstPort.Direction == PortDirection.Output && secondPort.Direction == PortDirection.Input) { outputPort = firstPort; inputPort = secondPort; return true; } if (firstPort.Direction == PortDirection.Input && secondPort.Direction == PortDirection.Output) { outputPort = secondPort; inputPort = firstPort; return true; } return false; } private bool IsWireTargetCompatible(NodePort port) { if (port == null) { return false; } if (activeWire == null) { return true; } if (activeWire.startGroup != null) { if (port.Direction == PortDirection.Input) { return port.Kind == PortKind.Value; } return false; } if (activeWire.startPort == null) { return false; } NodePort outputPort; NodePort inputPort; return TryResolveDirectionalPorts(activeWire.startPort, port, out outputPort, out inputPort); } private PortKind? GetExpectedWireTargetKind() { if (activeWire == null) { return null; } return activeWire.Kind; } private PortDirection? GetExpectedWireTargetDirection() { if (activeWire == null) { return null; } if (activeWire.startGroup != null) { return PortDirection.Input; } if (activeWire.startPort == null) { return null; } return (activeWire.startPort.Direction != PortDirection.Output) ? PortDirection.Output : PortDirection.Input; } private bool TryGetSelectionBounds(out Bounds bounds) { bounds = default(Bounds); bool flag = false; foreach (Node selectedNode in selectedNodes) { if (!(selectedNode == null)) { Bounds worldBounds = selectedNode.GetWorldBounds(); if (!flag) { bounds = worldBounds; flag = true; } else { bounds.Encapsulate(worldBounds); } } } return flag; } private bool TryGetGrabReferencePose(out Vector3 worldPosition, out Quaternion worldRotation) { if (UseXRInput()) { if (rightHandAnchor != null) { worldPosition = rightHandAnchor.position; worldRotation = rightHandAnchor.rotation; return true; } if (trackingOrigin != null && rightHandDevice.TryGetFeatureValue(CommonUsages.devicePosition, out var value) && rightHandDevice.TryGetFeatureValue(CommonUsages.deviceRotation, out var value2)) { worldPosition = trackingOrigin.TransformPoint(value); worldRotation = trackingOrigin.rotation * value2; return true; } } Camera main = Camera.main; if (main != null) { worldPosition = main.transform.position; worldRotation = main.transform.rotation; return true; } worldPosition = base.transform.position; worldRotation = base.transform.rotation; return false; } private static float ProjectBoundsAlongRay(Bounds bounds, Ray ray) { return Vector3.Dot(bounds.center - ray.origin, ray.direction); } private static Vector3 SnapVector(Vector3 value, float snap) { if (snap <= 0.0001f) { return value; } return new Vector3(Mathf.Round(value.x / snap) * snap, Mathf.Round(value.y / snap) * snap, Mathf.Round(value.z / snap) * snap); } private static Quaternion SnapRotation(Quaternion rotation, float snapDegrees) { if (snapDegrees <= 0.001f) { return rotation; } Vector3 eulerAngles = rotation.eulerAngles; eulerAngles.x = Mathf.Round(eulerAngles.x / snapDegrees) * snapDegrees; eulerAngles.y = Mathf.Round(eulerAngles.y / snapDegrees) * snapDegrees; eulerAngles.z = Mathf.Round(eulerAngles.z / snapDegrees) * snapDegrees; return Quaternion.Euler(eulerAngles); } private string GetWireKey(NodePort outputPort, NodePort inputPort) { int num = ((outputPort != null && outputPort.Owner != null) ? outputPort.Owner.NodeId : 0); int num2 = ((inputPort != null && inputPort.Owner != null) ? inputPort.Owner.NodeId : 0); return $"{num}:{outputPort?.Name}->{num2}:{inputPort?.Name}"; } private string GetGroupWireKey(int groupId, NodePort inputPort) { int num = ((inputPort != null && inputPort.Owner != null) ? inputPort.Owner.NodeId : 0); return $"group:{groupId}->{num}:{inputPort?.Name}"; } private bool TryResolveGroupSource(int groupId, out VoxelGroup group) { group = null; if (groupManager == null || groupId <= 0 || groupId > 65535) { return false; } return groupManager.TryGetGroup((ushort)groupId, out group); } private bool CanEdit() { if (groupManager != null && groupManager.CreatorMode) { return !groupManager.EditingLocked; } return false; } private bool UseXRInput() { if (brush != null) { return brush.UsingXRInput; } if (!InputDevices.GetDeviceAtXRNode(XRNode.Head).isValid && !InputDevices.GetDeviceAtXRNode(XRNode.LeftHand).isValid) { return InputDevices.GetDeviceAtXRNode(XRNode.RightHand).isValid; } return true; } private NodeGraphPhotonSync ResolveNetworkSync() { if (networkSync == null) { networkSync = GetComponent(); } return networkSync; } private static int CompareNodesByHierarchyPath(Node a, Node b) { if ((object)a == b) { return 0; } if (a == null) { return -1; } if (b == null) { return 1; } return string.CompareOrdinal(GetHierarchyPath(a.transform), GetHierarchyPath(b.transform)); } private static string GetHierarchyPath(Transform target) { if (target == null) { return string.Empty; } List list = new List(); Transform transform = target; while (transform != null) { list.Add($"{transform.GetSiblingIndex():D4}_{transform.name}"); transform = transform.parent; } list.Reverse(); return string.Join("/", list); } private static bool ReadBool(InputDevice device, InputFeatureUsage usage) { bool value = default(bool); return device.isValid && device.TryGetFeatureValue(usage, out value) && value; } private static float ReadFloat(InputDevice device, InputFeatureUsage usage) { if (!device.isValid || !device.TryGetFeatureValue(usage, out var value)) { return 0f; } return value; } private bool ReadGrabHeld(InputDevice device) { return ReadFloat(device, CommonUsages.grip) >= rightGripThreshold; } private static bool BSValueEquals(BSValue a, BSValue b) { a = a.Normalize(); b = b.Normalize(); if (a.Kind == b.Kind && Math.Abs(a.Number - b.Number) <= 0.0001 && string.Equals(a.Text ?? string.Empty, b.Text ?? string.Empty, StringComparison.Ordinal) && a.PlayerId == b.PlayerId) { return a.GroupId == b.GroupId; } return false; } }