Search code examples
websocketwebserver2d-games

Network structure for online programming game with webSockets


Problem

I'm making a game where you would provide a piece of code to represent the agent program of an Intelligent Agent (think Robocode and the like), but browser-based. Being an AI/ML guy for the most part, my knowledge of web development was/is pretty lacking, so I'm having a bit of a trouble implementing the whole architecture. Basically, after the upload of text (code), naturally part of the client-side, the backend would be responsible for running the core logics and returning JSON data that would be parsed and used by the client mainly for the drawing part. There isn't really a need for multiplayer support right now.

If I model after Robocode's execution loop, I would need a separate process for each battle that then assigns different agents (user-made or not) to different threads and gives them some execution time for each loop, generating new information to be given to the agents as well as data for drawing the whole scene. I've tried to think of a good way to structure the multiple clients, servers/web servers/processes [...], and came to multiple possible solutions.

Favored solution (as of right now)

Clients communicate with a Node.js server that works kinda like an interface (think websocketd) for unique processes running on the same (server) machine, keeping track of client and process via ID and forwarding the data (via webSockets) accordingly. So an example scenario would be:

  1. Client C1 requests new battle to server S and sends code (not necessarily a single step, I know);
  2. S handles the code (e.g. compiling), executes new battle and starts a connection with it's process P1 (named pipes/FIFO?);
  3. P1 generates JSON, sends to S;
  4. S sees P1 is "connected" to C1, sends data to C1 (steps 3 and 4 will be repeated as long as the battle is active);
  5. Client C2 requests new battle;
  6. Previous steps repeated; C2 is assigned to new process P2;
  7. Client C3 requests "watching" battle under P1 (using a unique URL or a token);
  8. S finds P1's ID, compares to the received one and binds P1 to C3;

This way, the Server forwards received data from forked processes to all clients connected to each specific Battle.

Questions

Regarding this approach:

  • Is it simple enough? Are there easier or even more elegant ways of doing it? Could scalability be a problem?
  • Is it secure enough (the whole compiling and running code — likely C++ — on the server)?
  • Is it fast enough (this one worries me the most for now)? It seems a bit counter intuitive to have a single server dealing with the entire traffic, but as far as I know, if I'd assign all these processes to a separate web server, I would need different ports for each of them, which seems even worse.

Solution

  • Since this is a theoretical and opinion based question... I feel free to throwing the ball in different directions. I'll probably edit the answer as I think things over or read comments.

    1. A process per battle?

      sounds expensive. Also, there is the issue of messages going back and forth between processes... might as well be able to send the messages between machines and have a total separation of concerns.

      Instead of forking battles, we could have them running on their own, allowing them to crash and reboot and do whatever they feel like without ever causing any of the other battles or our server any harm.

    2. Javascript? Why just one language?

      I would consider leveraging an Object Oriented approach or language - at least for the battles, if not for the server as well.

      If we are separating the code, we can use different languages. Otherwise I would probably go with Ruby, as it's easy for me, but maybe I'm mistaken and delving deeper into Javascript's prototypes will do.

    3. Oh... foreign code - sanitization is in order.

      How safe is the foreign code? should it be in a localized sped language that promises safety of using an existing language interpreter, that might allow the code to mess around with things it really shouldn't...

      I would probably write my own "pseudo language" designed for the battles... or (if it was a very local project for me and mine) use Ruby with one of it's a sanitizing gems.

    4. Battles and the web-services might not scale at the same speed.

      It seems to me that handling messages - both client->server->battle and battle->server->client - is fairly easy work. However, handling the battle seems more resource intensive.

      I'm convincing myself that a separation of concerns is almost unavoidable.

      Having a server backend and a different battle backend would allow you to scale the battle handlers up more rapidly and without wasting resources on scaling the web-server before there's any need.

    5. Network disconnections.

      Assuming we allow the players to go offline while their agents "fight" in the field ... What happens when we need to send our user "Mitchel", who just reconnected to server X, a message to a battle he left raging on server Y?

      Separating concerns would mean that right from the start we have a communication system that is ready to scale, allowing our users to connect to different endpoints and still get their messages.

    Summing these up, I would consider this as my workflow:

    • Http workflow:

      • Client -> Web Server : requesting agent with identifier and optional battle data (battle data is for creating an agent, omitting battle data will be used for limiting the request to an existing agent if it exists).

        This step might be automated based on Client authentication / credentials (i.e. session data / cookie identifier or login process).

        • if battle data exists in the request (request to make):

          Web Server -> Battle instance for : creating agent if it doesn't exist.

        • if battle data is missing from the request:

          Web Server -> Battle Database, to check if agent exists.

      • Web Server -> Client : response about agent (exists / created vs none)

        • If Agent exists or created, initiate a Websocket connection after setting up credentials for the connection (session data, a unique cookie identifier or a single-use unique token to be appended to the Websocket request query).

        • If Agent does't exist, forward client to a web form to fill in data such as agent code, battle type etc'.

    • Websocket "workflows" (non linear):

      • Agent has data: Agent message -> (Battle communication manager) -> Web Server -> Client

        It's possible to put Redis or a similar DB in there, to allow messages to stack while the user is offline and to allow multiple battle instances and multiple web server instances.

      • Client updates to Agent: Client message -> (Battle communication manager) -> Web Server -> Agent