public void NotifyStartEndNode(ushort segmentId) { // notify observers of start node and end node (e.g. for separate traffic lights) ushort startNodeId = Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_startNode; ushort endNodeId = Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_endNode; if (startNodeId != 0) { NodeGeometry.Get(startNodeId).NotifyObservers(); } if (endNodeId != 0) { NodeGeometry.Get(endNodeId).NotifyObservers(); } }
public void NotifyStartEndNode(ushort segmentId) { // TODO this is hacky. Instead of notifying geometry observers we should add a seperate notification mechanic // notify observers of start node and end node (e.g. for separate traffic lights) ushort startNodeId = Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_startNode; ushort endNodeId = Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_endNode; if (startNodeId != 0) { Constants.ManagerFactory.GeometryManager.MarkAsUpdated(NodeGeometry.Get(startNodeId)); } if (endNodeId != 0) { Constants.ManagerFactory.GeometryManager.MarkAsUpdated(NodeGeometry.Get(endNodeId)); } }
public bool SetPrioritySign(ushort segmentId, bool startNode, PriorityType sign) { SegmentGeometry segGeo = SegmentGeometry.Get(segmentId); if (segGeo == null) { Log.Error($"PrioritySignsTool.SetPrioritySign: No geometry information available for segment {segmentId}"); return(false); } ushort nodeId = segGeo.GetNodeId(startNode); // check for restrictions if (!MayNodeHavePrioritySigns(nodeId)) { Log._Debug($"PrioritySignsTool.SetPrioritySign: MayNodeHavePrioritySigns({nodeId})=false"); return(false); } bool success = TrafficPriorityManager.Instance.SetPrioritySign(segmentId, startNode, sign); Log._Debug($"PrioritySignsTool.SetPrioritySign: SetPrioritySign({segmentId}, {startNode}, {sign})={success}"); if (success && (sign == PriorityType.Stop || sign == PriorityType.Yield)) { // make all undefined segments a main road Log._Debug($"PrioritySignsTool.SetPrioritySign: flagging remaining segments at node {nodeId} as main road."); NodeGeometry nodeGeo = NodeGeometry.Get(nodeId); foreach (SegmentEndGeometry endGeo in nodeGeo.SegmentEndGeometries) { if (endGeo == null) { continue; } if (endGeo.SegmentId == segmentId) { continue; } if (TrafficPriorityManager.Instance.GetPrioritySign(endGeo.SegmentId, endGeo.StartNode) == PriorityType.None) { Log._Debug($"PrioritySignsTool.SetPrioritySign: setting main priority sign for segment {endGeo.SegmentId} @ {nodeId}"); TrafficPriorityManager.Instance.SetPrioritySign(endGeo.SegmentId, endGeo.StartNode, PriorityType.Main); } } } return(success); }
/// <summary> /// Removes custom traffic lights at the given node /// </summary> /// <param name="nodeId"></param> public void RemoveNodeLights(ushort nodeId) { NodeGeometry nodeGeo = NodeGeometry.Get(nodeId); /*if (!nodeGeo.IsValid()) * return;*/ foreach (SegmentEndGeometry endGeo in nodeGeo.SegmentEndGeometries) { if (endGeo == null) { continue; } RemoveSegmentLight(endGeo.SegmentId, endGeo.StartNode); } }
/// <summary> /// Add custom traffic lights at the given node /// </summary> /// <param name="nodeId"></param> public void AddNodeLights(ushort nodeId) { NodeGeometry nodeGeo = NodeGeometry.Get(nodeId); if (!nodeGeo.IsValid()) { return; } foreach (SegmentEndGeometry endGeo in nodeGeo.SegmentEndGeometries) { if (endGeo == null) { continue; } AddSegmentLights(endGeo.SegmentId, endGeo.StartNode); } }
//private bool defaultPedestrianCrossingAllowed; public void UpdateDefaults(SegmentEndGeometry segmentEndGeometry) { NodeGeometry nodeGeo = NodeGeometry.Get(segmentEndGeometry.NodeId()); bool newDefaultEnterWhenBlockedAllowed = false; NetNode.Flags _nodeFlags = NetNode.Flags.None; Constants.ServiceFactory.NetService.ProcessNode(segmentEndGeometry.NodeId(), delegate(ushort nodeId, ref NetNode node) { _nodeFlags = node.m_flags; int numOutgoing = 0; int numIncoming = 0; node.CountLanes(nodeId, 0, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, true, ref numOutgoing, ref numIncoming); newDefaultEnterWhenBlockedAllowed = numOutgoing == 1 || numIncoming == 1; return(true); }); defaultEnterWhenBlockedAllowed = newDefaultEnterWhenBlockedAllowed; //Log._Debug($"SegmentEndFlags.UpdateDefaults: this={this} _nodeFlags={_nodeFlags} defaultEnterWhenBlockedAllowed={defaultEnterWhenBlockedAllowed}"); }
/// <summary> /// Add custom traffic lights at the given node /// </summary> /// <param name="nodeId"></param> public void AddNodeLights(ushort nodeId) { NodeGeometry nodeGeo = NodeGeometry.Get(nodeId); if (!nodeGeo.IsValid()) { return; } foreach (SegmentEndGeometry endGeo in nodeGeo.SegmentEndGeometries) { if (endGeo == null) { continue; } AddSegmentLights(endGeo.SegmentId, endGeo.StartNode); DebugOutputPanel.AddMessage(PluginManager.MessageType.Message, "Adding to Queue: " + nodeId); NetworkInterface.Network.UpdateSelectedIds(nodeId); } }
/// <summary> /// Recalculates lane arrows based on present lane connections. /// </summary> /// <param name="laneId"></param> /// <param name="nodeId"></param> private void RecalculateLaneArrows(uint laneId, ushort nodeId, bool startNode) { #if DEBUGCONN Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}) called"); #endif if (!Options.laneConnectorEnabled) { return; } if (!Flags.mayHaveLaneArrows(laneId, startNode)) { #if DEBUGCONN Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): lane {laneId}, startNode? {startNode} must not have lane arrows"); #endif return; } if (!HasConnections(laneId, startNode)) { #if DEBUGCONN Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): lane {laneId} does not have outgoing connections"); #endif return; } if (nodeId == 0) { #if DEBUGCONN Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): invalid node"); #endif return; } Flags.LaneArrows arrows = Flags.LaneArrows.None; NetManager netManager = Singleton <NetManager> .instance; ushort segmentId = netManager.m_lanes.m_buffer[laneId].m_segment; if (segmentId == 0) { #if DEBUGCONN Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): invalid segment"); #endif return; } #if DEBUGCONN Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): startNode? {startNode}"); #endif NodeGeometry nodeGeo = NodeGeometry.Get(nodeId); if (!nodeGeo.IsValid()) { #if DEBUGCONN Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): invalid node geometry"); #endif return; } SegmentGeometry segmentGeo = SegmentGeometry.Get(segmentId); if (segmentGeo == null) { #if DEBUGCONN Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): invalid segment geometry"); #endif return; } ushort[] connectedSegmentIds = segmentGeo.GetConnectedSegments(startNode); if (connectedSegmentIds == null) { #if DEBUGCONN Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): connectedSegmentIds is null"); #endif return; } ushort[] allSegmentIds = new ushort[connectedSegmentIds.Length + 1]; allSegmentIds[0] = segmentId; Array.Copy(connectedSegmentIds, 0, allSegmentIds, 1, connectedSegmentIds.Length); foreach (ushort otherSegmentId in allSegmentIds) { if (otherSegmentId == 0) { continue; } ArrowDirection dir = segmentGeo.GetDirection(otherSegmentId, startNode); #if DEBUGCONN Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {connectedSegmentId}. dir={dir}"); #endif // check if arrow has already been set for this direction switch (dir) { case ArrowDirection.Turn: if (Constants.ServiceFactory.SimulationService.LeftHandDrive) { if ((arrows & Flags.LaneArrows.Right) != Flags.LaneArrows.None) { continue; } } else { if ((arrows & Flags.LaneArrows.Left) != Flags.LaneArrows.None) { continue; } } break; case ArrowDirection.Forward: if ((arrows & Flags.LaneArrows.Forward) != Flags.LaneArrows.None) { continue; } break; case ArrowDirection.Left: if ((arrows & Flags.LaneArrows.Left) != Flags.LaneArrows.None) { continue; } break; case ArrowDirection.Right: if ((arrows & Flags.LaneArrows.Right) != Flags.LaneArrows.None) { continue; } break; default: continue; } #if DEBUGCONN Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {connectedSegmentId}: need to determine arrows"); #endif bool addArrow = false; uint curLaneId = netManager.m_segments.m_buffer[otherSegmentId].m_lanes; while (curLaneId != 0) { #if DEBUGCONN Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {connectedSegmentId}: checking lane {curLaneId}"); #endif if (AreLanesConnected(laneId, curLaneId, startNode)) { #if DEBUGCONN Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {connectedSegmentId}: checking lane {curLaneId}: lanes are connected"); #endif addArrow = true; break; } curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; } #if DEBUGCONN Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {connectedSegmentId}: finished processing lanes. addArrow={addArrow} arrows (before)={arrows}"); #endif if (addArrow) { switch (dir) { case ArrowDirection.Turn: if (Constants.ServiceFactory.SimulationService.LeftHandDrive) { arrows |= Flags.LaneArrows.Right; } else { arrows |= Flags.LaneArrows.Left; } break; case ArrowDirection.Forward: arrows |= Flags.LaneArrows.Forward; break; case ArrowDirection.Left: arrows |= Flags.LaneArrows.Left; break; case ArrowDirection.Right: arrows |= Flags.LaneArrows.Right; break; default: continue; } #if DEBUGCONN Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {connectedSegmentId}: arrows={arrows}"); #endif } } #if DEBUGCONN Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): setting lane arrows to {arrows}"); #endif LaneArrowManager.Instance.SetLaneArrows(laneId, arrows, true); }
public override void OnToolGUI(Event e) { var hoveredSegment = false; if (SelectedNodeId != 0) { CustomSegmentLightsManager customTrafficLightsManager = CustomSegmentLightsManager.Instance; TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; var nodeSimulation = tlsMan.GetNodeSimulation(SelectedNodeId); if (nodeSimulation == null || !nodeSimulation.IsManualLight()) { return; } nodeSimulation.housekeeping(); /*if (Singleton<NetManager>.instance.m_nodes.m_buffer[SelectedNode].CountSegments() == 2) { * _guiManualTrafficLightsCrosswalk(ref Singleton<NetManager>.instance.m_nodes.m_buffer[SelectedNode]); * return; * }*/// TODO check NodeGeometry nodeGeometry = NodeGeometry.Get(SelectedNodeId); foreach (SegmentEndGeometry end in nodeGeometry.SegmentEndGeometries) { if (end == null) { continue; } var position = CalculateNodePositionForSegment(Singleton <NetManager> .instance.m_nodes.m_buffer[SelectedNodeId], ref Singleton <NetManager> .instance.m_segments.m_buffer[end.SegmentId]); var segmentLights = customTrafficLightsManager.GetSegmentLights(end.SegmentId, end.StartNode, false); if (segmentLights == null) { continue; } var screenPos = Camera.main.WorldToScreenPoint(position); screenPos.y = Screen.height - screenPos.y; if (screenPos.z < 0) { continue; } var diff = position - Camera.main.transform.position; var zoom = 1.0f / diff.magnitude * 100f; // original / 2.5 var lightWidth = 41f * zoom; var lightHeight = 97f * zoom; var pedestrianWidth = 36f * zoom; var pedestrianHeight = 61f * zoom; // SWITCH MODE BUTTON var modeWidth = 41f * zoom; var modeHeight = 38f * zoom; var guiColor = GUI.color; if (segmentLights.PedestrianLightState != null) { // pedestrian light // SWITCH MANUAL PEDESTRIAN LIGHT BUTTON hoveredSegment = RenderManualPedestrianLightSwitch(zoom, end.SegmentId, screenPos, lightWidth, segmentLights, hoveredSegment); // SWITCH PEDESTRIAN LIGHT guiColor.a = _hoveredButton[0] == end.SegmentId && _hoveredButton[1] == 2 && segmentLights.ManualPedestrianMode ? 0.92f : 0.6f; GUI.color = guiColor; var myRect3 = new Rect(screenPos.x - pedestrianWidth / 2 - lightWidth + 5f * zoom, screenPos.y - pedestrianHeight / 2 + 22f * zoom, pedestrianWidth, pedestrianHeight); switch (segmentLights.PedestrianLightState) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(myRect3, TrafficLightToolTextureResources.PedestrianGreenLightTexture2D); break; case RoadBaseAI.TrafficLightState.Red: default: GUI.DrawTexture(myRect3, TrafficLightToolTextureResources.PedestrianRedLightTexture2D); break; } hoveredSegment = IsPedestrianLightHovered(myRect3, end.SegmentId, hoveredSegment, segmentLights); } int lightOffset = -1; foreach (ExtVehicleType vehicleType in segmentLights.VehicleTypes) { ++lightOffset; CustomSegmentLight segmentLight = segmentLights.GetCustomLight(vehicleType); Vector3 offsetScreenPos = screenPos; offsetScreenPos.y -= (lightHeight + 10f * zoom) * lightOffset; SetAlpha(end.SegmentId, -1); var myRect1 = new Rect(offsetScreenPos.x - modeWidth / 2, offsetScreenPos.y - modeHeight / 2 + modeHeight - 7f * zoom, modeWidth, modeHeight); GUI.DrawTexture(myRect1, TrafficLightToolTextureResources.LightModeTexture2D); hoveredSegment = GetHoveredSegment(myRect1, end.SegmentId, hoveredSegment, segmentLight); // COUNTER hoveredSegment = RenderCounter(end.SegmentId, offsetScreenPos, modeWidth, modeHeight, zoom, segmentLights, hoveredSegment); if (lightOffset > 0) { // Info sign var infoWidth = 56.125f * zoom; var infoHeight = 51.375f * zoom; int numInfos = 0; for (int k = 0; k < TrafficManagerTool.InfoSignsToDisplay.Length; ++k) { if ((TrafficManagerTool.InfoSignsToDisplay[k] & vehicleType) == ExtVehicleType.None) { continue; } var infoRect = new Rect(offsetScreenPos.x + modeWidth / 2f + 7f * zoom * (float)(numInfos + 1) + infoWidth * (float)numInfos, offsetScreenPos.y - infoHeight / 2f, infoWidth, infoHeight); guiColor.a = 0.6f; GUI.DrawTexture(infoRect, TrafficLightToolTextureResources.VehicleInfoSignTextures[TrafficManagerTool.InfoSignsToDisplay[k]]); ++numInfos; } } if (end.OutgoingOneWay) { continue; } var hasLeftSegment = end.NumLeftSegments > 0; var hasForwardSegment = end.NumStraightSegments > 0; var hasRightSegment = end.NumRightSegments > 0; switch (segmentLight.CurrentMode) { case CustomSegmentLight.Mode.Simple: hoveredSegment = SimpleManualSegmentLightMode(end.SegmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); break; case CustomSegmentLight.Mode.SingleLeft: hoveredSegment = LeftForwardRManualSegmentLightMode(hasLeftSegment, end.SegmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment, hasForwardSegment, hasRightSegment); break; case CustomSegmentLight.Mode.SingleRight: hoveredSegment = RightForwardLSegmentLightMode(end.SegmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, hasForwardSegment, hasLeftSegment, segmentLight, hasRightSegment, hoveredSegment); break; default: // left arrow light if (hasLeftSegment) { hoveredSegment = LeftArrowLightMode(end.SegmentId, lightWidth, hasRightSegment, hasForwardSegment, offsetScreenPos, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); } // forward arrow light if (hasForwardSegment) { hoveredSegment = ForwardArrowLightMode(end.SegmentId, lightWidth, hasRightSegment, offsetScreenPos, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); } // right arrow light if (hasRightSegment) { hoveredSegment = RightArrowLightMode(end.SegmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); } break; } } } } if (hoveredSegment) { return; } _hoveredButton[0] = 0; _hoveredButton[1] = 0; }
public void ShowGUI(bool viewOnly) { try { TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; TrafficPriorityManager prioMan = TrafficPriorityManager.Instance; TrafficLightManager tlm = TrafficLightManager.Instance; Vector3 camPos = Constants.ServiceFactory.SimulationService.CameraPosition; bool clicked = !viewOnly?MainTool.CheckClicked() : false; ushort removedNodeId = 0; bool showRemoveButton = false; foreach (ushort nodeId in currentPriorityNodeIds) { if (!Constants.ServiceFactory.NetService.IsNodeValid(nodeId)) { continue; } if (!MainTool.IsNodeWithinViewDistance(nodeId)) { continue; } NodeGeometry nodeGeo = NodeGeometry.Get(nodeId); Vector3 nodePos = default(Vector3); Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate(ushort nId, ref NetNode node) { nodePos = node.m_position; return(true); }); foreach (SegmentEndGeometry endGeo in nodeGeo.SegmentEndGeometries) { if (endGeo == null) { continue; } if (endGeo.OutgoingOneWay) { continue; } ushort segmentId = endGeo.SegmentId; bool startNode = endGeo.StartNode; // calculate sign position Vector3 signPos = nodePos; Constants.ServiceFactory.NetService.ProcessSegment(segmentId, delegate(ushort sId, ref NetSegment segment) { signPos += 10f * (startNode ? segment.m_startDirection : segment.m_endDirection); return(true); }); Vector3 signScreenPos; if (!MainTool.WorldToScreenPoint(signPos, out signScreenPos)) { continue; } // draw sign and handle input PriorityType sign = prioMan.GetPrioritySign(segmentId, startNode); if (viewOnly && sign == PriorityType.None) { continue; } if (!viewOnly && sign != PriorityType.None) { showRemoveButton = true; } if (MainTool.DrawGenericSquareOverlayTexture(TextureResources.PrioritySignTextures[sign], camPos, signPos, 90f, !viewOnly) && clicked) { PriorityType?newSign = null; switch (sign) { case PriorityType.Main: newSign = PriorityType.Yield; break; case PriorityType.Yield: newSign = PriorityType.Stop; break; case PriorityType.Stop: newSign = PriorityType.Main; break; case PriorityType.None: default: newSign = prioMan.CountPrioritySignsAtNode(nodeId, PriorityType.Main) >= 2 ? PriorityType.Yield : PriorityType.Main; break; } if (newSign != null) { SetPrioritySign(segmentId, startNode, (PriorityType)newSign); } } // draw sign } // foreach segment end if (viewOnly) { continue; } // draw remove button and handle click if (showRemoveButton && MainTool.DrawHoverableSquareOverlayTexture(TextureResources.SignRemoveTexture2D, camPos, nodePos, 90f) && clicked) { prioMan.RemovePrioritySignsFromNode(nodeId); Log._Debug($"PrioritySignsTool.ShowGUI: Removed priority signs from node {nodeId}"); removedNodeId = nodeId; } } // foreach node if (removedNodeId != 0) { currentPriorityNodeIds.Remove(removedNodeId); SelectedNodeId = 0; } } catch (Exception e) { Log.Error(e.ToString()); } }
public void SimulationStep(bool onlyFirstPass = false) { #if DEBUGGEO bool debug = GlobalConfig.Instance.Debug.Switches[5]; #endif if (!stateUpdated) { return; } NetManager netManager = Singleton <NetManager> .instance; if (!onlyFirstPass && (netManager.m_segmentsUpdated || netManager.m_nodesUpdated)) // TODO maybe refactor NetManager use (however this could influence performance) { #if DEBUGGEO if (debug) { Log._Debug($"GeometryManager.SimulationStep(): Skipping! stateUpdated={stateUpdated}, m_segmentsUpdated={netManager.m_segmentsUpdated}, m_nodesUpdated={netManager.m_nodesUpdated}"); } #endif return; } try { Monitor.Enter(updateLock); bool updatesMissing = onlyFirstPass; for (int pass = 0; pass < (onlyFirstPass ? 1 : 2); ++pass) { bool firstPass = pass == 0; int len = updatedSegmentBuckets.Length; for (int i = 0; i < len; i++) { ulong segMask = updatedSegmentBuckets[i]; if (segMask != 0uL) { for (int m = 0; m < 64; m++) { if ((segMask & 1uL << m) != 0uL) { ushort segmentId = (ushort)(i << 6 | m); SegmentGeometry segmentGeometry = SegmentGeometry.Get(segmentId, true); if (firstPass ^ !segmentGeometry.IsValid()) { if (!firstPass) { updatesMissing = true; #if DEBUGGEO if (debug) { Log.Warning($"GeometryManager.SimulationStep(): Detected invalid segment {segmentGeometry.SegmentId} in second pass"); } #endif } continue; } #if DEBUGGEO if (debug) { Log._Debug($"GeometryManager.SimulationStep(): Notifying observers about segment {segmentGeometry.SegmentId}. Valid? {segmentGeometry.IsValid()} First pass? {firstPass}"); } #endif NotifyObservers(new GeometryUpdate(segmentGeometry)); updatedSegmentBuckets[i] &= ~(1uL << m); } } } } len = updatedNodeBuckets.Length; for (int i = 0; i < len; i++) { ulong nodeMask = updatedNodeBuckets[i]; if (nodeMask != 0uL) { for (int m = 0; m < 64; m++) { if ((nodeMask & 1uL << m) != 0uL) { ushort nodeId = (ushort)(i << 6 | m); NodeGeometry nodeGeometry = NodeGeometry.Get(nodeId); if (firstPass ^ !nodeGeometry.IsValid()) { if (!firstPass) { updatesMissing = true; #if DEBUGGEO if (debug) { Log.Warning($"GeometryManager.SimulationStep(): Detected invalid node {nodeGeometry.NodeId} in second pass"); } #endif } continue; } #if DEBUGGEO if (debug) { Log._Debug($"GeometryManager.SimulationStep(): Notifying observers about node {nodeGeometry.NodeId}. Valid? {nodeGeometry.IsValid()} First pass? {firstPass}"); } #endif NotifyObservers(new GeometryUpdate(nodeGeometry)); updatedNodeBuckets[i] &= ~(1uL << m); } } } } } if (!updatesMissing) { while (segmentReplacements.Count > 0) { SegmentEndReplacement replacement = segmentReplacements.Dequeue(); #if DEBUGGEO if (debug) { Log._Debug($"GeometryManager.SimulationStep(): Notifying observers about segment end replacement {replacement}"); } #endif NotifyObservers(new GeometryUpdate(replacement)); } stateUpdated = false; } } finally { Monitor.Exit(updateLock); } }