From 54d8db95b3bb690b712ec09179922829d0c68a54 Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Tue, 8 Dec 2020 21:15:21 +0100 Subject: [PATCH] Recalc FOVs and their map view results only on relevant changes. --- plomrogue/commands.py | 7 +++++-- plomrogue/game.py | 42 +++++++++++++++++++++++++++--------------- plomrogue/mapping.py | 2 +- plomrogue/tasks.py | 7 +++++++ plomrogue/things.py | 27 +++++++++++++++++++++++++-- 5 files changed, 65 insertions(+), 20 deletions(-) diff --git a/plomrogue/commands.py b/plomrogue/commands.py index 7544c06..dea0717 100644 --- a/plomrogue/commands.py +++ b/plomrogue/commands.py @@ -57,6 +57,7 @@ def cmd_LOGIN(game, nick, connection_id): t.position = s.position break game.changed = True + game.changed_fovs = True cmd_LOGIN.argtypes = 'string' def cmd_BECOME_ADMIN(game, password, connection_id): @@ -84,6 +85,7 @@ def cmd_SET_TILE_CONTROL(game, yx, control_char, connection_id): map_control = game.get_map(big_yx, 'control') map_control[little_yx] = control_char game.changed = True + game.changed_fovs = True cmd_SET_TILE_CONTROL.argtypes = 'yx_tuple:nonneg char' def cmd_THING_PROTECTION(game, thing_id, protection_char, connection_id): @@ -192,14 +194,14 @@ def cmd_GOD_ANNOTATE(game, big_yx, little_yx, msg): if big_yx not in game.annotations: game.annotations[big_yx] = {} game.annotations[big_yx][little_yx] = msg - game.changed = True + #game.changed = True cmd_GOD_ANNOTATE.argtypes = 'yx_tuple yx_tuple:nonneg string' def cmd_GOD_PORTAL(game, big_yx, little_yx, msg): if big_yx not in game.portals: game.portals[big_yx] = {} game.portals[big_yx][little_yx] = msg - game.changed = True + #game.changed = True cmd_GOD_PORTAL.argtypes = 'yx_tuple yx_tuple:nonneg string' def cmd_MAP_LINE(game, big_yx, y, line): @@ -238,6 +240,7 @@ def cmd_THING(game, big_yx, little_yx, thing_type, thing_id): else: game.things += [t_new] game.changed = True + game.changed_fovs = True cmd_THING.argtypes = 'yx_tuple yx_tuple:nonneg string:thing_type int:nonneg' def cmd_THING_NAME(game, thing_id, name, pw, connection_id): diff --git a/plomrogue/game.py b/plomrogue/game.py index 47f09d1..6e8afdf 100755 --- a/plomrogue/game.py +++ b/plomrogue/game.py @@ -117,6 +117,7 @@ class Game(GameBase): def __init__(self, save_file, *args, **kwargs): super().__init__(*args, **kwargs) self.changed = True + self.changed_fovs = True self.io = GameIO(self, save_file) self.tasks = {} self.thing_types = {} @@ -213,32 +214,38 @@ class Game(GameBase): """Send out game state data relevant to clients.""" # TODO: limit to connection_id if provided + print('DEBUG send_gamestate') self.io.send('TURN ' + str(self.turn)) from plomrogue.mapping import FovMap import multiprocessing - pool = multiprocessing.Pool() - players = [] c_ids = [c_id for c_id in self.sessions] - for c_id in c_ids: - players += [self.get_player(c_id)] + # Only recalc FOVs for players with ._fov = None player_fovs = [] - for player in players: - player.prepare_multiprocessible_fov_stencil() + player_fov_ids = [] + for c_id in c_ids: + player = self.get_player(c_id) + if player._fov: + continue + player.prepare_multiprocessible_fov_stencil() #! player_fovs += [player._fov] - new_fovs = pool.map(FovMap.init_terrain, [fov for fov in player_fovs]) - for i in range(len(players)): - players[i]._fov = new_fovs[i] - pool.close() - pool.join() + player_fov_ids += [player.id_] + if len(player_fovs) > 0: + print('DEBUG regenerating FOVs') + pool = multiprocessing.Pool() + new_fovs = pool.map(FovMap.init_terrain, [fov for fov in player_fovs]) #! + pool.close() + pool.join() + for i in range(len(player_fov_ids)): + id_ = player_fov_ids[i] + player = self.get_thing(id_) + player._fov = new_fovs[i] for c_id in c_ids: player = self.get_player(c_id) - visible_terrain = player.fov_stencil_map() self.io.send('FOV %s' % quote(player.fov_stencil.terrain), c_id) self.io.send('MAP %s %s %s' % (self.get_map_geometry_shape(), player.fov_stencil.geometry.size, - quote(visible_terrain)), c_id) - visible_control = player.fov_stencil_map('control') - self.io.send('MAP_CONTROL %s' % quote(visible_control), c_id) + quote(player.visible_terrain)), c_id) + self.io.send('MAP_CONTROL %s' % quote(player.visible_control), c_id) for t in [t for t in self.things if player.fov_test(*t.position)]: target_yx = player.fov_stencil.target_yx(*t.position) self.io.send('THING %s %s %s %s %s' % (target_yx, t.type_, @@ -288,6 +295,7 @@ class Game(GameBase): if hasattr(t, 'name'): self.io.send('CHAT ' + quote(t.name + ' left the map.')) self.things.remove(t) + self.changed_fovs = True to_delete += [connection_id] for connection_id in to_delete: del self.sessions[connection_id] @@ -304,6 +312,9 @@ class Game(GameBase): for connection_id in [c_id for c_id in self.sessions if self.sessions[c_id]['thing_id'] == t.id_]: self.io.send('PLAY_ERROR ' + quote(str(e)), connection_id) + if self.changed_fovs: + for t in [t for t in self.things]: + t.invalidate_map_view() if self.changed: self.turn += 1 # send_gamestate() can be rather expensive, due to among other reasons @@ -312,6 +323,7 @@ class Game(GameBase): datetime.datetime.now() -self.send_gamestate_interval: self.send_gamestate() self.changed = False + self.changed_fovs = False self.save() self.last_send_gamestate = datetime.datetime.now() diff --git a/plomrogue/mapping.py b/plomrogue/mapping.py index d4b19b1..2b19f56 100644 --- a/plomrogue/mapping.py +++ b/plomrogue/mapping.py @@ -228,7 +228,7 @@ class SourcedMap(Map): if yxyx[0] not in obstacles: obstacles[yxyx[0]] = [] obstacles[yxyx[0]] += [yxyx[1]] - for yx in self: + for yx in self: # TODO: iter and source_yxyx expensive, cache earlier? big_yx, little_yx = self.source_yxyx(yx) if big_yx in obstacles and little_yx in obstacles[big_yx]: self.source_map_segment += 'X' diff --git a/plomrogue/tasks.py b/plomrogue/tasks.py index 9d3c23e..edfc3d1 100644 --- a/plomrogue/tasks.py +++ b/plomrogue/tasks.py @@ -41,6 +41,7 @@ class Task_MOVE(Task): self.thing.position = self.get_move_target() if self.thing.carrying: self.thing.carrying.position = self.thing.position + self.thing.game.changed_fovs = True @@ -56,6 +57,7 @@ class Task_WRITE(Task): big_yx = self.thing.position[0] little_yx = self.thing.position[1] self.thing.game.maps[big_yx][little_yx] = self.args[0] + self.thing.game.changed_fovs = True @@ -72,6 +74,7 @@ class Task_FLATTEN_SURROUNDINGS(Task): if not self.thing.game.can_do_tile_with_pw(*yxyx, self.args[0]): continue self.thing.game.maps[yxyx[0]][yxyx[1]] = '.' + self.thing.game.changed_fovs = True @@ -100,6 +103,7 @@ class Task_PICK_UP(Task): to_pick_up = self.thing.game.get_thing(self.args[0]) to_pick_up.position = self.thing.position[:] self.thing.carrying = to_pick_up + #self.thing.game.changed_fovs = True @@ -129,6 +133,7 @@ class Task_DROP(Task): t.accept(self.thing.carrying) break self.thing.carrying = None + #self.thing.game.changed_fovs = True @@ -144,6 +149,7 @@ class Task_DOOR(Task): t.open() else: t.close() + self.thing.game.changed_fovs = True @@ -163,6 +169,7 @@ class Task_INTOXICATE(Task): self.thing.send_msg('RANDOM_COLORS') self.thing.send_msg('CHAT "You are drunk now."') self.thing.drunk = 10000 + self.thing.game.changed_fovs = True diff --git a/plomrogue/things.py b/plomrogue/things.py index 116ad69..23f66ad 100644 --- a/plomrogue/things.py +++ b/plomrogue/things.py @@ -100,6 +100,7 @@ class ThingSpawner(Thing): position=self.position) self.game.things += [t] self.game.changed = True + self.game.changed_fovs = True @@ -255,6 +256,7 @@ class Thing_MusicPlayer(Thing): self.playlist_index -= 1 if self.playlist_index < -1: self.playlist_index = -1 + self.game.changed = True return ['removed song'] elif command == 'REWIND': self.playlist_index = -1 @@ -309,6 +311,7 @@ class Thing_BottleDeposit(Thing): msg += 'pick it up and then use "(un-)wear" on it!' self.sound('BOTTLE DEPOSITOR', msg) self.game.changed = True + self.game.changed_fovs = True def accept(self): self.bottle_counter += 1 @@ -327,7 +330,12 @@ class ThingAnimate(Thing): super().__init__(*args, **kwargs) self.next_task = [None] self.task = None + self.invalidate_map_view() + + def invalidate_map_view(self): self._fov = None + self._visible_terrain = None + self._visible_control = None def set_next_task(self, task_name, args=()): task_class = self.game.tasks[task_name] @@ -345,11 +353,12 @@ class ThingAnimate(Thing): if self.drunk == 0: for c_id in self.game.sessions: if self.game.sessions[c_id]['thing_id'] == self.id_: + # TODO: refactor with self.send_msg self.game.io.send('DEFAULT_COLORS', c_id) self.game.io.send('CHAT "You sober up."', c_id) + self.game.changed_fovs = True break self.game.changed = True - self._fov = None if self.task is None: self.task = self.get_next_task() return @@ -393,7 +402,7 @@ class ThingAnimate(Thing): return True return False - def fov_stencil_map(self, map_type='normal'): + def fov_stencil_map(self, map_type): visible_terrain = '' for yx in self.fov_stencil: if self.fov_stencil[yx] == '.': @@ -404,6 +413,20 @@ class ThingAnimate(Thing): visible_terrain += ' ' return visible_terrain + @property + def visible_terrain(self): + if self._visible_terrain: + return self._visible_terrain + self._visible_terrain = self.fov_stencil_map('normal') + return self._visible_terrain + + @property + def visible_control(self): + if self._visible_control: + return self._visible_control + self._visible_control = self.fov_stencil_map('control') + return self._visible_control + class Thing_Player(ThingAnimate): -- 2.30.2