0

I have been building a card game web app with firebase. Because firestore charges by number of reads/writes, I have only been updating the opponents of your plays when you place a card, so other players just see a card you moved pop into its new position -- one write, one read.

I momentarily tested updating the other players whenever you are moving a card, such that they can see you moving cards around in real time, not only the final result when you place it in a new location. This results in hundreds of writes/reads per second as the onMouseMove event fires continuously.

This is too expensive to leave, so I undid it. But I'm disappointed and I feel there must be a better way, but I am too new to this to know what it is.

How can I affordably transmit very large amounts of update events between players?

To give an idea: assume 1000 games per day, each could invoke 500k to 1million move events (each card drag fires off hundreds, and there are up to 6 players!), so I would be transmitting a billion or more small pieces of information per day. The numbers sound large but this is just for a moderately successful small web game and pretty realistic. A lot of webgames handle this kind of traffic daily no problem.

How is it done affordably?

6
  • 2
    Why do you need to persist every single pixel-by-pixel move? What's going to go horribly wrong if one of those gets lost and isn't replayed? Nov 15, 2020 at 22:59
  • How else would the movement be smooth? If it stutters or jumps that doesn't look very good... Nov 15, 2020 at 23:00
  • 3
    Probably stupid but shouldn't transient and inconsequential data be kept in plain old memory? A Game object can maintain it and send it to clients as needed.
    – Kain0_0
    Nov 16, 2020 at 2:08
  • 1
    I really really don't see why a card game needs any sort of detailed statefulness or eventing around moving a card. It seems very overkill to me. If the game focuses on moving cards from one zone to another, I'd expected to two relevant events: pickup and drop ... where card and zone are arguments. When a card is "picked up", other clients can animate the card lifting off the table. When it's "dropped", just animate an arc from the pickup zone to the drop zone and perform the game logic. If exact coordinate matter in the drop zone, send them with the drop event.
    – svidgen
    Nov 16, 2020 at 19:56

2 Answers 2

2

It sounds like you have a fundamental architectural problem here.

You describe using a database to communicate between different players' sessions. In a comment, you say that everything must be stored in a database because it's the only way to persist the game state if the user closes the tab and opens a new one. It sounds like you are using a framework which forces all sessions to be isolated from each other.

This isolation is not what you want for a game server! For a game server, you want to be able to just send things, when they happen. You want to be able to write this (pardon the lack of OOP):

void onCardMoved(Connection playerConnection, int playerIndex, int cardIndex, int x, int y) {
    for(Connection otherConnection : c.game.connections) {
        // Don't send the message back to the same page that initiated the action.
        // Note that a player could have more than one connection (several tabs open).
        if(otherConnection != playerConnection)
            otherConnection.send(new CardMovedMessage(playerIndex, cardIndex, x, y));
    }
}

You simply cannot write this type of code in this type of framework, no matter how much you want to, because there is no way for an event processed on one connection to directly affect the other active connections.

I'm not familiar with Firebase. It looks like an auto-scaling system where your app runs on multiple servers. Therefore, I assume there's no way to directly access other players' connections, because they might be connected to different servers. Consider using a traditional architecture where you have one server and everyone connects to it, and then store all the game state in memory (no database!). If your game gets so popular that you need more than one server, you can still do that, if you make sure that people who are playing together connect to the same server as each other.

If a player quits and reconnects, you can even connect them to the same game state, as long as it's in memory. You only need a game state database if you want to be able to restart the server while people are playing. (You might still need a database for other things, like user accounts)

5

This is done affordably by not using Firestore as the network layer for your game. A database is not really suited for the real-time movement of objects in a game.

4
  • Yes, thank you, I can see that-- I'm trying to learn what alternatives there are. Nov 16, 2020 at 14:38
  • @temporary_user_name usually, players connect to your server, and every time something happens, the server sends a message to every player who's interested. Why do you need a database? You must have some other piece of your architecture which is currently preventing you from "just sending the messages"!
    – user253751
    Nov 16, 2020 at 16:05
  • Eventing does make sense for this. The reason for the database is that game state needs to be persisted-- if a player disconnects from the game by closing the tab, they should be able to rejoin without issue. Like I said, I haven't done anything like this before, but to me that implied I needed to be keeping the game state in the database. Nov 16, 2020 at 17:05
  • @temporary_user_name It sounds like you're using a traditional framework where each HTTP request is totally separate from all other HTTP requests. For a game server, that might not be what you want. I assume your game isn't so popular that you need more than one server to run it?
    – user253751
    Nov 16, 2020 at 17:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.