def pause(self) -> None: """ Pauses the simulation if it's running. """ assert self.state != SimulationState.not_started # make sure the simulation has actually been started. self.state = SimulationState.paused log(SIMULATION_LOGGING, f"Pausing the simulation.")
def shutdown(self) -> None: """ Shuts down the simulation. """ log(SIMULATION_LOGGING, f"Simulation {self.name} shutting down.") self.__send_event(SimulationShutdownEvent()) self.state = SimulationState.shutting_down
def run(self) -> None: """ Causes the simulation thread to run. This could be called from Simulation.start(). """ # Make sure this doesn't get called if the simulation is shutting down. if self.state == SimulationState.shutting_down: return log(SIMULATION_LOGGING, f"Simulation {self.name} starting.") self.__send_event(SimulationStartupEvent()) self.state = SimulationState.paused # Keep running until shutting down. Note that this is not a deamon, so shutdowns can be abrupt. while self.state != SimulationState.shutting_down: # If the state is not running, then sleep for a second and see if that changes. if self.state != SimulationState.running: time.sleep(0.05) else: # simulation is running, see if it should step. if self.max_time > 0 and (self._previous_time >= self.max_time): self.shutdown() if self.__steps_to_advance == Simulation.ADVANCE_UNLIMITED: self.step() elif self.__steps_to_advance > 0: self.step() self.__steps_to_advance -= 1 else: # no time to advance, so pause until time to advance. time.sleep(0.05)
def __send_event(self, event) -> None: """ Sends the given event to interested entities. :param event: The event to send. """ log( EVENT_LOGGING, f"sending event {event.name} at sim time {event.sim_time}: {event}" ) event.sim_time = self._current_time self._event_mediator.send_event(event=event)
def remove_entity(self, entity) -> None: """ Removes and entity from the simulation and informs other entities. Note that any entities holding a reference to this entity should remove the entity. :param Entity entity: The entity to remove. """ log(ENTITY_LOGGING, f"Removing entity type {entity.name} with GUID {entity.guid}") self._entities.pop(entity.guid) self._event_mediator.deregister_entity(entity=entity) self._previous_state.pop(entity.guid) self.__send_event(EntityDestroyedEvent(entity=RemoteEntity(entity)))
def test_list_logging(self): """Tests logging with the list logger.""" ll1 = ListLogger("topic1") ll2 = ListLogger(["topic2", "topic3"]) ll3 = ListLogger("topic3") log(topic="topic1", message="message 1") log(topic="topic2", message="message 2") log(topic="topic3", message="message 3") log(topic="topic4", message="message 4") self.assertEqual(1, len(ll1)) self.assertEqual(2, len(ll2)) self.assertEqual(1, len(ll3)) self.assertTrue("] topic1: message 1" in ll1.get_log_messages()[0]) self.assertTrue("] topic2: message 2" in ll2.get_log_messages()[0]) self.assertTrue("] topic3: message 3" in ll2.get_log_messages()[1]) self.assertTrue("] topic3: message 3" in ll3.get_log_messages()[0]) ll1.clear() self.assertEqual(0, len(ll1)) self.assertEqual(2, len(ll2))
def advance(self, steps=ADVANCE_UNLIMITED) -> None: """ Manually advances time by the number of steps specified. :param int steps: The number of steps to advance by. If not specified runs unlimited. """ assert steps > 0 or steps == Simulation.ADVANCE_UNLIMITED if steps > 0: log(SIMULATION_LOGGING, f"Advancing the simulation {steps} times.") else: log(SIMULATION_LOGGING, f"Advancing the simulation until shutdown.") self.__steps_to_advance = steps self.state = SimulationState.running print(f"simulation state is {self.state}")
def add_entity(self, entity) -> None: """ Adds a new entity to the simulation. Note that this is not thread safe. :param Entity entity: The entity to add. """ # Add the entity to the list along with the handlers and then generate an entity created event. # The simulation sets the guids. entity.guid = get_uuid() log(ENTITY_LOGGING, f"Adding entity type {entity.name} with GUID {entity.guid}") self._entities[entity.guid] = entity self.queue_event(EntityCreatedEvent(entity=RemoteEntity(entity))) self._event_mediator.register_entity(entity=entity) self._previous_state[entity.guid] = EntityState(entity=entity)
def test_file_logger(self): """Tests logging to a file.""" test_file = "test.log" fl = FileLogger(topics="topic1", filename=test_file) self.assertFalse(os.path.exists(test_file)) log(topic="topic1", message="message 1") log(topic="topic1", message="message 2") self.assertTrue(os.path.exists(test_file)) fl.close() with open(test_file, "r") as f: line = f.readline() self.assertTrue("] topic1: message 1" in line) line = f.readline() self.assertTrue("] topic1: message 2" in line) os.remove(test_file)
def step(self) -> None: """ Causes the simulation to advance a single step. """ start_cycle_clock_time = time.time() self._current_time = self._event_queue.next_time() # If there are no events in the queue, cycle one in any case. previous_time = self._previous_time if not self._current_time: # this causes the same behavior for time stepped and jumped simulations. self._current_time = self._previous_time + 1 self._previous_time = self._current_time log( SIMULATION_LOGGING, f"Advancing the simulation from {previous_time} to {self._current_time}." ) # send a time update event for the next time. self.__send_event( NewTimeEvent(previous_time=previous_time, new_time=self._current_time)) # also notify the views to update. self._update_views(previous_time=previous_time, new_time=self._current_time) # send all of the events for the current time. while self._event_queue.next_time() == self._current_time: # send all events from the queue to interested simulation entities. self.__send_event(self._event_queue.next()) self.__send_change_events() elapsed_time = time.time() - start_cycle_clock_time while elapsed_time < self.minimum_step_time: time.sleep(0.05) elapsed_time = time.time() - start_cycle_clock_time
def send_event(self, event) -> None: """ Handles the event by sending to all of the entities that registered it. :param Event event: The event to handle. """ if event.name == ENTITY_CREATED_EVENT: entity_list = self._entity_created_interest.get( event.entity.name, None) elif event.name == ENTITY_CHANGED_EVENT: entity_list = self._entity_changed_interest.get( event.entity.name, None) elif event.name == ENTITY_DESTROYED_EVENT: entity_list = self._entity_destroyed_interest.get( event.entity.name, None) else: entity_list = self._named_event_interest.get(event.name, None) if entity_list: for e in entity_list: log( EVENT_LOGGING, f"sending event {event.name} to {e.guid} at time {event.sim_time}: {event.__dict__}" ) e.handle_event(event=event)