home · contact · privacy
Recalc FOVs and their map view results only on relevant changes.
authorChristian Heller <c.heller@plomlompom.de>
Tue, 8 Dec 2020 20:15:21 +0000 (21:15 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Tue, 8 Dec 2020 20:15:21 +0000 (21:15 +0100)
plomrogue/commands.py
plomrogue/game.py
plomrogue/mapping.py
plomrogue/tasks.py
plomrogue/things.py

index 7544c06e03c94018ae975bf65a0ace44e2f4253f..dea07173c6181f2233260823977c95766732bd60 100644 (file)
@@ -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):
index 47f09d161ceff332b2e8ad43f086161d9ad75854..6e8afdf83182dded8a25c735b94dc7cdc2a7095a 100755 (executable)
@@ -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()
 
index d4b19b1a8bad56d64aee96d1243aef6592b42e00..2b19f565378d278303a4b772bd599f4f94b9f4e8 100644 (file)
@@ -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'
index 9d3c23e7b69359da3dd9cdcd23a6cf853eb4ebbc..edfc3d10efddbd1fc4a9d95031b3fd8ea4101aca 100644 (file)
@@ -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
 
 
 
index 116ad696338ffea4fae46fc579426f8e6489048f..23f66ad4a5bb25b5fe297554992116a338879556 100644 (file)
@@ -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):