home · contact · privacy
Optimize send_gamestate, don't send any invisible state changes.
[plomrogue2] / plomrogue / game.py
index a57a507cfc0a1f1d98d677068f60b3fc6772f3bd..e1c92cf21cd477080ccff2b3cffba660a200b7dc 100755 (executable)
@@ -213,6 +213,8 @@ class Game(GameBase):
         return None
 
     def remove_thing(self, t):
+        if t.carrying:
+            t.uncarry()
         self.things.remove(t)
         self.record_fov_change(t.position)
 
@@ -232,10 +234,12 @@ class Game(GameBase):
         """Send out game state data relevant to clients."""
 
         # TODO: limit to connection_id if provided
-        self.io.send('TURN ' + str(self.turn))
         from plomrogue.mapping import FovMap
         import multiprocessing
-        c_ids = [c_id for c_id in self.sessions]
+        if connection_id:
+            c_ids = [connection_id]
+        else:
+            c_ids = [c_id for c_id in self.sessions]
         # Only recalc FOVs for players with ._fov = None
         player_fovs = []
         player_fov_ids = []
@@ -247,7 +251,7 @@ class Game(GameBase):
             player_fovs += [player._fov]
             player_fov_ids += [player.id_]
         new_fovs = []
-        single_core_until = 8  # since multiprocess has its own overhead
+        single_core_until = 16  # since multiprocess has its own overhead
         if len(player_fovs) > single_core_until:
             pool = multiprocessing.Pool()
             new_fovs = pool.map(FovMap.init_terrain, [fov for fov in player_fovs])
@@ -261,53 +265,67 @@ class Game(GameBase):
             player = self.get_thing(id_)
             player._fov = new_fovs[i]
         for c_id in c_ids:
+            self.io.send('TURN ' + str(self.turn), c_id)
             player = self.get_player(c_id)
-            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(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 %s'
-                             % (target_yx, t.type_, quote(t.protection), t.id_,
-                                int(t.portable), int(t.commandable)),
-                             c_id)
-                if hasattr(t, 'name'):
-                    self.io.send('THING_NAME %s %s' % (t.id_, quote(t.name)), c_id)
-                    if t.type_ == 'Player' and t.name in self.hats:
-                        hat = self.hats[t.name]
-                        self.io.send('THING_HAT %s %s' % (t.id_, quote(hat)), c_id)
-                face = self.get_face(t)
-                if face:
-                    self.io.send('THING_FACE %s %s' % (t.id_, quote(face)), c_id)
-                if hasattr(t, 'thing_char'):
-                    self.io.send('THING_CHAR %s %s' % (t.id_,
-                                                       quote(t.thing_char)), c_id)
-                if hasattr(t, 'carrying') and t.carrying:
+            if player.id_ in player_fov_ids:
+                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(player.visible_terrain)), c_id)
+                self.io.send('MAP_CONTROL %s' % quote(player.visible_control), c_id)
+            if player.id_ in player_fov_ids:
+                # FIXME: Many of the following updates are triggered by technically
+                # inappropriate calls to game.record_fov_change, since they depict
+                # states that might change independent of FOV changes.  They are
+                # collected here as a shortcut, but a cleaner way would be to
+                # differentiate the changes somehow.
+                self.io.send('PSEUDO_FOV_WIPE', c_id)
+                for t in player.seen_things:
+                    target_yx = player.fov_stencil.target_yx(*t.position)
+                    self.io.send('THING %s %s %s %s %s %s'
+                                 % (target_yx, t.type_, quote(t.protection), t.id_,
+                                    int(t.portable), int(t.commandable)),
+                                 c_id)
+                    if hasattr(t, 'name'):
+                        self.io.send('THING_NAME %s %s' % (t.id_, quote(t.name)), c_id)
+                        if t.type_ == 'Player' and t.name in self.hats:
+                            hat = self.hats[t.name]
+                            self.io.send('THING_HAT %s %s' % (t.id_, quote(hat)), c_id)
+                    face = self.get_face(t)
+                    if face:
+                        self.io.send('THING_FACE %s %s' % (t.id_, quote(face)), c_id)
+                    if hasattr(t, 'thing_char'):
+                        self.io.send('THING_CHAR %s %s' % (t.id_,
+                                                           quote(t.thing_char)), c_id)
+                    if hasattr(t, 'installable') and not t.portable:
+                        self.io.send('THING_INSTALLED %s' % (t.id_), c_id)
+                    if hasattr(t, 'design'):
+                        self.io.send('THING_HAT %s %s' % (t.id_,
+                                                          quote(t.design)), c_id)
+                for t in [t for t in player.seen_things if t.carrying]:
+                    # send this last so all carryable things are already created
                     self.io.send('THING_CARRYING %s %s' % (t.id_, t.carrying.id_),
                                  c_id)
