loading...

Kotlin, Box2d, gRPC and Robots

viniciusccarvalho profile image Vinicius Carvalho ・3 min read

The idea

I have always been a fan of robocode, but it seems to be a while since a new update happened to that community. With the launch of grpc for streaming communication and the rise of kubernetes, it seemed that writing a GameServer that could handle bots deployed as pods would not be a far away idea.

So I started this with the current design in mind:

  • The server is written in gRPC, so that clients can be created using any language that has bindings to it.
  • Clients react to events (HIT, ENEMY_DETECTED, HIT_WALL) and emit actions (FIRE, THROTTLE, ROTATE) to the server.
  • The server handles all the physics (using and learning jbox2d a port of the amazing box2d physics engine), and enforces rules (reload speed, health)
  • Besides gRPC a web based endpoint helps control the server lifecycle
  • A websocket based dashboard to draw the world for visualization

Putting it in motion

I chose gRPC due it's support for bi-directional streaming. Since the engine world updates 30 times per second (30 FPS), that is very easy handled by gRPC message streaming. Also clients can easily react to events on the bi-directional stream.

service GameService {
    rpc connect(stream Action) returns (stream FrameUpdate);
}

So your bot only need to create a client to connect to the server and handle events and emit actions. It's really up to each bot implementation to decide what to do best.

Server side

On the server side the main class to handle a simulation is called ArenaService it contains all the bots and a suspended function that tries to run at least 30 per second. One of the very important things in game simulation is getting your main loop right, you need to make sure you compute a delta step between frames, that won't always be 1/30 of second spaced, so that your physics calculation don't go crazy.

This class then only loops over each bot, updating it state, it then steps into the world (a box2d feature that allows it to compute collision and update fixture features such as speed), and finally it broadcast all the queued events to the bots.

Competing consumers/producers

The first problem I bumped into was the World class. It turns out it's not thread safe. And because each bot could create an event such as FIRE that creates a projectile into the world, I rapidly ran into concurrent issues.

My first naive attempt was to use locks, and then it become a nightmare to manage all that.

But since the main loop is the only place where we run game logic I turned into using a ConcurrentQueue, this way any changes to the world are first stacked into the queue, and then only one thread at the main loop modifies it.

Take the following code that is trigged via a listener attached to the World instance. It detects collisions between a bullet and a wall. It then stacks a WorldEvent to the queue, that later will destroy the bullet from the world and also update the ServerProjectile collection that hosts all the active bullets on the simulation.

FixtureType.WALL -> {
                if (targetData.type == FixtureType.BULLET) {
                    worldEvents.offer(WorldEvent(WorldEventType.DESTROY_BULLET, mapOf("id" to targetData.context["id"]!!)))
                }
            }

Current State

I'm still playing with all this, a lot of problems are coming from concurrent modifications and lack of better understanding of box2d (I'm learning more about this engine as I go)

You can find the project on https://github.com/viniciusccarvalho/robo-cloud but it's still very unstable, no build, and still too many open bugs to call it a real project. As soon as I'm done with all the experimentation around box2d, and feel confident enough with the physics engine, I'll move into polishing it more into a consistent build.

And here's a video of the current state of the project. Physics are ok, raycasting to detect bullets are still a miss after change in coordinate system

Discussion

pic
Editor guide