So after my exploration into immutable lenses (Parts 1, 2 and 3) it is time to put them to use in the engine. To test them I have created two of the level manipulations.
The first, shown below, spawns an actor within a level.
public static Level SpawnActor( this Level level, Actor actor, Point location) { var actorState = ActorState.Create(actor.Id, location); return level .Set(ActorLookupAt(location), actor.ToSome()) .Set(ActorStateLookup(actor.Id), actorState.ToSome()); }
As you can see the code is very clean.
- Create the new state
- Insert the actor into level.Tiles[location].Actor
- Insert the actor state into level.ActorStates[actorId]
ActorLookupAt() combines a couple of lenses targeted at the specified tile location and ActorStateLookup() is similar.
The situation is similar moving an actor from one location to another, as shown below.
public static Level MoveActor( this Level level, long actorId, Point newLocation) { var actorLocationLens = ActorLocationByKey(actorId); var oldLocation = level.Get(actorLocationLens); var actorAtOldLocationLens = ActorLookupAt(oldLocation); var actor = level.Get(actorAtOldLocationLens); return level .Set(actorAtOldLocationLens, Maybe≪Actor>.None) .Set(ActorLookupAt(newLocation), actor) .Set(actorLocationLens, newLocation); }
There are a couple more steps as we need to get some data out of the level first and we also reuse some of the lenses so cache them locally before using them.
To move the actor we:
- Get the old actor location.
- Get the actor from it’s current tile.
- Remove the actor from it’s current tile.
- Place the actor at its new location.
- Update the actors location in the actor states
One downside of this is that level is rebuilt 3 times by the operation. This is not a major impact though as the level object is small but if this does prove an issue later I can tease the code apart some more such that the tiles is pulled out to a builder and then updated and then stuff the updates back into the level as a single operation to minimise churn.
The reality of a roguelike is that the reads vs writes is very asymmetrical with most of the focus on read due to AI path finding and the like. Given the turn based nature this should not be an issue but there is lots of wiggle room to tweak later all located in one area of the code base..
You must be logged in to post a comment.