From 74c0ec0247f058b977996f9e1e2d696f3d2d162b Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Sun, 13 Dec 2020 00:25:52 +0100 Subject: [PATCH] Optimize send_gamestate, don't send any invisible state changes. --- config.json | 1 + plomrogue/commands.py | 20 ++++++-- plomrogue/game.py | 110 +++++++++++++++++++++++------------------- plomrogue/tasks.py | 11 ++++- plomrogue/things.py | 18 +++++-- rogue_chat.html | 11 +++-- rogue_chat_curses.py | 12 +++-- 7 files changed, 116 insertions(+), 67 deletions(-) diff --git a/config.json b/config.json index 6c4fa14..76a1f72 100644 --- a/config.json +++ b/config.json @@ -22,6 +22,7 @@ "install": "I", "wear": "W", "consume": "C", + "spin": "S", "help": "h", "toggle_map_mode": "L", "toggle_tile_draw": "m", diff --git a/plomrogue/commands.py b/plomrogue/commands.py index 1cf0512..8b1a1b9 100644 --- a/plomrogue/commands.py +++ b/plomrogue/commands.py @@ -46,15 +46,15 @@ def cmd_LOGIN(game, nick, connection_id): 'thing_id': t.id_, 'status': 'player' } + game.io.send('PLAYER_ID %s' % t.id_, connection_id) game.io.send('LOGIN_OK', connection_id) t.name = nick game.io.send('CHAT ' + quote(t.name + ' entered the map.')) - game.io.send('PLAYER_ID %s' % t.id_, connection_id) for s in [s for s in game.things if s.type_ == 'SpawnPoint' and s.name == t.name]: t.position = s.position break - game.changed = True + # game.changed = True # handled by game.add_thing cmd_LOGIN.argtypes = 'string' def cmd_BECOME_ADMIN(game, password, connection_id): @@ -95,7 +95,9 @@ def cmd_THING_PROTECTION(game, thing_id, protection_char, connection_id): if not t: raise GameError('thing of ID %s not found' % thing_id) t.protection = protection_char - #game.changed = True + game.changed = True + # FIXME: pseudo-FOV-change actually + game.record_fov_change(t.position) cmd_THING_PROTECTION.argtypes = 'int:pos char' def cmd_SET_MAP_CONTROL_PASSWORD(game, tile_class, password, connection_id): @@ -120,6 +122,8 @@ def cmd_NICK(game, nick, connection_id): t.name = nick game.io.send('CHAT ' + quote(old_nick + ' renamed themselves to ' + nick)) game.changed = True + # FIXME: pseudo-FOV-change actually + game.record_fov_change(t.position) cmd_NICK.argtypes = 'string' def cmd_GET_GAMESTATE(game, connection_id): @@ -166,6 +170,8 @@ def cmd_ANNOTATE(game, yx, msg, pw, connection_id): if big_yx not in game.annotations: game.annotations[big_yx] = {} game.annotations[big_yx][little_yx] = msg + # FIXME: pseudo-FOV-change actually + game.record_fov_change([big_yx, little_yx]) game.changed = True cmd_ANNOTATE.argtypes = 'yx_tuple:nonneg string string' @@ -184,6 +190,8 @@ def cmd_PORTAL(game, yx, msg, pw, connection_id): if big_yx not in game.portals: game.portals[big_yx] = {} game.portals[big_yx][little_yx] = msg + # FIXME: pseudo-FOV-change actually + game.record_fov_change([big_yx, little_yx]) game.changed = True cmd_PORTAL.argtypes = 'yx_tuple:nonneg string string' @@ -228,7 +236,7 @@ def cmd_THING(game, big_yx, little_yx, thing_type, thing_id): raise GameError('illegal thing type %s' % thing_type) _ = game.get_map(big_yx) game.add_thing(thing_type, (big_yx, little_yx), id_=thing_id) - game.changed = True + # game.changed = True handled by add_thing cmd_THING.argtypes = 'yx_tuple yx_tuple:nonneg string:thing_type int:nonneg' def cmd_THING_NAME(game, thing_id, name, pw, connection_id): @@ -240,6 +248,8 @@ def cmd_THING_NAME(game, thing_id, name, pw, connection_id): raise GameError('wrong password for thing') t.name = name game.changed = True + # FIXME: pseudo-FOV-change actually + game.record_fov_change(t.position) cmd_THING_NAME.argtypes = 'int:pos string string' def cmd_GOD_THING_NAME(game, thing_id, name): @@ -313,6 +323,8 @@ def cmd_PLAYER_FACE(game, face, connection_id): raise GameError('wrong face string length') game.faces[t.name] = face game.changed = True + # FIXME: pseudo-FOV-change actually + game.record_fov_change(t.position) cmd_PLAYER_FACE.argtypes = 'string' def cmd_GOD_PLAYER_FACE(game, name, face): diff --git a/plomrogue/game.py b/plomrogue/game.py index d5f8430..e1c92cf 100755 --- a/plomrogue/game.py +++ b/plomrogue/game.py @@ -234,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 = [] @@ -263,59 +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) - seen_things = [t for t in self.things - if player.fov_test(*t.position)] - for t in 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 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) - 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') + 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) + 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 = [] @@ -333,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: diff --git a/plomrogue/tasks.py b/plomrogue/tasks.py index 3dc0928..4b63634 100644 --- a/plomrogue/tasks.py +++ b/plomrogue/tasks.py @@ -110,6 +110,8 @@ class Task_PICK_UP(Task): to_pick_up.position = self.thing.position[:] self.thing.carrying = to_pick_up to_pick_up.carried = True + # FIXME: pseudo-FOV-change actually + self.thing.game.record_fov_change(self.thing.position) @@ -143,6 +145,8 @@ class Task_DROP(Task): and t.position == dropped.position]: t.accept(dropped) break + # FIXME: pseudo-FOV-change actually + self.thing.game.record_fov_change(self.thing.position) @@ -177,7 +181,8 @@ 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.invalidate_map_view() + # FIXME: pseudo-FOV-change actually + self.thing.game.record_fov_change(self.thing.position) @@ -226,6 +231,8 @@ class Task_INSTALL(Task): else: self._get_uninstallables()[0].uninstall() self.thing.send_msg('CHAT "You uninstall the thing here."') + # FIXME: pseudo-FOV-change actually + self.thing.game.record_fov_change(self.thing.position) @@ -263,6 +270,8 @@ class Task_WEAR(Task): self.thing.send_msg('CHAT "You put on a hat."') self.thing.game.remove_thing(self.thing.carrying) self.thing.carrying = None + # FIXME: pseudo-FOV-change actually + self.thing.game.record_fov_change(self.thing.position) diff --git a/plomrogue/things.py b/plomrogue/things.py index a13f771..8885aa0 100644 --- a/plomrogue/things.py +++ b/plomrogue/things.py @@ -116,7 +116,7 @@ class ThingSpawner(Thing): if t != self and t.position == self.position]: return self.game.add_thing(self.child_type, self.position) - self.game.changed = True + # self.game.changed = True handled by add_thing @@ -244,6 +244,8 @@ class Thing_HatRemixer(Thing): hat.design = new_design self.sound('HAT REMIXER', 'remixing a hat …') self.game.changed = True + # FIXME: pseudo-FOV-change actually + self.game.record_fov_change(self.thing.position) @@ -369,7 +371,7 @@ class Thing_BottleDeposit(Thing): elif choice == 'Hat': msg += 'pick it up and then use "(un-)wear" on it!' self.sound('BOTTLE DEPOSITOR', msg) - self.game.changed = True + # self.game.changed = True done by game.add_thing def accept(self): self.bottle_counter += 1 @@ -394,6 +396,7 @@ class ThingAnimate(Thing): self._fov = None self._visible_terrain = None self._visible_control = None + self._seen_things = None def set_next_task(self, task_name, args=()): task_class = self.game.tasks[task_name] @@ -414,7 +417,9 @@ class ThingAnimate(Thing): # 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.invalidate_map_view() + #self.invalidate_map_view() + # FIXME: pseudo-FOV-change actually + self.game.record_fov_change(self.thing.position) break self.game.changed = True if self.task is None: @@ -485,6 +490,13 @@ class ThingAnimate(Thing): self._visible_control = self.fov_stencil_map('control') return self._visible_control + @property + def seen_things(self): + if self._seen_things is not None: + return self._seen_things + self._seen_things = [t for t in self.game.things + if self.fov_test(*t.position)] + return self._seen_things class Thing_Player(ThingAnimate): diff --git a/rogue_chat.html b/rogue_chat.html index f47cf12..0ba8cf0 100644 --- a/rogue_chat.html +++ b/rogue_chat.html @@ -480,6 +480,10 @@ let server = { if (tokens[0] === 'TURN') { game.turn_complete = false; game.turn = parseInt(tokens[1]); + } else if (tokens[0] === 'PSEUDO_FOV_WIPE') { + game.portals_new = {}; + explorer.annotations_new = {}; + game.things_new = []; } else if (tokens[0] === 'THING') { let t = game.get_thing_temp(tokens[4], true); t.position = parser.parse_yx(tokens[1]); @@ -517,7 +521,6 @@ let server = { game.terrains[tokens[1]] = tokens[2] } else if (tokens[0] === 'MAP') { game.map_geometry_new = tokens[1]; - tui.init_keys(); game.map_size_new = parser.parse_yx(tokens[2]); game.map_new = tokens[3] } else if (tokens[0] === 'FOV') { @@ -525,19 +528,17 @@ let server = { } else if (tokens[0] === 'MAP_CONTROL') { game.map_control_new = tokens[1] } else if (tokens[0] === 'GAME_STATE_COMPLETE') { - game.turn_complete = true; game.portals = game.portals_new; - game.portals_new = {}; game.map_geometry = game.map_geometry_new; game.map_size = game.map_size_new; game.map = game.map_new; + tui.init_keys(); game.map_control = game.map_control_new; explorer.annotations = explorer.annotations_new; - explorer.annotations_new = {}; explorer.info_cached = false; game.things = game.things_new; - game.things_new = []; game.player = game.things[game.player_id]; + game.turn_complete = true; if (tui.mode.name == 'post_login_wait') { tui.switch_mode('play'); } else { diff --git a/rogue_chat_curses.py b/rogue_chat_curses.py index 7145101..d76c3be 100755 --- a/rogue_chat_curses.py +++ b/rogue_chat_curses.py @@ -173,6 +173,12 @@ def cmd_TURN(game, n): game.turn_complete = False cmd_TURN.argtypes = 'int:nonneg' +def cmd_PSEUDO_FOV_WIPE(game): + game.portals_new = {} + game.annotations_new = {} + game.things_new = [] +cmd_PSEUDO_FOV_WIPE.argtypes = '' + def cmd_LOGIN_OK(game): game.tui.switch_mode('post_login_wait') game.tui.send('GET_GAMESTATE') @@ -268,20 +274,17 @@ def cmd_MAP_CONTROL(game, content): cmd_MAP_CONTROL.argtypes = 'string' def cmd_GAME_STATE_COMPLETE(game): - game.turn_complete = True game.tui.do_refresh = True game.tui.info_cached = None game.things = game.things_new - game.things_new = [] game.portals = game.portals_new - game.portals_new = {} game.annotations = game.annotations_new - game.annotations_new = {} game.fov = game.fov_new game.map_geometry = game.map_geometry_new game.map_content = game.map_content_new game.map_control_content = game.map_control_content_new game.player = game.get_thing(game.player_id) + game.turn_complete = True if game.tui.mode.name == 'post_login_wait': game.tui.switch_mode('play') cmd_GAME_STATE_COMPLETE.argtypes = '' @@ -364,6 +367,7 @@ class Game(GameBase): self.register_command(cmd_REPLY) self.register_command(cmd_PLAYER_ID) self.register_command(cmd_TURN) + self.register_command(cmd_PSEUDO_FOV_WIPE) self.register_command(cmd_THING) self.register_command(cmd_THING_TYPE) self.register_command(cmd_THING_NAME) -- 2.30.2