Just to tag on about chat history here - we don’t really have a true chat history per-se, more just a log of messages the player has been there to observe since connecting.
A chat history like how IRC channels work is totally possible for the lifetime of the server, however, it’s a cause for a security concern, memory usage concern and/or an overwhelming of a player’s network connection. I considered adding Chat History to WinryChat because it’s something I was used to, but there’s a few issues with the implementation of this feature that I’ve considered.
I am absolutely sure ROBLOX’s staff have done their best to find a way to handle this properly - they likely did it before, and they’ll likely do it again because they’ve got an all-star team of computer scientists behind them.
Feel free to correct or question everything here, I’m just presenting things as if they’re worst-case scenarios, not everyday concerns. This is me overthinking the entire problem for the sake of making sure there’s a record of this somewhere.
The constants - Problems that exist in every method
These are problems which, while considerations, aren’t exactly major issues. This section is just here to go “this would be a worst case scenario - i hope we never run into it” and provide a preamble to a generally workable solution.
-
Constant 1. We have to somehow verify the receiving player’s device has enough memory to handle all messages. (We can’t, we aren’t allowed low-level access to devices).
-
Constant 2. We have to pace the creation of messages accordingly so that the receiving player’s device doesn’t freeze or stutter. (Processing Speed of a CPU).
-
Constant 3. We have to account for if the player has a bandwidth weaker than 8 Megabits (8Mb/s) This is not uncommon on 3G and 4G networks on mobile devices in countries with access to ROBLOX and while we can get away with 4-8 mb/s, we should really try make it as least impactful on the player’s network as possible. (Networking & Resources Concern)
ROBLOX is a game that should be able to run on a variety of devices. Whether it runs well on some… well, that’s another topic for others to delve into. If you’re making a static environment that doesn’t have any major scripting outside of the chat system, then this really shouldn’t be a worry. This is likely only a concern for games that are REALLY hammering a player’s device.
We are talking in the kilobytes of storage for TextLabels (and buttons for whispers for each user-name) for each message at most. 1-2kb maximum. A message alone isn’t enough for us to worry about on most experiences, it’s an insignificant transient object for the lifetime of the application. But we’ve got a resource to worry about - Device Memory (RAM).
RAM is admittedly only a concern if we’re creating and storing thousands, of message frames on the client. It’s entirely possible for a message log to meet 50MB on it’s own if a server has lasted long enough (social games can no doubt hit this if a server lives long enough). We can minimize this by say using numerical tables instead to associate words to numbers or hexadecimal keys, but that itself is an engineering effort all of it’s own.
Memory Usage on it’s own is fine. Everything we do uses memory in the ROBLOX engine. How much depends on how many things we’re doing. Just food for thought on the general problem of memory management. For developers like myself and Chilly, we absolutely should consider the resource costs (Processing, Rendering & Memory Usage) of our UI, players’ messages & any intermediary content/objects. For a Chat History feature, it’s a foundational concern… even if an excessive one when we’re dealing in mere kilobytes.
As for pacing message creation - not all CPUs are made equally. Android and iOS devices are especially known for having some really powerful, and really power-efficient, CPUs that can push through a lot nowadays. That said, ROBLOX still supports (even if only partly), old models powered by old ARM Chipsets. Most, if not all, are multi-core 1.0-1.6GHz chips with performance and efficiency cores. That being the case… then Parallel Luau would be wonderful for this type of heavy-compute task.. But it doesn’t support parallel object creation - dangit!
So we’re stuck in serial processing for the creation of messages - the bulk of our workload. We’d need to make it so that messages are created either when the player scrolls up far enough, or so that messages are created in bursts until all are created. Realistically, we’d cache the data for when it is needed, and load messages in chunks because we really do not want to be hogging memory from a user’s device for something they’ll never read.
Lastly for networking bandwidth, we can’t just send a bunch of messages at once nor can we send them in measured bursts until the player client reports that it has all messages. Well… we can, but it won’t be pretty for some users. We’d be doing the burst method of say 16 messages after the user scrolls up some point.
Our solution is to then only send what the player requests, not what we think they will want to see immediately. There are tons of ISPs and Mobile Carriers that cap data for a mobile/wired contract, cap bandwidth speeds and so much more around the world. It’s irresponsible of us to be sending needless data to players that may already struggle to enjoy life on the internet. No resource is truly free and everything has a cost at the end of the day.
Now onto the bad method…
Peer-To-Peer Message History - No.
This is the first interpretation that came to mind from:
The major issues with this come straight away in the method header.
-
You rely on one or more players to broadcast the contents of their chat history to the server, and have the server broadcast to the new player.
-
You trust the output of each player or filter each message description again as a precaution.
-
One or more players already on the server and the new player joining expend bandwidth to send and retrieve the history.
By doing peer-to-peer replication of chat history, you essentially have to verify who has longest lifetime on the server (or the most complete message history). Or you could do it at complete random and end up trying to get history parity with a person who only joined moments before the new player did.
If you want to get someone with the most complete message history, you’ll have to filter through all players through some (admittedly menial) back and forth communication. Then that player has to asynchronously send a bunch of packets to the server for all messages.
If you want to get someone at random then you may get someone with an incomplete message history who has also just joined the server. In which case, their incomplete message history is used as a template for the new player. You can try resolve this by attempting to pace the replication of messages to match or be slower than that of the other player (a complete fools errand for many reasons) or break away with new player not being able to see the full chat history.
Once you have your player to replicate from, then comes the replication itself. Do you want to trust that player’s log as a source of truth? Absolutely not! Players can still use easy-to-access tools to modify objects, and although it’s not worth the hassle to modify message logs, it can still be done. Instead, we’ll likely need to filter again… but what with? Messages from some players may not even be able to be refiltered because that player already left the game, which is, at the time of writing, a limitation of TextService.FilterStringAsync.
We can just bypass filtering altogether, trust the source and deliver previously filtered messages as a “trusted resouce”… but- oh noes… We may break ToS! We can’t verify if any (or all) given messages are safe or appropriate for all ages - which is why we’re in this mess of a new chat system migration to begin with.
In this case, we’re instigating the issues of two of the constants potentially twice. We’re going to possibly make two households with poor connections struggle. We’d using bandwidth for data we really don’t need to use to make this feature work for no good reason. We’d also be increasing the processing power (and power draw) for two or more devices at once… that’s really not good.
Of course, no one in their right mind should chose this option. Needless risk for no meaningful gain.
Players Rebroadcast Sent Messages - Technically Possible, but an awful User Experience
This is the second interpretation of the following quote:
This is more about having players store a log of every message they sent since they joined from within their Chat UI (this is really the only place they can store the raw string).
However, we’ve got three major issues:
-
We’re flooding chat with messages we don’t need to for each player.
-
For each player rebroadcasting messages, they very likely will be Floodchecked, along with any message that player tries to send while this process occurs.
-
If a player leaves during this process, or left before this process, while another user they were having a conversation with still remains on the server… we’re sending half of a conversation (if that) to the recieving player.
The first issue is that this solution would affect all players’ Chats. You won’t be whispering the messages to one player, you’ll be outright flooding chat with every conversation each player had since they first joined that server. Not only would you wash out players’ current conversations, you would make it nigh impossible for players to remember what they were talking about. It might even lead to players getting reported for spamming chat (not good at all).
The second issue is that by handling a chat history this way, you are having each player broadcast through a TextChannel again. For each player that is having to rebroadcast their messages, they will very likely be floodchecked because they will likely have more messages than is permitted within the floodcheck restrictions. Even if you find a way around the first issue, this isn’t just bad for the chat experience in general. It’s bad for the player as now they have to wait for the chat system to rebroadcast everything between floodchecking periods. They won’t be able to have fun or continue socialising with friends.
Lastly, there’s a good chance that players will just leave before or during this process and new players will miss context to things said in chat. We’ve all been there once to enjoy someone who has been spinning a good tale in chat. But if we only have the reactions as the user, and not the story, we might just view the server as full of fake players or bot accounts. And if a player leaves during this process, we’ll have the issue of only partial context. This one’s purely a UX issue, but it’s an integral one on why this can’t work.
Server Cached Messages - Possible, but a Pain.
This is possible, but the implementation will vary based on the programmer and their experience with networking & data management. If you want to just get things up and running regardless of the server, this should do.
-
A Server’s lifetime varies. One server may last 4 hours, while another lasts 37 minutes. The amount of text logs generated can also wildly vary, and we can’t guarantee a consistent timing of each message log - though we can get close with the help of DateTime.FromUnixTimestampMillis in exchange for a byte or few.
-
We’re either making messages visible to all clients, or using a ModuleScript to store all filtered messages.
By doing this, we can get a message history going… we can’t get it going for all players though. We’re effectively making a blanket system and hoping it’s a one-size-fits-all thing. By doing this, we’re also using more server-side resources. Just like our devices, ROBLOX’s servers also have allocated memory for the virtual machines used to run our experiences.
This is completely fine to do if you’re making an error logger that anyone can see in a lobby or a pop-up UI to help report issues. After all, a global server log for this is really helpful. The problem for chat history comes into how do we store them. We can store them in ReplicatedStorage and offload the workload to ROBLOX’s core engine (why?), or in a ModuleScript.
The ReplicatedStorage option is outright terrible for message security and for wasting resources. We’ll be making messages visible to all clients for all channels, even if that player isn’t connected to that channel. We’ll be wasting the resources of every player and the resources of the server at the same time because we’re admittedly being lazy and misusing the intended purpose of ReplicatedStorage (much like people did with Lighting before ReplicatedStorage was a thing).
The ModuleScript option is good, especially for the data sharing properties of ModuleScripts in a thread, but is a complex task. With this method, you’ll need to send the messages to the receiving player(s) for only the channels they’re connected to. When any player joins a channel, they’ll also need the messages. This also means re-sending the same messages if a player leaves, then re-joins a channel.
For there to not be a significant enough impact on server performance, you should be able to make this work with an Actor and Parallel Luau, as this would be a task that’s purely data-driven without Instances. It might even be a good use-case for a SharedTable, though someone correct me if I’m wrong there.
As for the consistent timing of each message log, this is more of a niche issue. We can use milliseconds to get a pretty good idea of where to place the message in an ordered list. The only problem is there may genuinely be times where two messages are somehow (however unlikely) sent at the same time. That’s nanoseconds apart, really. You may have two separate conversations running on two separate things and if the message order is wrong on one client, then it’ll look strange.
Needless to say, the ModuleScript option is more desirable because we’ll be able to manage how messages are sent, who they’re sent to and how they’re organized. Just at the cost of server memory.
Final Notes
If I were to approach this for a project, the main components I’d look at are:
-
Parallel Luau & Multi-threading for asynchronous processing & sharing of chat history on request
-
A SharedTable or ModuleScript containing all the messages ever sent, being logged in real-time, so that we can have one or more actors on the task.
-
RemoteEvents to faciliate server-client communication explicitly for Message History. We can leave the messages already being sent through TextChatService alone as they’re already being managed just fine.
-
A way to compress the data as finely as possible to use the least amount of memory with the least data loss.
-
A Chat UI that can detect when a player is at the top of a channel. That way, we can ask the server for more messages to read if it can. There is a hacky way to do this with an upside down scrolling frame, but I’d try find an approximated way instead.
Of course, there are other ways to do message history that people may come up with - or have come up with. It’s a very complex, technical feature that requires server and client scripting. I personally have no plans to introduce this into WinryChat - it’s client-sided only and is meant to be there so any developer can drop it into their game without any scripting issues. I also have no reason to implement this in any other project right now as there doesn’t appear to be any demand for it.
If you wish to further explore this idea, feel free to… just be warned. Here be dragons.
I have been writing this for 4 hours straight and trying to correct what’s over the top, incorrect, or just unnecessary. But someone will definitely find something later and call me out on it or something. This is an intentional exaggeration of some of the concerns with design & logic so that it’s a bit more clearer on the how and why of a Chat History than a “just add it now”.
I’ll also probably regret going on a tangent, so uh- yeah thanks for reading and please be kind… I’m very sleepy now.