async def run_game_loop(self, interval: float = 0.02) -> None: # pylint: disable=function-redefined # pylint: disable=missing-docstring if self._game_state_store.get_game_state( ).game_status == GameStatus.get("Paused"): self._game_state_store.push_update( GameStateUpdate( self._game_state_store.get_game_state().time_order + 1, game_status=GameStatus.get("Active"))) game_state = self._game_state_store.get_game_state() dt = interval self._game_loop_is_running = True logger.info( f"State machine starting game loop with interval of {interval} seconds." ) while game_state.game_status == GameStatus.get("Active"): t0 = time.time() update_dict = self.time_step(game_state, dt) while not self._event_queue.empty(): event = await self._event_queue.get() event_update = await self._universal_event_handler.handle( event, game_state=game_state, dt=dt) update_dict.update(event_update) if time.time() - t0 > 0.95 * interval: break self._game_state_store.push_update( GameStateUpdate(game_state.time_order + 1, **update_dict)) game_state = self._game_state_store.get_game_state() dt = max(interval, time.time() - t0) await curio.sleep(max(0, interval - dt)) self.game_time += dt logger.info("Game loop stopped.") self._game_loop_is_running = False
def test_safe_concurrent_cache_access(self): store = GameStateStore() store.push_update(GameStateUpdate(2)) counter = 0 for update in store.get_update_cache(): counter += 1 if update.time_order == 2: store.push_update(GameStateUpdate(3)) assert counter == 2 counter = 0 for update in store.get_update_cache(): counter += 1 if update.time_order == 0: del store._game_state_update_cache[2] assert counter == 3 assert len(store.get_update_cache()) == 2
def test_cache_size(self): store = GameStateStore() for i in range(2 * store._update_cache_size): assert len(store.get_update_cache()) == min( i + 1, store._update_cache_size) store.push_update(GameStateUpdate(i + 1)) assert sum(store.get_update_cache()).time_order == i + 1
def __init__(self, initial_game_state: GameState = None): logger.debug("Creating GameStateStore instance.") self._game_state = initial_game_state if initial_game_state is not None else GameState( ) if not isinstance(self._game_state, GameState): raise TypeError( f"'initial_game_state' should be of type 'GameState', not '{self._game_state.__class__.__name__}'." ) self._game_state_update_cache = [GameStateUpdate(0)]
def _create_next_package(self): """Override #Connection._create_next_package to include game state updates.""" update_cache = self.game_state_store.get_update_cache() # Respond by sending the sum of all updates since the client's time-order point. # Or the whole game state if the client doesn't have it yet. if self.last_client_time_order == 0: logger.debug( f"Sending full game state to client {self.remote_address}.") game_state = self.game_state_store.get_game_state() update = GameStateUpdate(**game_state.__dict__) else: update_base = GameStateUpdate(self.last_client_time_order) update = sum((upd for upd in update_cache if upd > update_base), update_base) logger.debug(( f"Sending update from time order {self.last_client_time_order} " f"to {update.time_order} to client {self.remote_address}.")) return ServerPackage( Header(self.local_sequence, self.remote_sequence, self.ack_bitfield), update)
def from_datagram(cls, datagram: bytes) -> "ServerPackage": """Override #Package.from_datagram to include `game_state_update`.""" header, payload = Header.deconstruct_datagram(datagram) state_update_bytesize = int.from_bytes(payload[:2], "big") game_state_update = GameStateUpdate.from_bytes( payload[2:state_update_bytesize + 2]) payload = payload[state_update_bytesize + 2:] events = cls._read_out_event_block(payload) result = cls(header, game_state_update, events) result._datagram = datagram # pylint: disable=protected-access return result
async def stop(self, timeout: float = 1.0) -> bool: # pylint: disable=function-redefined # pylint: disable=missing-docstring logger.info("Trying to stop game loop ...") if self._game_state_store.get_game_state( ).game_status == GameStatus.get("Active"): self._game_state_store.push_update( GameStateUpdate( self._game_state_store.get_game_state().time_order + 1, game_status=GameStatus.get("Paused"))) t0 = time.time() while self._game_loop_is_running: if time.time() - t0 > timeout: break await curio.sleep(0) return not self._game_loop_is_running
def __init__(self, game_state_store, server): super().__init__(game_state_store) self.server = server self.player_characters = {} self.npc_actors = {} test_enemy = actors.TestEnemyActor("Test Enemy") test_enemy.position = (200, 100) test_enemy.direction = (-1, 0) test_enemy.velocity = (-1 * actors.ENEMY_VELOCITY, 0) self.npc_actors[0] = test_enemy add_enemy = GameStateUpdate( self._game_state_store.get_game_state().time_order + 1, npcs={0: test_enemy.get_state()} ) self._game_state_store.push_update(add_enemy) self.register_event_handler("JOIN", self.on_join) self.register_event_handler("MOVE", self.on_move) self.register_event_handler("LEAVE", self.on_leave) self.learn_counter = 0 global GRAPH GRAPH = tensorflow.get_default_graph()
def test_update_arithmetic(self): game_state = GameState(time_order=0, game_status=GameStatus.get("Paused")) update = GameStateUpdate(time_order=0, test=0) game_state += update assert not hasattr(game_state, "test") and game_state.time_order == 0 update.time_order = 1 game_state += update assert game_state.test == 0 and game_state.time_order == 1 update.time_order = 2 update.test = TO_DELETE game_state += update assert not hasattr(game_state, "test") and game_state.time_order == 2 update.time_order = 1 update.test = 2 game_state += update assert not hasattr(game_state, "test") and game_state.time_order == 2 game_state.test = 0 update += GameStateUpdate(time_order=3, test=TO_DELETE) assert update.time_order == 3 and update.test == TO_DELETE assert hasattr(game_state, "test") and game_state.time_order == 2 game_state += update assert not hasattr(game_state, "test") and game_state.time_order == 3 update += GameStateUpdate(time_order=4, test="test") assert update.test == "test" and update > game_state game_state += ( GameStateUpdate(time_order=3, test={1: TO_DELETE}) + GameStateUpdate(time_order=5, test={1: "test1", 2: "test2"}) + update ) assert game_state.time_order == 5 and game_state.test[1] == "test1"
def test_bytepacking(self): update = GameStateUpdate(5) bytepack = update.to_bytes() unpacked_update = GameStateUpdate.from_bytes(bytepack) assert update == unpacked_update
def test_push_update(self): store = GameStateStore() store.push_update(GameStateUpdate(1, test="foobar")) assert len(store._game_state_update_cache) == 2 assert store.get_game_state().time_order == 1 assert store.get_game_state().test == "foobar"
def test_instantiation(self): store = GameStateStore() assert store._game_state == GameState() assert store._game_state_update_cache == [GameStateUpdate(0)]
def test_instantiation(self): state_machine = GameStateMachine(GameStateStore()) assert state_machine.game_time == 0 assert state_machine._game_state_store._game_state_update_cache == [ GameStateUpdate(0) ]
def test_bytepacking(self): package = ServerPackage(Header(4, 5, "10" * 16), GameStateUpdate(2), [Event("TEST", "Foo", "Bar")]) datagram = package.to_datagram() unpacked_package = ServerPackage.from_datagram(datagram) assert package == unpacked_package