-                if hasattr(t, 'installable') and not t.portable:
-                    self.io.send('THING_INSTALLED %s' % (t.id_), c_id)
-            for big_yx in self.portals:
-                for little_yx in [little_yx for little_yx in self.portals[big_yx]
-                                  if player.fov_test(big_yx, little_yx)]:
-                    target_yx = player.fov_stencil.target_yx(big_yx, little_yx)
-                    portal = self.portals[big_yx][little_yx]
-                    self.io.send('PORTAL %s %s' % (target_yx, quote(portal)), c_id)
-            for big_yx in self.annotations:
-                for little_yx in [little_yx for little_yx in self.annotations[big_yx]
-                                  if player.fov_test(big_yx, little_yx)]:
-                    target_yx = player.fov_stencil.target_yx(big_yx, little_yx)
-                    annotation = self.annotations[big_yx][little_yx]
-                    self.io.send('ANNOTATION %s %s' % (target_yx,
-                                                       quote(annotation)), c_id)
-        self.io.send('GAME_STATE_COMPLETE')
+                for big_yx in self.portals:
+                    for little_yx in [little_yx for little_yx in self.portals[big_yx]
+                                      if player.fov_test(big_yx, little_yx)]:
+                        target_yx = player.fov_stencil.target_yx(big_yx, little_yx)
+                        portal = self.portals[big_yx][little_yx]
+                        self.io.send('PORTAL %s %s' % (target_yx, quote(portal)), c_id)
+                for big_yx in self.annotations:
+                    for little_yx in [little_yx for little_yx in self.annotations[big_yx]
+                                      if player.fov_test(big_yx, little_yx)]:
+                        target_yx = player.fov_stencil.target_yx(big_yx, little_yx)
+                        annotation = self.annotations[big_yx][little_yx]
+                        self.io.send('ANNOTATION %s %s' % (target_yx,
+                                                           quote(annotation)), c_id)
+            self.io.send('GAME_STATE_COMPLETE', c_id)
 
     def record_fov_change(self, position):
         big_yx, little_yx = position
         self.changed_tiles += [self.map_geometry.undouble_yxyx(big_yx,
                                                                little_yx)]
+        self.changed = True
 
     def run_tick(self):
         to_delete = []
@@ -325,7 +343,7 @@ class Game(GameBase):
                 to_delete += [connection_id]
         for connection_id in to_delete:
             del self.sessions[connection_id]
-            self.changed = True
+            # self.changed = True  already handled by remove_thing
         for t in [t for t in self.things]:
             if t in self.things:
                 try:
@@ -354,6 +372,7 @@ class Game(GameBase):
                         y_range_end = absolute_position.y + fov_radius
                         x_range_start = absolute_position.x - fov_radius
                         x_range_end = absolute_position.x + fov_radius
+                        # TODO: refactor with SourcedMap.inside?
                         for position in self.changed_tiles:
                             if position.y < y_range_start\
                                or position.y > y_range_end: