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;
}
}