Optimizations from server profile (#38290)

* Properly cache regexes in chat sanitization/accents

Wow I wonder if `new Regex()` has a cost to it *looks at server profile*.

* Avoid lag caused by Tippy command completions

CompletionHelper.PrototypeIDs explicitly says *not* to use it with EntityPrototype. Unsurprisingly, reporting a completion result for every entity prototype in the game is a *bad idea*.

* Add active count metrics to some high-load systems

Mover & NPCs

I suspect the thing that caused the Leviathan round to shit itself on performance is NPC spam in space or something. So let's verify that.

* Enable parallel processing on pow3r again

Originally disabled due to a theory of it causing bugs, it was re-enabled on Vulture, and I'm not aware of it having caused any issues there.

* Replace hashset with bitflags for AtmosMonitor alert types.

Allocating these hashsets was like 20% of the CPU of atmos, somehow.

* Cache HashSet used for space movement collider checks

Turns out this was a ton of server allocations. Huh.
This commit is contained in:
Pieter-Jan Briers
2025-07-26 11:44:34 +02:00
committed by GitHub
parent d0c104e4b0
commit 444180c20d
14 changed files with 217 additions and 128 deletions

View File

@@ -48,7 +48,7 @@ public sealed partial class AtmosAlarmableComponent : Component
public HashSet<string> SyncWithTags { get; private set; } = new();
[DataField("monitorAlertTypes")]
public HashSet<AtmosMonitorThresholdType>? MonitorAlertTypes { get; private set; }
public AtmosMonitorThresholdTypeFlags MonitorAlertTypes { get; private set; }
/// <summary>
/// If this device should receive only. If it can only

View File

@@ -59,7 +59,7 @@ public sealed partial class AtmosMonitorComponent : Component
public AtmosAlarmType LastAlarmState = AtmosAlarmType.Normal;
[DataField("trippedThresholds")]
public HashSet<AtmosMonitorThresholdType> TrippedThresholds = new();
public AtmosMonitorThresholdTypeFlags TrippedThresholds;
/// <summary>
/// Registered devices in this atmos monitor. Alerts will be sent directly

View File

@@ -108,9 +108,9 @@ public sealed class AtmosAlarmableSystem : EntitySystem
break;
}
if (args.Data.TryGetValue(AlertTypes, out HashSet<AtmosMonitorThresholdType>? types) && component.MonitorAlertTypes != null)
if (args.Data.TryGetValue(AlertTypes, out AtmosMonitorThresholdTypeFlags types) && component.MonitorAlertTypes != AtmosMonitorThresholdTypeFlags.None)
{
isValid = types.Any(type => component.MonitorAlertTypes.Contains(type));
isValid = (types & component.MonitorAlertTypes) != 0;
}
if (!component.NetworkAlarmStates.ContainsKey(args.SenderAddress))

View File

