publishing the world state

In my last post I had my robot making one of the first updates to a world state and now I need to ensure that my robot engine does indeed update the world state. This is a complete change to my robot as described in the last few posts, so for the time being I'll be losing the observables I had previously set up, but they will return when I map to my different contexts.

I need the update to occur in my main robot loop and because these updates will often happen in an event, I would like the world state update stored and then executed in the main loop.

First, I start with a new specification for this state update:

    public class when_told_to_publish_to_the_world_state : with_a_robot_engine
    {
        Establish context = () =>
        {
            robotEngine.Initialise(robot);            
            worldStateUpdate = w => { };
        };

        Because of = () => robotEngine.PublishToTheWorldState(worldStateUpdate);

        It should_have_a_pending_world_state_update = () =>
            robotEngine.PendingWorldStateUpdates.ShouldContain(worldStateUpdate);

        static Action<IWorldState> worldStateUpdate;        
    }

I'm creating an action to update the world state - any instance of a delegate will do for this purpose - and am publishing to the world state.
It should then have stored this pending update in the robot engine.

The implementation: (only relevant parts of code shown)

    public class RobotEngine : ImARobotEngine
    {
        List<Action<IWorldState>> pendingWorldStateUpdates = new List<Action<IWorldState>>();

        public ReadOnlyCollection<Action<IWorldState>> PendingWorldStateUpdates
        {
            get { return pendingWorldStateUpdates.AsReadOnly(); }
        }

        public void PublishToTheWorldState(Action<IWorldState> worldStateUpdate)
        {
            pendingWorldStateUpdates.Add(worldStateUpdate);
        }
    }

Next stage is to add a method in for ensuring that the pending world states get activated:

    public class when_told_to_ensure_the_world_state_is_up_to_date : with_a_robot_engine
    {
        Establish context = () =>
        {
            robotEngine.Initialise(robot);
            expectedGunTemperature = 222;
            worldStateUpdate = w => w.GunTemperature = expectedGunTemperature;
            robotEngine.PublishToTheWorldState(worldStateUpdate);
        };

        Because of = () => robotEngine.EnsureTheWorldStateIsUpToDate();

        It should_update_the_world_state_accordingly = () =>
            worldState.GunTemperature.ShouldEqual(expectedGunTemperature);

        It should_have_no_pending_world_state_updates_left = () =>
            robotEngine.PendingWorldStateUpdates.Count.ShouldEqual(0);

        static Action<IWorldState> worldStateUpdate;
        static int expectedGunTemperature;
    }

The implementation of this is:

        public void EnsureTheWorldStateIsUpToDate()
        {
            foreach(var makeAnUpdateTo in pendingWorldStateUpdates)
            {
                makeAnUpdateTo(worldState);
            }
            pendingWorldStateUpdates.RemoveAll(a => true);
        }

I now need to integrate this into my robot - I'll not be able to test an infinite loop which is usually required in the Run() method of a robot so I'm going to put some conditions in that loop in for testing.

I've added an observation into an existing specification:

    public class when_the_slayer_robot_is_run : with_robot_set_up
    {
        Establish context = () => 
        {
            robot.ServiceLocator = serviceLocator;

            robotEngine = MockRepository.GenerateStub<ImARobotEngine>();
            serviceLocator.Stub(x => x.ProvideRobotEngine()).Return(robotEngine);
        };

        Because of = () => robot.Run(false);

        It should_ask_the_service_locator_for_a_robot_engine = () =>
            serviceLocator.AssertWasCalled(x => x.ProvideRobotEngine());

        It should_ask_the_robot_engine_to_initialise_with_itself = () =>
            robotEngine.AssertWasCalled(x => x.Initialise(robot));

        It should_ensure_that_the_world_state_is_updated = () =>
            robotEngine.AssertWasCalled(r => r.EnsureTheWorldStateIsUpToDate());

        static ImARobotEngine robotEngine;
    }

The third observation here ensures that when run is called that the world state is made up to date. By providing false in the overloaded Run() method I'm declaring that I don't want an infinite loop.

Here is my reworked robot implementation:

    public class SlayerRobot : Robot, ImARobot
    {
        ImARobotEngine robotEngine;

        public override void Run()
        {
            Run(true);
        }

        public void Run(bool shouldContinueToLoop)
        {
            robotEngine = ServiceLocator.ProvideRobotEngine();
            robotEngine.Initialise(this);

            bool shouldLoop = true;

            while(shouldLoop)
            {
                robotEngine.EnsureTheWorldStateIsUpToDate();

                shouldLoop = shouldContinueToLoop;
            }
        }

        public override void OnStatus(StatusEvent e)
        {
            robotEngine.PublishToTheWorldState(w =>
               {
                   w.CurrentTurnNumber = e.Status.Time;
                   w.GunTemperature = e.Status.GunHeat;
               });
        }
    }

I now have the first of the world state updates hooked into my robot - my run method will perform an infinite loop of ensuring that world state updates occur (should there be any). The OnStatus event will publish to the world state and this will be recorded by the robot engine, ready for update in the main loop.

Here's the reworked robot engine that accompanies it:

    public class RobotEngine : ImARobotEngine
    {
        readonly IManageStatistics statisticsManager;
        readonly IWorldState worldState;
        ImARobot robot;

        List<Action<IWorldState>> pendingWorldStateUpdates = new List<Action<IWorldState>>();

        public RobotEngine(IWorldState worldState, IManageStatistics statisticsManager)
        {
            this.statisticsManager = statisticsManager;
            this.worldState = worldState;
        }

        public ReadOnlyCollection<Action<IWorldState>> PendingWorldStateUpdates
        {
            get { return pendingWorldStateUpdates.AsReadOnly(); }
        }

        public void Initialise(ImARobot robot)
        {
            this.robot = robot;
            statisticsManager.EnsureWeStartWithCleanStatistics();
        }

        public void PublishToTheWorldState(Action<IWorldState> worldStateUpdate)
        {
            pendingWorldStateUpdates.Add(worldStateUpdate);
        }

        public void EnsureTheWorldStateIsUpToDate()
        {
            foreach(var makeAnUpdateTo in pendingWorldStateUpdates)
            {
                makeAnUpdateTo(worldState);
            }
            pendingWorldStateUpdates.RemoveAll(a => true);
        }
    }

The world state now gets updated but there is nothing "listening" to these updates at present.
My next post will move to updating each context every time the world state gets updated.



0 comments: