Vinicius Carvalho
Posted on September 5, 2018
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
Posted on September 5, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.