Stop atmos/power consoles from re-scanning sensors and re-running EnsureComp every tick#1392
Merged
fenndragon merged 4 commits intoHardLightSector:masterfrom Apr 23, 2026
Conversation
…paths
Round-2 of low-risk, gameplay-neutral perf cleanups.
- Speech/ReplacementAccentSystem: cache compiled (RegexOptions.Compiled)
word-boundary regexes per replacement word instead of building a fresh
Regex per word per spoken message. Big win for chat-heavy rounds with
accents like Bleat/Spanglish/etc.
- Power/BatterySystem: reuse a single scratch List<(EntityUid,float)> for
PostSync and Update instead of allocating a fresh ValueList sized to
Count<T>() every tick / every net sync.
- _NF/Cargo/NFCargoSystem.Telepad: replace per-tick LinkedSources.Select(...)
.ToList() with a reused scratch List<NetEntity> on the partial system.
- _NF/Shipyard/ShipyardGridSaveSystem.Update: lazy-allocate the 'expired'
list so the common (no-expirations) tick path allocates nothing.
- _Mono/FireControl/FireControlSystem.HasLineOfSight: drop redundant
.ToList() materialization of the raycast results; use Any() directly.
- _NF/Atmos/GasDepositScannerSystem: drop redundant .ToArray() copy of an
already-typed GasEntry[] before sending the UI message.
- _HL/ShuttleDeedTracking: gate per-shuttle Debug log interpolation behind
IsLogLevelEnabled(LogLevel.Debug) so the ToPrettyString call disappears
when debug logging is off.
- Storage/Material{Storage,Reclaimer}MagnetPickupSystem.ToggleMagnet: drop
dead 'var query = EntityQueryEnumerator<...>()' allocation that was
declared but never used.
No behavior changes, no gameplay changes. Build clean, 385 tests pass.
- RadarBlipSystem: stop globally clearing the cached blip-report dictionary on every single blip ComponentShutdown. The 75ms TTL is sufficient and combat (projectile/torpedo despawns) was thrashing the cache, forcing every open radar console to redo a full AssembleBlipsReport every frame. - ServerCleanupSystem.CleanupFloatingEntities: replace the O(entities * players) per-cycle nested scan with spatial bucketing keyed by (MapId, floor(x/120), floor(y/120)). At 100-150 CCU this is the difference between a several-hundred-millisecond stall and a near-zero pass. Also drop the unused _disconnectedPlayers stale-entry .Where().ToList() and switch the inner distance check to DistanceSquared. - ThrusterSystem.Update: replace per-firing-thruster comp.Colliding.ToArray() defensive copy with a single reusable scratch list. Capital ships with many active burn-fixture thrusters were allocating one List per thruster per tick. Touches HardLight-only code paths; no upstream Robust changes.
Previous fix removed the global _recentRadarReports.Clear() but left a subtle ordering hole: if BlipRemovalEvent arrived at the client before a cached GiveBlipsEvent (served within the 75ms TTL), the cached payload would still contain the dead blip and re-introduce it on the radar for up to ~300ms (TTL + 225ms throttle). Iterate each cached report once per shutdown and remove the dead blip's BlipNetData by Uid. O(consoles * blips_per_report) per shutdown — still dwarfed by the cache-hit savings during combat — and eliminates the visual flicker entirely.
…r-tick UI refresh AtmosMonitoringConsoleSystem.Update used to walk every GasPipeSensorComponent in the world once per console per second, so a capital ship with multiple atmos consoles paid that cost N times. Cache the built sensor entry list per gridUid for the duration of one Update() pass and reuse it for every console on that grid. Cleared at the end of the pass, so behavior across passes is identical. Also hoist the IsUiOpen check up to the Update() loop so closed consoles skip the GridUid lookup entirely. EnsureComp<NavMapComponent>(gridUid) was being called every UI refresh from all three consoles (Atmos monitoring, Atmos alerts, Power monitoring). Move it into the existing init / parent-change handlers so it runs once per console placement instead of every second per console. No gameplay change — same UI payloads, same cadence, same NavMap presence.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
About the PR
Another small no-gameplay-change perf pass, this time aimed at a few of the
grid-mounted consoles that show up a lot on big ships.
While poking around at where server time goes during a populated round, I
noticed the atmos monitoring console, the atmos alerts computer, and the
power monitoring console all share the same shape of problem: every second
they refresh their UI, and that refresh ends up doing a couple of things
it doesn't really need to do per tick.
The big one is the atmos monitoring console. Its UI refresh walks every
GasPipeSensorComponentin the world to figure out what to show. That'sfine for one console, but a capital-style ship usually has more than one
atmos console open at the same time, and each of them was redoing the
exact same world-wide scan and building the exact same sensor list. This
PR caches that built list per
gridUidfor the duration of oneUpdate()pass and lets the rest of the consoles on that grid reuse it. The cache
is dropped at the end of the pass, so behavior across passes is identical.
The other small thing is
EnsureComp<NavMapComponent>(gridUid). All threeof these consoles were calling that on the grid every UI refresh, even
though the only thing it actually has to do is run once when the console
gets placed on a grid. This PR moves that call into the existing init /
parent-change handlers so it only runs when a console first lands on a
grid (or moves to a new one), instead of every second per console for the
rest of the round.
Also hoisted the
IsUiOpencheck on the atmos monitoring console up tothe outer
Update()loop so closed consoles don't even pay theGridUidlookup. It was already the first check insideUpdateUIState,this just makes it a bit cheaper.
Why / Balance
No gameplay or balance change intended.
NavMapComponentends up on the same grids —EnsureCompisidempotent, so consoles that already had a NavMap in upstream still
have one, and consoles that re-parent (e.g. a ship docking to a new
grid) re-run the init path and get one again.
refresh window; the cache only deduplicates within a single pass
across consoles on the same grid.
before.
This is purely a "do the same thing, with less repeated server work" pass.
Technical details
AtmosMonitoringConsoleSystemUpdate()scratch dictionaryDictionary<EntityUid, List<AtmosMonitoringConsoleEntry>>keyed bygrid. The first console for a given grid in a pass populates it; all
subsequent consoles on that grid reuse the list. Cleared at the end
of the pass.
IsUiOpencheck up to theUpdate()loop so closedconsoles short-circuit before the per-console work starts.
EnsureComp<NavMapComponent>(gridUid)intoInitializeAtmosMonitoringConsoleso it runs at console init andparent-change, not every UI refresh.
AtmosAlertsComputerSystemEnsureComp<NavMapComponent>(gridUid)out ofUpdateUIStateand into
InitalizeConsole. (Init runs onComponentInit,EntParentChangedMessage, andOnGridSplit, so any new grid theconsole lands on still gets its NavMap.)
PowerMonitoringConsoleSystemEnsureNavMapForConsolehelper calledfrom
OnConsoleInitandOnConsoleParentChangedso the per-tickUpdateUIStateno longer callsEnsureCompevery second per console.No CVars, no prototypes, no YAML, no shared/client code touched.
How to test
dotnet build /property:GenerateFullPaths=true /consoleloggerparameters:'ForceNoAlign;NoSummary'and confirm the solution still builds.
atmos monitoring consoles. Open both consoles and confirm both show
the same sensor list with the same temperature/pressure/gas data as
before.
disappears in the console UI on the next refresh window (~1s), same
as before.
and NavMap regions still render. Trigger an alarm and confirm the
console state still updates and the appearance still changes.
load lists still populate and update normally, including the
"rogue power consumer" flag if you spawn a high-draw consumer.
console was placed still get one as soon as a console lands on the
grid (e.g. spawn a fresh grid, anchor a console, open the UI — NavMap
visualisation should work).
Media
N/A — server-side perf only, no visual change.
Breaking changes
None.
Changelog
No changelog. Internal perf cleanup only.