//[MethodImpl(MethodImplOptions.AggressiveInlining)] public void EqualizePressureInZone(int cycleNum) { if (Air == null || (_tileAtmosInfo.LastCycle >= cycleNum)) { return; // Already done. } _tileAtmosInfo = new TileAtmosInfo(); var startingMoles = Air.TotalMoles; var runAtmos = false; // We need to figure if this is necessary foreach (var(direction, other) in _adjacentTiles) { if (other?.Air == null) { continue; } var comparisonMoles = other.Air.TotalMoles; if (!(MathF.Abs(comparisonMoles - startingMoles) > Atmospherics.MinimumMolesDeltaToMove)) { continue; } runAtmos = true; break; } if (!runAtmos) // There's no need so we don't bother. { _tileAtmosInfo.LastCycle = cycleNum; return; } var queueCycle = ++_gridAtmosphereComponent.EqualizationQueueCycleControl; var totalMoles = 0f; var tiles = new TileAtmosphere[Atmospherics.ZumosHardTileLimit]; tiles[0] = this; _tileAtmosInfo.LastQueueCycle = queueCycle; var tileCount = 1; for (var i = 0; i < tileCount; i++) { if (i > Atmospherics.ZumosHardTileLimit) { break; } var exploring = tiles[i]; if (i < Atmospherics.ZumosTileLimit) { var tileMoles = exploring.Air.TotalMoles; exploring._tileAtmosInfo.MoleDelta = tileMoles; totalMoles += tileMoles; } foreach (var(_, adj) in exploring._adjacentTiles) { if (adj?.Air == null) { continue; } if (adj._tileAtmosInfo.LastQueueCycle == queueCycle) { continue; } adj._tileAtmosInfo = new TileAtmosInfo(); adj._tileAtmosInfo.LastQueueCycle = queueCycle; if (tileCount < Atmospherics.ZumosHardTileLimit) { tiles[tileCount++] = adj; } if (adj.Air.Immutable) { // Looks like someone opened an airlock to space! ExplosivelyDepressurize(cycleNum); return; } } } if (tileCount > Atmospherics.ZumosTileLimit) { for (var i = Atmospherics.ZumosTileLimit; i < tileCount; i++) { //We unmark them. We shouldn't be pushing/pulling gases to/from them. var tile = tiles[i]; if (tile == null) { continue; } tiles[i]._tileAtmosInfo.LastQueueCycle = 0; } tileCount = Atmospherics.ZumosTileLimit; } //tiles = tiles.AsSpan().Slice(0, tileCount).ToArray(); // According to my benchmarks, this is much slower. Array.Resize(ref tiles, tileCount); var averageMoles = totalMoles / (tiles.Length); var giverTiles = new List <TileAtmosphere>(); var takerTiles = new List <TileAtmosphere>(); for (var i = 0; i < tileCount; i++) { var tile = tiles[i]; tile._tileAtmosInfo.LastCycle = cycleNum; tile._tileAtmosInfo.MoleDelta -= averageMoles; if (tile._tileAtmosInfo.MoleDelta > 0) { giverTiles.Add(tile); } else { takerTiles.Add(tile); } } var logN = MathF.Log2(tiles.Length); // Optimization - try to spread gases using an O(nlogn) algorithm that has a chance of not working first to avoid O(n^2) if (giverTiles.Count > logN && takerTiles.Count > logN) { // Even if it fails, it will speed up the next part. Array.Sort(tiles, (a, b) => a._tileAtmosInfo.MoleDelta.CompareTo(b._tileAtmosInfo.MoleDelta)); foreach (var tile in tiles) { tile._tileAtmosInfo.FastDone = true; if (!(tile._tileAtmosInfo.MoleDelta > 0)) { continue; } Direction eligibleAdjBits = 0; var amtEligibleAdj = 0; foreach (var direction in Cardinal) { if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) { continue; } // skip anything that isn't part of our current processing block. Original one didn't do this unfortunately, which probably cause some massive lag. if (tile2._tileAtmosInfo.FastDone || tile2._tileAtmosInfo.LastQueueCycle != queueCycle) { continue; } eligibleAdjBits |= direction; amtEligibleAdj++; } if (amtEligibleAdj <= 0) { continue; // Oof we've painted ourselves into a corner. Bad luck. Next part will handle this. } var molesToMove = tile._tileAtmosInfo.MoleDelta / amtEligibleAdj; foreach (var direction in Cardinal) { if ((eligibleAdjBits & direction) == 0 || !tile._adjacentTiles.TryGetValue(direction, out var tile2)) { continue; } tile.AdjustEqMovement(direction, molesToMove); tile._tileAtmosInfo.MoleDelta -= molesToMove; tile2._tileAtmosInfo.MoleDelta += molesToMove; } } giverTiles.Clear(); takerTiles.Clear(); foreach (var tile in tiles) { if (tile._tileAtmosInfo.MoleDelta > 0) { giverTiles.Add(tile); } else { takerTiles.Add(tile); } } // This is the part that can become O(n^2). if (giverTiles.Count < takerTiles.Count) { // as an optimization, we choose one of two methods based on which list is smaller. We really want to avoid O(n^2) if we can. var queue = new List <TileAtmosphere>(takerTiles.Count); foreach (var giver in giverTiles) { giver._tileAtmosInfo.CurrentTransferDirection = (Direction)(-1); giver._tileAtmosInfo.CurrentTransferAmount = 0; var queueCycleSlow = ++_gridAtmosphereComponent.EqualizationQueueCycleControl; queue.Clear(); queue.Add(giver); giver._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow; var queueCount = queue.Count; for (var i = 0; i < queueCount; i++) { if (giver._tileAtmosInfo.MoleDelta <= 0) { break; // We're done here now. Let's not do more work than needed. } var tile = queue[i]; foreach (var direction in Cardinal) { if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) { continue; } if (giver._tileAtmosInfo.MoleDelta <= 0) { break; // We're done here now. Let's not do more work than needed. } if (tile2?._tileAtmosInfo == null || tile2._tileAtmosInfo.LastQueueCycle != queueCycle) { continue; } if (tile2._tileAtmosInfo.LastSlowQueueCycle == queueCycleSlow) { continue; } queue.Add(tile2); queueCount++; tile2._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow; tile2._tileAtmosInfo.CurrentTransferDirection = direction.GetOpposite(); tile2._tileAtmosInfo.CurrentTransferAmount = 0; if (tile2._tileAtmosInfo.MoleDelta < 0) { // This tile needs gas. Let's give it to 'em. if (-tile2._tileAtmosInfo.MoleDelta > giver._tileAtmosInfo.MoleDelta) { // We don't have enough gas! tile2._tileAtmosInfo.CurrentTransferAmount -= giver._tileAtmosInfo.MoleDelta; tile2._tileAtmosInfo.MoleDelta += giver._tileAtmosInfo.MoleDelta; giver._tileAtmosInfo.MoleDelta = 0; } else { // We have enough gas. tile2._tileAtmosInfo.CurrentTransferAmount += tile2._tileAtmosInfo.MoleDelta; giver._tileAtmosInfo.MoleDelta += tile2._tileAtmosInfo.MoleDelta; tile2._tileAtmosInfo.MoleDelta = 0; } } } } // Putting this loop here helps make it O(n^2) over O(n^3) for (var i = queue.Count - 1; i >= 0; i--) { var tile = queue[i]; if (tile._tileAtmosInfo.CurrentTransferAmount != 0 && tile._tileAtmosInfo.CurrentTransferDirection != (Direction)(-1)) { tile.AdjustEqMovement(tile._tileAtmosInfo.CurrentTransferDirection, tile._tileAtmosInfo.CurrentTransferAmount); if (tile._adjacentTiles.TryGetValue(tile._tileAtmosInfo.CurrentTransferDirection, out var adjacent)) { adjacent._tileAtmosInfo.CurrentTransferAmount += tile._tileAtmosInfo.CurrentTransferAmount; } tile._tileAtmosInfo.CurrentTransferAmount = 0; } } } } else { var queue = new List <TileAtmosphere>(giverTiles.Count); foreach (var taker in takerTiles) { taker._tileAtmosInfo.CurrentTransferDirection = Direction.Invalid; taker._tileAtmosInfo.CurrentTransferAmount = 0; var queueCycleSlow = ++_gridAtmosphereComponent.EqualizationQueueCycleControl; queue.Clear(); queue.Add(taker); taker._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow; var queueCount = queue.Count; for (int i = 0; i < queueCount; i++) { if (taker._tileAtmosInfo.MoleDelta >= 0) { break; // We're done here now. Let's not do more work than needed. } var tile = queue[i]; foreach (var direction in Cardinal) { if (!tile._adjacentTiles.ContainsKey(direction)) { continue; } var tile2 = tile._adjacentTiles[direction]; if (taker._tileAtmosInfo.MoleDelta >= 0) { break; // We're done here now. Let's not do more work than needed. } if (tile2?._tileAtmosInfo == null || tile2._tileAtmosInfo.LastQueueCycle != queueCycle) { continue; } if (tile2._tileAtmosInfo.LastSlowQueueCycle == queueCycleSlow) { continue; } queue.Add(tile2); queueCount++; tile2._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow; tile2._tileAtmosInfo.CurrentTransferDirection = direction.GetOpposite(); tile2._tileAtmosInfo.CurrentTransferAmount = 0; if (tile2._tileAtmosInfo.MoleDelta > 0) { // This tile has gas we can suck, so let's if (tile2._tileAtmosInfo.MoleDelta > -taker._tileAtmosInfo.MoleDelta) { // They have enough gas tile2._tileAtmosInfo.CurrentTransferAmount -= taker._tileAtmosInfo.MoleDelta; tile2._tileAtmosInfo.MoleDelta += taker._tileAtmosInfo.MoleDelta; taker._tileAtmosInfo.MoleDelta = 0; } else { // They don't have enough gas! tile2._tileAtmosInfo.CurrentTransferAmount += tile2._tileAtmosInfo.MoleDelta; taker._tileAtmosInfo.MoleDelta += tile2._tileAtmosInfo.MoleDelta; tile2._tileAtmosInfo.MoleDelta = 0; } } } } for (var i = queue.Count - 1; i >= 0; i--) { var tile = queue[i]; if (tile._tileAtmosInfo.CurrentTransferAmount == 0 || tile._tileAtmosInfo.CurrentTransferDirection == Direction.Invalid) { continue; } tile.AdjustEqMovement(tile._tileAtmosInfo.CurrentTransferDirection, tile._tileAtmosInfo.CurrentTransferAmount); if (tile._adjacentTiles.TryGetValue(tile._tileAtmosInfo.CurrentTransferDirection, out var adjacent)) { adjacent._tileAtmosInfo.CurrentTransferAmount += tile._tileAtmosInfo.CurrentTransferAmount; } tile._tileAtmosInfo.CurrentTransferAmount = 0; } } } foreach (var tile in tiles) { tile.FinalizeEq(); } foreach (var tile in tiles) { foreach (var direction in Cardinal) { if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) { continue; } if (tile2?.Air?.Compare(Air) == GasMixture.GasCompareResult.NoExchange) { continue; } _gridAtmosphereComponent.AddActiveTile(tile2); break; } } } }
public static bool TryGetTileAtmosphere(this EntityCoordinates coordinates, [MaybeNullWhen(false)] out TileAtmosphere atmosphere) { // ReSharper disable once ConditionIsAlwaysTrueOrFalse return(!Equals(atmosphere = coordinates.GetTileAtmosphere() !, default));
private void ConsiderFirelocks(TileAtmosphere other) { // TODO ATMOS firelocks! //throw new NotImplementedException(); }