@@ -207,7 +207,7 @@ public sealed class AtmosMonitorSystem : EntitySystem
if (component.MonitorFire
&& component.LastAlarmState != AtmosAlarmType.Danger)
{
component.TrippedThresholds.Add(AtmosMonitorThresholdType.Temperature);
component.TrippedThresholds |= AtmosMonitorThresholdTypeFlags.Temperature;
Alert(uid, AtmosAlarmType.Danger, null, component); // technically???
}
@@ -218,7 +218,7 @@ public sealed class AtmosMonitorSystem : EntitySystem
&& component.TemperatureThreshold.CheckThreshold(args.Temperature, out var temperatureState)
&& temperatureState > component.LastAlarmState)
{
component.TrippedThresholds.Add(AtmosMonitorThresholdType.Temperature);
component.TrippedThresholds |= AtmosMonitorThresholdTypeFlags.Temperature;
Alert(uid, AtmosAlarmType.Danger, null, component);
}
}
@@ -259,7 +259,7 @@ public sealed class AtmosMonitorSystem : EntitySystem
if (!Resolve(uid, ref monitor)) return;
var state = AtmosAlarmType.Normal;
HashSet<AtmosMonitorThresholdType> alarmTypes = new(monitor.TrippedThresholds);
var alarmTypes = monitor.TrippedThresholds;
if (monitor.TemperatureThreshold != null
&& monitor.TemperatureThreshold.CheckThreshold(air.Temperature, out var temperatureState))
@@ -267,11 +267,11 @@ public sealed class AtmosMonitorSystem : EntitySystem
if (temperatureState > state)
{
state = temperatureState;
alarmTypes.Add(AtmosMonitorThresholdType.Temperature);
alarmTypes |= AtmosMonitorThresholdTypeFlags.Temperature;
}
else if (temperatureState == AtmosAlarmType.Normal)
{
alarmTypes.Remove(AtmosMonitorThresholdType.Temperature);
alarmTypes &= ~AtmosMonitorThresholdTypeFlags.Temperature;
}
}
@@ -282,11 +282,11 @@ public sealed class AtmosMonitorSystem : EntitySystem
if (pressureState > state)
{
state = pressureState;
alarmTypes.Add(AtmosMonitorThresholdType.Pressure);
alarmTypes |= AtmosMonitorThresholdTypeFlags.Pressure;
}
else if (pressureState == AtmosAlarmType.Normal)
{
alarmTypes.Remove(AtmosMonitorThresholdType.Pressure);
alarmTypes &= ~AtmosMonitorThresholdTypeFlags.Pressure;
}
}
@@ -306,17 +306,17 @@ public sealed class AtmosMonitorSystem : EntitySystem
if (tripped)
{
alarmTypes.Add(AtmosMonitorThresholdType.Gas);
alarmTypes |= AtmosMonitorThresholdTypeFlags.Gas;
}
else
{
alarmTypes.Remove(AtmosMonitorThresholdType.Gas);
alarmTypes &= ~AtmosMonitorThresholdTypeFlags.Gas;
}
}
// if the state of the current air doesn't match the last alarm state,
// we update the state
if (state != monitor.LastAlarmState || !alarmTypes.SetEquals(monitor.TrippedThresholds))
if (state != monitor.LastAlarmState || alarmTypes != monitor.TrippedThresholds)
{
Alert(uid, state, alarmTypes, monitor);
}
@@ -327,7 +327,7 @@ public sealed class AtmosMonitorSystem : EntitySystem
/// </summary>
/// <param name="state">The alarm state to set this monitor to.</param>
/// <param name="alarms">The alarms that caused this alarm state.</param>
public void Alert(EntityUid uid, AtmosAlarmType state, HashSet<AtmosMonitorThresholdType>? alarms = null, AtmosMonitorComponent? monitor = null)
public void Alert(EntityUid uid, AtmosAlarmType state, AtmosMonitorThresholdTypeFlags? alarms = null, AtmosMonitorComponent? monitor = null)
{
if (!Resolve(uid, ref monitor))
return;

View File

@@ -12,86 +12,86 @@ namespace Content.Server.Chat.Managers;
/// </summary>
public sealed class ChatSanitizationManager : IChatSanitizationManager
{
private static readonly Dictionary<string, string> ShorthandToEmote = new()
{
{ ":)", "chatsan-smiles" },
{ ":]", "chatsan-smiles" },
{ "=)", "chatsan-smiles" },
{ "=]", "chatsan-smiles" },
{ "(:", "chatsan-smiles" },
{ "[:", "chatsan-smiles" },
{ "(=", "chatsan-smiles" },
{ "[=", "chatsan-smiles" },
{ "^^", "chatsan-smiles" },
{ "^-^", "chatsan-smiles" },
{ ":(", "chatsan-frowns" },
{ ":[", "chatsan-frowns" },
{ "=(", "chatsan-frowns" },
{ "=[", "chatsan-frowns" },
{ "):", "chatsan-frowns" },
{ ")=", "chatsan-frowns" },
{ "]:", "chatsan-frowns" },
{ "]=", "chatsan-frowns" },
{ ":D", "chatsan-smiles-widely" },
{ "D:", "chatsan-frowns-deeply" },
{ ":O", "chatsan-surprised" },
{ ":3", "chatsan-smiles" },
{ ":S", "chatsan-uncertain" },
{ ":>", "chatsan-grins" },
{ ":<", "chatsan-pouts" },
{ "xD", "chatsan-laughs" },
{ ":'(", "chatsan-cries" },
{ ":'[", "chatsan-cries" },
{ "='(", "chatsan-cries" },
{ "='[", "chatsan-cries" },
{ ")':", "chatsan-cries" },
{ "]':", "chatsan-cries" },
{ ")'=", "chatsan-cries" },
{ "]'=", "chatsan-cries" },
{ ";-;", "chatsan-cries" },
{ ";_;", "chatsan-cries" },
{ "qwq", "chatsan-cries" },
{ ":u", "chatsan-smiles-smugly" },
{ ":v", "chatsan-smiles-smugly" },
{ ">:i", "chatsan-annoyed" },
{ ":i", "chatsan-sighs" },
{ ":|", "chatsan-sighs" },
{ ":p", "chatsan-stick-out-tongue" },
{ ";p", "chatsan-stick-out-tongue" },
{ ":b", "chatsan-stick-out-tongue" },
{ "0-0", "chatsan-wide-eyed" },
{ "o-o", "chatsan-wide-eyed" },
{ "o.o", "chatsan-wide-eyed" },
{ "._.", "chatsan-surprised" },
{ ".-.", "chatsan-confused" },
{ "-_-", "chatsan-unimpressed" },
{ "smh", "chatsan-unimpressed" },
{ "o/", "chatsan-waves" },
{ "^^/", "chatsan-waves" },
{ ":/", "chatsan-uncertain" },
{ ":\\", "chatsan-uncertain" },
{ "lmao", "chatsan-laughs" },
{ "lmfao", "chatsan-laughs" },
{ "lol", "chatsan-laughs" },
{ "lel", "chatsan-laughs" },
{ "kek", "chatsan-laughs" },
{ "rofl", "chatsan-laughs" },
{ "o7", "chatsan-salutes" },
{ ";_;7", "chatsan-tearfully-salutes" },
{ "idk", "chatsan-shrugs" },
{ ";)", "chatsan-winks" },
{ ";]", "chatsan-winks" },
{ "(;", "chatsan-winks" },
{ "[;", "chatsan-winks" },
{ ":')", "chatsan-tearfully-smiles" },
{ ":']", "chatsan-tearfully-smiles" },
{ "=')", "chatsan-tearfully-smiles" },
{ "=']", "chatsan-tearfully-smiles" },
{ "(':", "chatsan-tearfully-smiles" },
{ "[':", "chatsan-tearfully-smiles" },
{ "('=", "chatsan-tearfully-smiles" },
{ "['=", "chatsan-tearfully-smiles" }
};
private static readonly (Regex regex, string emoteKey)[] ShorthandToEmote =
[
Entry(":)", "chatsan-smiles"),
Entry(":]", "chatsan-smiles"),
Entry("=)", "chatsan-smiles"),
Entry("=]", "chatsan-smiles"),
Entry("(:", "chatsan-smiles"),
Entry("[:", "chatsan-smiles"),
Entry("(=", "chatsan-smiles"),
Entry("[=", "chatsan-smiles"),
Entry("^^", "chatsan-smiles"),
Entry("^-^", "chatsan-smiles"),
Entry(":(", "chatsan-frowns"),
Entry(":[", "chatsan-frowns"),
Entry("=(", "chatsan-frowns"),
Entry("=[", "chatsan-frowns"),
Entry("):", "chatsan-frowns"),
Entry(")=", "chatsan-frowns"),
Entry("]:", "chatsan-frowns"),
Entry("]=", "chatsan-frowns"),
Entry(":D", "chatsan-smiles-widely"),
Entry("D:", "chatsan-frowns-deeply"),
Entry(":O", "chatsan-surprised"),
Entry(":3", "chatsan-smiles"),
Entry(":S", "chatsan-uncertain"),
Entry(":>", "chatsan-grins"),
Entry(":<", "chatsan-pouts"),
Entry("xD", "chatsan-laughs"),
Entry(":'(", "chatsan-cries"),
Entry(":'[", "chatsan-cries"),
Entry("='(", "chatsan-cries"),
Entry("='[", "chatsan-cries"),
Entry(")':", "chatsan-cries"),
Entry("]':", "chatsan-cries"),
Entry(")'=", "chatsan-cries"),
Entry("]'=", "chatsan-cries"),
Entry(";-;", "chatsan-cries"),
Entry(";_;", "chatsan-cries"),
Entry("qwq", "chatsan-cries"),
Entry(":u", "chatsan-smiles-smugly"),
Entry(":v", "chatsan-smiles-smugly"),
Entry(">:i", "chatsan-annoyed"),
Entry(":i", "chatsan-sighs"),
Entry(":|", "chatsan-sighs"),
Entry(":p", "chatsan-stick-out-tongue"),
Entry(";p", "chatsan-stick-out-tongue"),
Entry(":b", "chatsan-stick-out-tongue"),
Entry("0-0", "chatsan-wide-eyed"),
Entry("o-o", "chatsan-wide-eyed"),
Entry("o.o", "chatsan-wide-eyed"),
Entry("._.", "chatsan-surprised"),
Entry(".-.", "chatsan-confused"),
Entry("-_-", "chatsan-unimpressed"),
Entry("smh", "chatsan-unimpressed"),
Entry("o/", "chatsan-waves"),
Entry("^^/", "chatsan-waves"),
Entry(":/", "chatsan-uncertain"),
Entry(":\\", "chatsan-uncertain"),
Entry("lmao", "chatsan-laughs"),
Entry("lmfao", "chatsan-laughs"),
Entry("lol", "chatsan-laughs"),
Entry("lel", "chatsan-laughs"),
Entry("kek", "chatsan-laughs"),
Entry("rofl", "chatsan-laughs"),
Entry("o7", "chatsan-salutes"),
Entry(";_;7", "chatsan-tearfully-salutes"),
Entry("idk", "chatsan-shrugs"),
Entry(";)", "chatsan-winks"),
Entry(";]", "chatsan-winks"),
Entry("(;", "chatsan-winks"),
Entry("[;", "chatsan-winks"),
Entry(":')", "chatsan-tearfully-smiles"),
Entry(":']", "chatsan-tearfully-smiles"),
Entry("=')", "chatsan-tearfully-smiles"),
Entry("=']", "chatsan-tearfully-smiles"),
Entry("(':", "chatsan-tearfully-smiles"),
Entry("[':", "chatsan-tearfully-smiles"),
Entry("('=", "chatsan-tearfully-smiles"),
Entry("['=", "chatsan-tearfully-smiles"),
];
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly ILocalizationManager _loc = default!;
@@ -125,21 +125,8 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager
// -1 is just a canary for nothing found yet
var lastEmoteIndex = -1;
foreach (var (shorthand, emoteKey) in ShorthandToEmote)
foreach (var (r, emoteKey) in ShorthandToEmote)
{
// We have to escape it because shorthands like ":)" or "-_-" would break the regex otherwise.
var escaped = Regex.Escape(shorthand);
// So there are 2 cases:
// - If there is whitespace before it and after it is either punctuation, whitespace, or the end of the line
// Delete the word and the whitespace before
// - If it is at the start of the string and is followed by punctuation, whitespace, or the end of the line
// Delete the word and the punctuation if it exists.
var pattern =
$@"\s{escaped}(?=\p{{P}}|\s|$)|^{escaped}(?:\p{{P}}|(?=\s|$))";
var r = new Regex(pattern, RegexOptions.RightToLeft | RegexOptions.IgnoreCase);
// We're using sanitized as the original message until the end so that we can make sure the indices of
// the emotes are accurate.
var lastMatch = r.Match(sanitized);
@@ -159,4 +146,21 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager
sanitized = message.Trim();
return emote is not null;
}
private static (Regex regex, string emoteKey) Entry(string shorthand, string emoteKey)
{
// We have to escape it because shorthands like ":)" or "-_-" would break the regex otherwise.
var escaped = Regex.Escape(shorthand);
// So there are 2 cases:
// - If there is whitespace before it and after it is either punctuation, whitespace, or the end of the line
// Delete the word and the whitespace before
// - If it is at the start of the string and is followed by punctuation, whitespace, or the end of the line
// Delete the word and the punctuation if it exists.
var pattern = new Regex(
$@"\s{escaped}(?=\p{{P}}|\s|$)|^{escaped}(?:\p{{P}}|(?=\s|$))",
RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Compiled);
return (pattern, emoteKey);
}
}

View File

@@ -30,11 +30,16 @@ using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Content.Shared.Prying.Systems;
using Microsoft.Extensions.ObjectPool;
using Prometheus;
namespace Content.Server.NPC.Systems;
public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
{
private static readonly Gauge ActiveSteeringGauge = Metrics.CreateGauge(
"npc_steering_active_count",
"Amount of NPCs trying to actively do steering");
/*
* We use context steering to determine which way to move.
* This involves creating an array of possible directions and assigning a value for the desireability of each direction.
@@ -87,6 +92,8 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
private object _obstacles = new();
private int _activeSteeringCount;
public override void Initialize()
{
base.Initialize();
@@ -244,12 +251,15 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
};
var curTime = _timing.CurTime;
_activeSteeringCount = 0;
Parallel.For(0, index, options, i =>
{
var (uid, steering, mover, xform) = npcs[i];
Steer(uid, steering, mover, xform, frameTime, curTime);
});
ActiveSteeringGauge.Set(_activeSteeringCount);
if (_subscribedSessions.Count > 0)
{
@@ -324,6 +334,8 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
return;
}
Interlocked.Increment(ref _activeSteeringCount);
var agentRadius = steering.Radius;
var worldPos = _transform.GetWorldPosition(xform);
var (layer, mask) = _physics.GetHardCollision(uid);

View File

@@ -8,6 +8,7 @@ using Content.Shared.Mobs;
using Content.Shared.Mobs.Systems;
using Content.Shared.NPC;
using Content.Shared.NPC.Systems;
using Prometheus;
using Robust.Server.GameObjects;
using Robust.Shared.Configuration;
using Robust.Shared.Player;
@@ -19,6 +20,10 @@ namespace Content.Server.NPC.Systems
/// </summary>
public sealed partial class NPCSystem : EntitySystem
{
private static readonly Gauge ActiveGauge = Metrics.CreateGauge(
"npc_active_count",
"Amount of NPCs that are actively processing");
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly HTNSystem _htn = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
@@ -138,6 +143,8 @@ namespace Content.Server.NPC.Systems
// Add your system here.
_htn.UpdateNPC(ref _count, _maxUpdates, frameTime);
ActiveGauge.Set(Count<ActiveNPCComponent>());
}
public void OnMobStateChange(EntityUid uid, HTNComponent component, MobStateChangedEvent args)

View File

@@ -7,6 +7,7 @@ using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Shuttles.Components;
using Content.Shared.Shuttles.Systems;
using Prometheus;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
using DroneConsoleComponent = Content.Server.Shuttles.DroneConsoleComponent;
@@ -17,6 +18,10 @@ namespace Content.Server.Physics.Controllers;
public sealed class MoverController : SharedMoverController
{
private static readonly Gauge ActiveMoverGauge = Metrics.CreateGauge(
"physics_active_mover_count",
"Active amount of InputMovers being processed by MoverController");
[Dependency] private readonly ThrusterSystem _thruster = default!;
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
@@ -97,6 +102,8 @@ public sealed class MoverController : SharedMoverController
HandleMobMovement(mover, frameTime);
}
ActiveMoverGauge.Set(_movers.Count);
HandleShuttleMovement(frameTime);
}

View File

@@ -19,9 +19,21 @@ namespace Content.Server.Speech.EntitySystems
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ILocalizationManager _loc = default!;
private readonly Dictionary<ProtoId<ReplacementAccentPrototype>, (Regex regex, string replacement)[]>
_cachedReplacements = new();
public override void Initialize()
{
SubscribeLocalEvent<ReplacementAccentComponent, AccentGetEvent>(OnAccent);
_proto.PrototypesReloaded += OnPrototypesReloaded;
}
public override void Shutdown()
{
base.Shutdown();
_proto.PrototypesReloaded -= OnPrototypesReloaded;
}
private void OnAccent(EntityUid uid, ReplacementAccentComponent component, AccentGetEvent args)
@@ -48,27 +60,22 @@ namespace Content.Server.Speech.EntitySystems
return prototype.FullReplacements.Length != 0 ? Loc.GetString(_random.Pick(prototype.FullReplacements)) : "";
}
if (prototype.WordReplacements == null)
return message;
// Prohibition of repeated word replacements.
// All replaced words placed in the final message are placed here as dashes (___) with the same length.
// The regex search goes through this buffer message, from which the already replaced words are crossed out,
// ensuring that the replaced words cannot be replaced again.
var maskMessage = message;
foreach (var (first, replace) in prototype.WordReplacements)
foreach (var (regex, replace) in GetCachedReplacements(prototype))
{
var f = _loc.GetString(first);
var r = _loc.GetString(replace);
// this is kind of slow but its not that bad
// essentially: go over all matches, try to match capitalization where possible, then replace
// rather than using regex.replace
for (int i = Regex.Count(maskMessage, $@"(?<!\w){f}(?!\w)", RegexOptions.IgnoreCase); i > 0; i--)
for (int i = regex.Count(maskMessage); i > 0; i--)
{
// fetch the match again as the character indices may have changed
Match match = Regex.Match(maskMessage, $@"(?<!\w){f}(?!\w)", RegexOptions.IgnoreCase);
var replacement = r;
Match match = regex.Match(maskMessage);
var replacement = replace;
// Intelligently replace capitalization
// two cases where we will do so:
@@ -98,5 +105,40 @@ namespace Content.Server.Speech.EntitySystems
return message;
}
private (Regex regex, string replacement)[] GetCachedReplacements(ReplacementAccentPrototype prototype)
{
if (!_cachedReplacements.TryGetValue(prototype.ID, out var replacements))
{
replacements = GenerateCachedReplacements(prototype);
_cachedReplacements.Add(prototype.ID, replacements);
}
return replacements;
}
private (Regex regex, string replacement)[] GenerateCachedReplacements(ReplacementAccentPrototype prototype)
{
if (prototype.WordReplacements is not { } replacements)
return [];
return replacements.Select(kv =>
{
var (first, replace) = kv;
var firstLoc = _loc.GetString(first);
var replaceLoc = _loc.GetString(replace);
var regex = new Regex($@"(?<!\w){firstLoc}(?!\w)", RegexOptions.IgnoreCase);
return (regex, replaceLoc);
})
.ToArray();
}
private void OnPrototypesReloaded(PrototypesReloadedEventArgs obj)
{
_cachedReplacements.Clear();
}
}
}

View File

@@ -68,9 +68,13 @@ public sealed class TipsSystem : EntitySystem
{
return args.Length switch
{
1 => CompletionResult.FromHintOptions(CompletionHelper.SessionNames(), Loc.GetString("cmd-tippy-auto-1")),
1 => CompletionResult.FromHintOptions(
CompletionHelper.SessionNames(players: _playerManager),
Loc.GetString("cmd-tippy-auto-1")),
2 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-2")),
3 => CompletionResult.FromHintOptions(CompletionHelper.PrototypeIDs<EntityPrototype>(), Loc.GetString("cmd-tippy-auto-3")),
3 => CompletionResult.FromHintOptions(
CompletionHelper.PrototypeIdsLimited<EntityPrototype>(args[2], _prototype),
Loc.GetString("cmd-tippy-auto-3")),
4 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-4")),
5 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-5")),
6 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-6")),

View File

@@ -388,9 +388,21 @@ public enum AtmosMonitorLimitType //<todo.eoin Very similar to the above...
// fields you can find this prototype in
public enum AtmosMonitorThresholdType
{
Temperature,
Pressure,
Gas
Temperature = 0,
Pressure = 1,
Gas = 2
}
/// <summary>
/// Bitflags version of <see cref="AtmosMonitorThresholdType"/>
/// </summary>
[Flags]
public enum AtmosMonitorThresholdTypeFlags
{
None = 0,
Temperature = 1 << 0,
Pressure = 1 << 1,
Gas = 1 << 2,
}
[Serializable, NetSerializable]

View File

@@ -36,5 +36,5 @@ public sealed partial class CCVars : CVars
/// Set to true to disable parallel processing in the pow3r solver.
/// </summary>
public static readonly CVarDef<bool> DebugPow3rDisableParallel =
CVarDef.Create("debug.pow3r_disable_parallel", true, CVar.SERVERONLY);
CVarDef.Create("debug.pow3r_disable_parallel", false, CVar.SERVERONLY);
}

View File

@@ -72,6 +72,8 @@ public abstract partial class SharedMoverController : VirtualController
/// </summary>
public Dictionary<EntityUid, bool> UsedMobMovement = new();
private readonly HashSet<EntityUid> _aroundColliderSet = [];
public override void Initialize()
{
UpdatesBefore.Add(typeof(TileFrictionController));
@@ -454,7 +456,9 @@ public abstract partial class SharedMoverController : VirtualController
var (uid, collider, mover, transform) = entity;
var enlargedAABB = _lookup.GetWorldAABB(entity.Owner, transform).Enlarged(mover.GrabRange);
foreach (var otherEntity in lookupSystem.GetEntitiesIntersecting(transform.MapID, enlargedAABB))
_aroundColliderSet.Clear();
lookupSystem.GetEntitiesIntersecting(transform.MapID, enlargedAABB, _aroundColliderSet);
foreach (var otherEntity in _aroundColliderSet)
{
if (otherEntity == uid)
continue; // Don't try to push off of yourself!

View File

@@ -14,6 +14,3 @@ force_client_hud_version_watermark = true
[chat]
motd = "\n########################################################\n\n[font size=17]This is a test server. You can play with the newest changes to the game, but these [color=red]changes may not be final or stable[/color], and may be reverted. Please report bugs via our GitHub, forum, or community Discord.[/font]\n\n########################################################\n"
[debug]
pow3r_disable_parallel = false