I am trying to create a script that shows the recent chat history to a user from before they joined the experience (like the legacy Lua Chat System does).
DisplaySystemMessage
seems to be my only option here. I’ve been trying to use it to show all of the recent messages as soon as a user joins, and have those messages displayed in order.
In this thread, I am going to simplify the problem by just using numbers rather than message logs.
Consider this code:
task.wait(1)
local defaultChannel = game:GetService("TextChatService"):WaitForChild("TextChannels"):WaitForChild("RBXGeneral")
for i = 1, 10 do
defaultChannel:DisplaySystemMessage("Test " .. tostring(i))
print(i)
end
Here is how the chat looks after running that code:
Output:
Even tossing in a task.wait()
doesn’t do the trick to keep the chat in proper order:
for i = 1, 10 do
defaultChannel:DisplaySystemMessage("Test " .. tostring(i))
print(i)
task.wait()
end
I tried an alternative approach to force the messages into appearing in the right order. The idea is that the TextChatService.MessageReceived
or TextChannel.MessageReceived
events would be fired as soon as the message appears in chat, and so my loop would just yield for the latest message to have appeared before moving to display the next message.
Here is the new code:
local logSignature = game:GetService("HttpService"):GenerateGUID(false)
-- attach logSignature to joining messages so they're distinct from regular ones
local logIndex = 0
-- logIndex keeps track of the latest sent message
local latestIndex = logIndex
-- latestIndex keeps track of the latest received message
local loggedMessageLoaded = Instance.new("BindableEvent")
-- the loop uses this to yield for the latest message to be received
defaultChannel.MessageReceived:Connect(function(TextChatMessage)
local logData = string.split(TextChatMessage.Metadata, " ")
local signature, index = logData[1], tonumber(logData[2])
if signature == logSignature and index == logIndex then
print("MESSAGE RECEIVED:", index)
latestIndex = logIndex
loggedMessageLoaded:Fire()
end
end)
task.wait(1)
for i = 1, 10 do
logIndex += 1
local metadata = string.format("%s %s", logSignature, logIndex)
defaultChannel:DisplaySystemMessage("Test " .. tostring(i), metadata)
print("MESSAGE SENT:", logIndex)
if latestIndex ~= logIndex then
print("Yielding for message", logIndex, "to be received...")
loggedMessageLoaded.Event:Wait()
-- don't go to the next message until the last one has loaded
end
end
This does not work. Sure, the output looks as if everything is in order and nothing even needs to yield:
Everything is still out of order in the chat, though!
As an alternative to TextChannel.MessageReceived
, there is TextChatService.OnIncomingMessage
— this could be used to intercept chat messages and ensure the correct order before return
ing to allow those messages to be displayed in the chat. However, this is not ideal:
I wouldn’t want to override any other modules in the experience which might be using that callback function. Regardless, when I tried this it still had the same problem MessageReceived
did — the chat did not match the output.
This behavior suggests a few things:
-
TextChannel:DisplaySystemMessage()
is asynchronous, not yielding for the message to appear in chat before allowing the code to move on -
TextChannel.MessageReceived
does not fire when messages actually appear in the chat -
When
TextChatService.OnIncomingMessage
returns aTextChatMessageProperties
instance, it takes some time before it’s displayed it the chat
There doesn’t seem to be any remaining ways to detect when a message actually appears in chat, and there is simply no way to ensure message order. I could use artificial yields by putting a task.wait(0.1)
at the bottom of the loop, but there could be chat messages by other users in-game while our system messages are still yielding. That messes up the intended effect of all the logged messages being side-by-side. Additionally, using artificial waits to ensure order is just bad practice — it creates a race condition, and may not always work, as seen with the task.wait()
example at the top of this thread.
It would make more sense to me if DisplaySystemMessage
yielded until the message appeared in the chat window. More events and also more explicit naming of events would help here as well.
Repro: dump many chats on join.rbxm (2.2 KB)
-
Place the folder in
game.ReplicatedStorage
. -
Ensure
game.TextChatService.ChatVersion
is set toTextChatService
. -
When testing any of the scripts, ensure only one is enabled at a time.
-
Ensure
game.Players.CharacterAutoLoads
is disabled for this test just in case it slows down the loading time. -
If nothing shows in the chat, just increase the yielding time from right before where the loop begins. Try
task.wait(5)
, for example.