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

@@ -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();
}
}
}