diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.CVars.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.CVars.cs index bb5f0609a2..4954f3e237 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.CVars.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.CVars.cs @@ -21,6 +21,12 @@ namespace Content.Server.Atmos.EntitySystems public bool ExcitedGroupsSpaceIsAllConsuming { get; private set; } public float AtmosMaxProcessTime { get; private set; } public float AtmosTickRate { get; private set; } + + /// + /// Time between each atmos sub-update. If you are writing an atmos device, use AtmosDeviceUpdateEvent.dt + /// instead of this value, because atmos devices do not update each are sub-update and sometimes are skipped to + /// meet the tick deadline. + /// public float AtmosTime => 1f / AtmosTickRate; private void InitializeCVars() diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs index f5b06a5f0c..ea25874db7 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs @@ -14,7 +14,6 @@ namespace Content.Server.Atmos.EntitySystems { [Dependency] private readonly IGameTiming _gameTiming = default!; - private readonly AtmosDeviceUpdateEvent _updateEvent = new(); private readonly Stopwatch _simulationStopwatch = new(); /// @@ -337,7 +336,7 @@ namespace Content.Server.Atmos.EntitySystems var number = 0; while (atmosphere.CurrentRunAtmosDevices.TryDequeue(out var device)) { - RaiseLocalEvent(device.Owner, _updateEvent, false); + RaiseLocalEvent(device.Owner, new AtmosDeviceUpdateEvent(AtmosTime * (int)AtmosphereProcessingState.NumStates), false); device.LastProcess = time; if (number++ < LagCheckIterations) continue; @@ -509,5 +508,6 @@ namespace Content.Server.Atmos.EntitySystems Superconductivity, PipeNet, AtmosDevices, + NumStates } } diff --git a/Content.Server/Atmos/Piping/Components/AtmosDeviceComponent.cs b/Content.Server/Atmos/Piping/Components/AtmosDeviceComponent.cs index d4e153dd97..5afcf2705e 100644 --- a/Content.Server/Atmos/Piping/Components/AtmosDeviceComponent.cs +++ b/Content.Server/Atmos/Piping/Components/AtmosDeviceComponent.cs @@ -7,32 +7,50 @@ namespace Content.Server.Atmos.Piping.Components public sealed class AtmosDeviceComponent : Component { /// - /// Whether this device requires being anchored to join an atmosphere. + /// If true, this device must be anchored before it will receive any AtmosDeviceUpdateEvents. /// [ViewVariables(VVAccess.ReadWrite)] [DataField("requireAnchored")] public bool RequireAnchored { get; private set; } = true; /// - /// Whether this device will join an entity system to process when not in a grid. + /// If true, update even when there is no grid atmosphere. Normally, atmos devices only + /// update when inside a grid atmosphere, because they work with gases in the environment + /// and won't do anything useful if there is no environment. This is useful for devices + /// like gas canisters whose contents can still react if the canister itself is not inside + /// a grid atmosphere. /// [DataField("joinSystem")] public bool JoinSystem { get; } = false; /// - /// Whether we have joined an entity system to process. + /// If non-null, the grid that this device is part of. + /// + public EntityUid? JoinedGrid { get; set; } + + /// + /// Indicates that a device is not on a grid atmosphere but still being updated. /// [ViewVariables] public bool JoinedSystem { get; set; } = false; [ViewVariables] public TimeSpan LastProcess { get; set; } = TimeSpan.Zero; - - public EntityUid? JoinedGrid { get; set; } } public sealed class AtmosDeviceUpdateEvent : EntityEventArgs - {} + { + /// + /// Time elapsed since last update, in seconds. Multiply values used in the update handler + /// by this number to make them tickrate-invariant. Use this number instead of AtmosphereSystem.AtmosTime. + /// + public float dt; + + public AtmosDeviceUpdateEvent(float dt) + { + this.dt = dt; + } + } public sealed class AtmosDeviceEnabledEvent : EntityEventArgs {} diff --git a/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs b/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs index 130370599f..a3f5f2113f 100644 --- a/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs +++ b/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs @@ -11,9 +11,9 @@ namespace Content.Server.Atmos.Piping.EntitySystems [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; - private readonly AtmosDeviceUpdateEvent _updateEvent = new(); - private float _timer = 0f; + + // Set of atmos devices that are off-grid but have JoinSystem set. private readonly HashSet _joinedDevices = new(); public override void Initialize() @@ -27,40 +27,23 @@ namespace Content.Server.Atmos.Piping.EntitySystems SubscribeLocalEvent(OnDeviceAnchorChanged); } - private bool CanJoinAtmosphere(AtmosDeviceComponent component, TransformComponent transform) - { - return (!component.RequireAnchored || transform.Anchored) && transform.GridUid != null; - } - public void JoinAtmosphere(AtmosDeviceComponent component) { var transform = Transform(component.Owner); - if (!CanJoinAtmosphere(component, transform)) - { + if (component.RequireAnchored && !transform.Anchored) return; - } - // TODO: low-hanging fruit for perf improvements around here + // Attempt to add device to a grid atmosphere. + bool onGrid = (transform.GridUid != null) && _atmosphereSystem.AddAtmosDevice(transform.GridUid!.Value, component); - // GridUid is not null because we can join atmosphere. - // We try to add the device to a valid atmosphere, and if we can't, try to add it to the entity system. - if (!_atmosphereSystem.AddAtmosDevice(transform.GridUid!.Value, component)) + if (!onGrid && component.JoinSystem) { - if (component.JoinSystem) - { - _joinedDevices.Add(component); - component.JoinedSystem = true; - } - else - { - return; - } + _joinedDevices.Add(component); + component.JoinedSystem = true; } - component.LastProcess = _gameTiming.CurTime; - RaiseLocalEvent(component.Owner, new AtmosDeviceEnabledEvent(), false); } @@ -117,6 +100,10 @@ namespace Content.Server.Atmos.Piping.EntitySystems RejoinAtmosphere(component); } + /// + /// Update atmos devices that are off-grid but have JoinSystem set. For devices updates when + /// a device is on a grid, see AtmosphereSystem:UpdateProcessing(). + /// public override void Update(float frameTime) { _timer += frameTime; @@ -129,7 +116,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems var time = _gameTiming.CurTime; foreach (var device in _joinedDevices) { - RaiseLocalEvent(device.Owner, _updateEvent, false); + RaiseLocalEvent(device.Owner, new AtmosDeviceUpdateEvent(_atmosphereSystem.AtmosTime), false); device.LastProcess = time; } }