Hey everyone! Today, on this eventful day, I bring ye more news about the goings-on inside of my head, as I attempt to persuade you that I am not in fact an AI attempting to trick you into thinking that I am human - of course, I'm joking... but am I? Or was that a double bluff? Who knows!
Now you may be asking yourselves, why is this an eventful day? Nothing ever happens on the 29th of April! Well, you see, that was true only up until last year... because Tabletop Club officially released on the 29th of April, 2023!
Unfortunately, I only realised the game's birthday was coming up a few days ago, and so I don't have any special events planned or an update for the game scheduled to release, so instead I've decided to word vomit through the internet in your general direction with details about what I've been up to, and what I've got planned for the future, as well as a question for you all!
More Multiplayer Improvements
In the last devlog, I mentioned that I was refactoring the multiplayer code from the ground up in order to make lobbies less error prone and to give more feedback when something went wrong. Now that the new foundations have been laid, I've started work on re-writing the in-game systems themselves with the new improvements.
The first thing I did was create a new UI for the player list at the top-right corner of the screen. Currently, it looks like this:
It serves its purpose in showing the list of players currently in the room, but it could definitely look a lot nicer. Fortunately, now it does!
- The room code has been made a lot bigger, and spacing has been added between the letters for easier legibility.
- The "Hide Room Code" button that previously took up a lot of screen real estate is now a small icon button.
- A new copy button has been added, which when pressed, will put the room code in your clipboard so that you can paste it to other players.
- Instead of as just text, each player is now represented with a coloured button, with each button being the same width. The colour of the text and the border will automatically be adjusted depending on the player's set colour.
- The host of the room is now shown with a crown icon alongside their name.
I've also been re-working basic object operations, like locking them in place or deleting them, in order to reduce the amount of network traffic that is sent between the client and the host.
Currently, if you were to select 100 objects and delete them, the client would send 100 different packets to the host, each with the "delete" command and the ID of the object. For small amounts of objects, this works well, but if you end up deleting thousands of objects at once (unlikely, but possible), then there is a lot of redundant network traffic being sent.
Now, only one packet is sent from the client to the host, with the same "delete" command, but this time with a list of object IDs - this will significantly improve the amount of traffic sent to the host, and thus the operation should be a lot faster now when it is being done on a lot of objects. I'll be doing optimisations like this across the entire game when I am able to.
As of right now, I am currently trying to figure out a solution for a fundamental problem that plagues the game, although most of you might not have come across it... If you have happened to import a lot of custom assets, eventually you will get this warning when you host or join a multiplayer game:
For reference, the AssetDB is a database the game uses to keep track of what assets have been imported. Usually when a client joins a room, the host will send a compressed version of it's AssetDB to the client, and the client will compare it to it's own to make sure there aren't any discrepancies. If there are, the player is made aware and the game will offer to download any missing assets from the host if they want to.
The only issue is, if the compressed data is bigger than 64 KiB (which is equivalent to 65536 bytes), then it is impossible for the host to send the data to the client. This is because of a hard-coded value within the WebRTC library that the game uses, which can't be changed in the project settings, even though it is shown as an option.
To get around this and allow the clients to send packets bigger than 64 KiB, I have one of two options: either re-compile the WebRTC library with a bigger hard-coded value like 1 or 2 MiB, or add functionality within the game itself to split data into multiple chunks and send it chunk-by-chunk. Since I've had a pretty bad experience in the past trying to compile the library myself, I've decided to try and go for the latter.
However, this decision would open up a whole other bag of worms. For example, what would happen if a player joins a room while data is being transferred? What if a transfer is happening, and the recipient leaves the game during it?
After a lot of thinking, I think I've come up with a two-part system that will work. The first is what I've just described, which is a system that will break large amounts of data into multiple parts and send it chunk by chunk. The second is what I call a "state freeze" system.
I'll go through an example of how I'm thinking it will work: Say for example, two players are currently in a multiplayer game, and there are a lot of objects on the table and they've been painting on it as well. Let's say a third player wants to join the game - they need to get the current state of the game from the host, but it's a large amount of data to send, so it needs to be sent in chunks.
The issue that arises in this example is that in between the host saving the current state as data and it being sent to the new player, the client that was originally in the game can modify the state of the game during the transfer, either by moving an object, deleting it, changing it's colour, etc.
That's where the state freeze comes in. Before the host starts the transfer of data, the host will tell all of the clients to "freeze" their game. This would involve the players not being able to do anything on the table, and most likely a "Loading..." panel coming up on the screen. This way, it is almost guaranteed that the clients will not be able to modify the state until after the new player has received it in full.
That is essentially what I am currently working on at the moment! By solving this issue, hopefully the host will be able to send it's AssetDB to clients no matter the size, and there can be a ton of objects on the table, and it should no longer matter as clients should always be able to receive it.
Master Server 2: Electric Boogaloo
Speaking of multiplayer, another aspect of it that I'm planning on re-writing is the master server. For those that don't know, the master server is a program that keeps track of multiplayer rooms, who is hosting them, and which players are in them. So whenever you either host a game, or join someone else's game in Tabletop Club, the game will be talking to the master server.
The current code for the master server is based on code from Godot's own example repository. It definitely works, as lots of players have created and joined rooms, but there's a few potential issues that can arise.
Firstly, it's written in Javascript. I have... opinions... about Javascript as a language, but I will keep myself calm and explain my utter hatred for the programming language in another devlog... maybe.
Secondly, and probably more importantly, the server is single-threaded. This basically means that the server can only deal with one request at a time. So far, the game doesn't really have more than two rooms going at any given time, so this isn't really an issue currently, but if for whatever reason the game gets super popular, and a ton of people start creating and joining rooms at the same time, it will start to slow down the server a ton.
Thirdly, the server has limits for the number of rooms that can opened and players that can join hard-coded into the script itself. This means that if I ever need to increase the limit of how many players can join, then I would need to stop the server, change the source code, then run it again, which is very much less than ideal, as anyone that was in a multiplayer game in that time would see themselves kicked out when the server was shut down.
So, my solution for this is to switch from using what might be my least favourite programming language to my all-time favourite: Rust. For those that don't know, Rust is very similar to languages like C or C++, but it has a ton of features that make it really hard to write code that can break during runtime.
In particular, I am using what's called an "asynchronous runtime library" called Tokio, which is just a really fancy way of saying it can do multiple things at a time in a clever way. So far, I've managed to get it so that it can detect connections from clients, and it can read whether they want to host or join a game.
However, the primary reason I am now writing a new master server using Rust is for it's error handling. In essence, with the way you write code in Rust, you are forced to deal with all of the potential errors that can occur with an operation. For example, if a connection to a client drops suddenly, or if a message fails to send. For this reason alone, I am already a lot happier with the code compared to the current version, as I know how the server will react now if something goes wrong.
Plus, I have added a way for the server to repeatedly check it's configuration file for updates, and if any of the values change, it will update it's runtime values accordingly!
This means I can, for example, increase the maximum number of rooms, or the maximum number of players per room, without having to restart the server.
Currently this code is in a private repository, but once it's ready for testing I will make it public and release it under the open-source MIT license.
Back To The Future...
Shortly after I've published this devlog, I will be getting back to solving the packet size limit issue that I was discussing before, and after that it will be getting the host to sync it's state with new clients again!
In the long term, I'll be continuing to go through the game and improve it across the board, fixing fundamental issues that are in the game currently, and by adding small quality-of-life changes where I can.
Once thing that I'm also planning on doing that I haven't mentioned in any of the other devlogs is that of a dedicated forum for Tabletop Club. Currently, the only way to receive support for the game is by either joining the official Discord or Matrix servers, or by messaging me directly via email or Mastodon. While these options work okay, I would also like to have an option available to people that don't necessarily want to create an account. So, once I have a dedicated server up and running, I am planning to use the open-source platform Discourse to host a community forum for Tabletop Club that is accessible to everyone, which can be used to ask for support, suggest new features and improvements, and to potentially share community-made asset packs with others, which is one of the big features that I think the game needs most.
Speaking of big features, since all of this work is for the upcoming v0.2.0 update, I want to try and add at least one big feature as well, on top of all of the re-writes and smaller changes. An online database of asset packs was one of them, but the potential forum mentioned previously would be a good temporary alternative, I feel. The remaining highly-requested features have been the following:
- Automation using the Lua scripting language.
- An in-game asset creator.
- Migration to Godot 4.
- Releases on more platforms, e.g. The Web, Android, iOS.
So to finish, I want to get your opinions: Which of these features would you most like to see in the first major update for Tabletop Club?
Please let me know what you think in the comments! You can also message me on either Twitter or Mastodon, or you can discuss it with the community in either the official Discord or Matrix servers.
And before I go, I'd like to plug my new Ko-fi page once again - if you want to support the project and help the v0.2.0 update come out quicker, and to help chip in for the costs of hosting a dedicated server in the future, any and all donations would be extremely helpful!