I'm developing a simulation in Scala inside a game. I would like to use only immutable objects to define the game logic even if it's inefficient. Why? Because I can, so I will.
Now I'm stressing a bit the main game loop with pushing the simulation with a huge load. Basically I have these nested case classes and I operate with .copy() to define the following state of a given entity inside the simulation. The problem is that all these operations generate a lot of unreferenced objects and so at the end of every step, I trigger a Full GC. This is bad for the game performances.
So I started some aggressive optimization: first of all now the simulation step is running in parallel behind the game, computing a "next state" that basically covers a day in the game. So the player will be shown the state from the current day while the next one is being computed. This works because basically every major entity in the game (cities, basically) is assumed to evolve in isolation. If some agent (the player or other AI agent traveling between cities) will get in contact with the city, I will recompute the "next state" according to the actions operated by the agent. This is not relevant now anyway.
So I have these parallel entities evolving behind the scene and when a day is over (a day is defined as 5 steps of the player in the world map) I use Await.result(nextWorldState, 5 seconds)
as a rendez-vous point to update the current state of the simulation. This is not necessarily how the game should run but I want to test a rendez-vous point when the rest of the game wait for the next state to be computed, because this will be probably necessary.
My problem is that after this, I dereference a lot of objects all at once and this trigger the GC collection. I tried some solutions but there's always a point when I access the result of the Future and replace the current state with it and this always trigger the GC.
Assuming that I don't want to go for regular classes (probably I will) and assuming that I don't want to split the state in multiple pieces and process them separately (I will try this but it looks unmaintanable), is there any clever solution to this problem?
Here is some not-so-pseudo code from the actual function that handle this logic:
class WorldSimulationManager(var worldState: WorldState) {
private var nextWorldState: Future[WorldState] =
Future.successful(worldState)
def doImmutableStep(gameMap:GameMap,movedCountToday:Int):Unit = {
nextWorldState =
nextWorldState.flatMap(_.doStep(gameMap))
if (movedCountToday >= Constants.tileMovementsToDay) {
worldState = Await.result(nextWorldState, 5 seconds)
}
}
}