Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to manage the game state in face of the EDT?

I'm developing a real time strategy game clone on the Java platform and I have some conceptional questions about where to put and how to manage the game state. The game uses Swing/Java2D as rendering. In the current development phase, no simulation and no AI is present and only the user is able to change the state of the game (for example, build/demolish a building, add-remove production lines, assemble fleets and equipment). Therefore, the game state manipulation can be performed in the event dispatch thread without any rendering lookup. The game state is also used to display various aggregated information to the user.

However, as I need to introduce simulation (for example, building progress, population changes, fleet movements, manufacturing process, etc.), changing the game state in a Timer and EDT will surely slow down the rendering.

Lets say the simulation/AI operation is performed in every 500ms and I use SwingWorker for the computation of about 250ms in length. How can I ensure, that there is no race condition regarding the game state reads between the simulation and the possible user interaction?

I know that the result of the simulation (which is small amount of data) can be efficiently moved back to the EDT via the SwingUtilities.invokeLater() call.

The game state model seems to be too complex to be infeasible for just using immutable value classes everywhere.

Is there a relatively correct approach to eliminate this read race condition? Perhaps doing a full/partial game state cloning on every timer tick or change the living space of the game state from EDT into some other thread?

Update: (from the comments I gave) The game operates with 13 AI controlled players, 1 human player and has about 10000 game objects (planets, buildings, equipment, research, etc.). A game object for example has the following attributes:

World (Planets, Players, Fleets, ...)
Planet (location, owner, population, type, 
    map, buildings, taxation, allocation, ...)
Building (location, enabled, energy, worker, health, ...)

In a scenario, the user builds a new building onto this planet. This is performed in EDT as the map and buildings collection needs to be changed. Parallel to this, a simulation is run on every 500ms to compute the energy allocation to the buildings on all game planets, which needs to traverse the buildings collection for statistics gathering. If the allocation is computed, it is submitted to the EDT and each building's energy field gets assigned.

Only human player interactions have this property, because the results of the AI computation are applied to the structures in EDT anyway.

In general, 75% of the object attributes are static and used only for rendering. The rest of it is changeable either via user interaction or simulation/AI decision. It is also ensured, that no new simulation/AI step is started until the previous one has written back all changes.

My objectives are:

  • Avoid delaying the user interaction, e.g. user places the building onto the planet and only after 0.5s gets the visual feedback
  • Avoid blocking the EDT with computation, lock wait, etc.
  • Avoid concurrency issues with collection traversal and modification, attribute changes

Options:

  • Fine grained object locking
  • Immutable collections
  • Volatile fields
  • Partial snapshot

All of these have advantages, disadvantages and causes to the model and the game.

Update 2: I'm talking about this game. My clone is here. The screenshots might help to imagine the rendering and data model interactions.

Update 3:

I'll try to give a small code sample for clarify my problem as it seems from the comments it is misunderstood:

List<GameObject> largeListOfGameObjects = ...
List<Building> preFilteredListOfBuildings = ...
// In EDT
public void onAddBuildingClicked() {
    Building b = new Building(100 /* kW */);
    largeListOfGameObjects.add(b);
    preFilteredListOfBuildings.add(b);
}
// In EDT
public void paint(Graphics g) {
    int y = 0;
    for (Building b : preFilteredListOfBuildings) {
        g.drawString(Integer.toString(b.powerAssigned), 0, y);
        y += 20;
    }
}
// In EDT
public void assignPowerTo(Building b, int amount) {
    b.powerAssigned = amount;
}
// In simulation thread
public void distributePower() {
    int sum = 0;
    for (Building b : preFilteredListOfBuildings) {
        sum += b.powerRequired;
    }
    final int alloc = sum / (preFilteredListOfBuildings.size() + 1);
    for (final Building b : preFilteredListOfBuildings) {
        SwingUtilities.invokeLater(=> assignPowerTo(b, alloc));            
    }
}

So the overlapping is between the onAddBuildingClicked() and distributePower(). Now imagine the case where you have 50 of these kind of overlappings between various parts of the game model.

like image 550
akarnokd Avatar asked Jun 11 '09 15:06

akarnokd


1 Answers

This sounds like it could benefit from a client/server approach:

The player is a client - interactivity and rendering happen on that end. So the player presses a button, the request goes to the server. The reply from the server comes back, and the player's state is updated. At any point between these things happening, the screen can be re-painted, and it reflects the state of the game as the client currently knows it.

The AI is likewise a client - it's the equivalent of a bot.

The simulation is the server. It gets updates from its clients at various times and updates the state of the world, then sends out these updates to everyone as appropriate. Here's where it ties in with your situation: The simulation/AI requires a static world, and many things are happening at once. The server can simply queue up change requests and apply them before sending the updates back to the client(s). So as far as the server's concerned, the game world isn't actually changing in real time, it's changing whenever the server darn well decides it is.

Finally, on the client side, you can prevent the delay between pressing the button and seeing a result by doing some quick approximate calculations and displaying a result (so the immediate need is met) and then displaying the more correct result when the server gets around to talking to you.

Note that this does not actually have to be implemented in a TCP/IP over-the-internet sort of way, just that it helps to think of it in those terms.

Alternately, you can place the responsibility for keeping the data coherent during the simulation on a database, as they're already built with locking and coherency in mind. Something like sqlite could work as part of a non-networked solution.

like image 70
Atiaxi Avatar answered Oct 10 '22 06:10

Atiaxi