Repository navigation
perf: format each chat message once instead of once per recipient - #105
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WareZ57
approved these changes
Oct 28, 2026
WareZ57
left a comment
There was a problem hiding this comment.
Checked what the formatting depends on: the author's tag and colors, IsClientEnabled() (author flags), the replace list, SetGlobalTransTarget(g_msgAuthor) and CFormatColor(..., g_msgAuthor). All of them depend on the author, never on the recipient. g_msgIsTeammate is set in the say/say_team command listener (Command_Say) before any SayText2, and isn't touched here. So within one Host_Say loop every copy builds the same g_msgFinal, and a hit leaves the globals exactly as a full run would.
Edge cases look fine:
- Cache in-fields are compared after
TrimString, i.e. trimmed text against trimmed text. - If formatting throws (e.g. a missing phrase),
g_bMsgCacheValidstays false, so the next copy runs the full path again, as before. - Two messages from the same player in the same tick are separated by
player_say, and messages from different players differ ing_msgAuthor. - SayText2 sent by other plugins without
USERMSG_BLOCKHOOKSwith different text per recipient (per-client%t) is a miss, and identical text is a correct hit.
LGTM.
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.
Summary
On css-ze,
Hook_UserMessage(the SayText2 hook) uses 152 s of CPU over 7 days in our production profiler: 6.76M calls, 22.6 µs per call. Over the same periodEvent_PlayerSayran 135k times, so the hook runs about 50 times per chat message.That is how the game sends chat:
Host_Saysends a separate SayText2 to each recipient, then firesplayer_say. Each of these copies runs the full formatting in our hook again: tag and color key lookups, the replace list,%ttranslation, andCFormatColor()on the final string. Each copy builds the sameg_msgFinal, because the result only depends on the message fields and the author's settings, not on the recipient.Event_PlayerSaythen sends that one result to everyone.Changes:
Hook_UserMessagestores what it read (author, chat flag, name, sender, trimmed text) and the formatted name/sender/text. A later call in the same tick with the same fields restores these values and returnsPlugin_Handledright away.g_msgFinalalready holds the right result.Event_PlayerSay(the end of a chat message) clears this cache. The next message is always formatted again, even if the player sends the same text again in the same tick.Expected: about 49 out of 50 calls become a few string compares and copies, so most of the hook's cost goes away.
Behaviour
Unchanged. A cache hit leaves
g_msgName,g_msgSender,g_msgTextandg_msgFinalexactly as a full run would, and returns the same value (Plugin_Handled). The formatting reads nothing that can change between the copies of one message: they are sent in a loop with no commands or events in between. The cache only applies within the same tick, so it is also safe for SayText2 messages sent by other plugins withoutUSERMSG_BLOCKHOOKS, which are not followed byplayer_say.Test plan
say_team,/me,>greentext, replace-list words and dead chat still shows the same for every recipient. Check that a SelfMute ignore still hides the message. Then compareHook_UserMessagein the profiler.🤖 Generated with Claude Code