home · contact · privacy
Refactor parser code.
[plomrogue2] / plomrogue / io.py
1 import queue
2 import threading
3 import inspect
4
5
6
7 class GameIO():
8
9     def __init__(self, game, save_file='savefile'):
10         from plomrogue.parser import Parser
11         self.parser = Parser(game)
12         self.game = game
13         self.save_file = save_file
14         self.servers = []
15
16     def train_parser(self):
17         import string
18         self.parser.string_options = {
19             'map_geometry': {'Hex', 'Square'},
20             'char': [c for c in
21                      string.digits + string.ascii_letters + string.punctuation
22                      + ' '],
23             'direction': self.game.map_geometry.directions,
24             'direction+here': ['HERE'] + self.game.map_geometry.directions,
25             'thing_type': self.game.thing_types.keys()
26         }
27
28     def loop(self, q):
29         """Handle commands coming through queue q, run game, send results back.
30
31         As basic flood protection, only accepts ten commands per connection per
32         1/10 of a second.
33
34         """
35         import time
36         potential_flooders = {}
37         while True:
38             try:
39                 connection_id, command = q.get(timeout=0.001)
40                 now = int(time.time() * 10)
41                 if connection_id in potential_flooders and \
42                    potential_flooders[connection_id][0] == now:
43                     if potential_flooders[connection_id][1] > 10:
44                         continue
45                     potential_flooders[connection_id][1] += 1
46                 else:
47                     potential_flooders[connection_id] = [now, 1]
48                 self.handle_input(command, connection_id)
49             except queue.Empty:
50                 self.game.run_tick()
51
52     def start_loop(self):
53         """Start game loop, set up self.queue to communicate with it.
54
55         The game loop works sequentially through game commands received
56         via self.queue from connected servers' clients."""
57
58         self.queue = queue.Queue()
59
60         # optionally use this for main thread profiling:
61         # import cProfile
62         # class ProfiledThread(threading.Thread):
63         #     def run(self):
64         #         profiler = cProfile.Profile()
65         #         profiler.runcall(threading.Thread.run, self)
66         #         print('profiled thread finished')
67         #         profiler.dump_stats('profile')
68         # c = ProfiledThread(target=self.loop, args=(self.queue,))
69         c = threading.Thread(target=self.loop, args=(self.queue,))
70
71         c.start()
72
73     def start_server(self, port, server_class, certfile=None, keyfile=None):
74         """Start server of server_class in talk with game loop.
75
76         The server communicates with the game loop via self.queue.
77         """
78         if 'certfile' in list(inspect.signature(server_class.__init__).parameters):
79             server = server_class(self.queue, port, certfile=certfile, keyfile=keyfile)
80         else:
81             server = server_class(self.queue, port)
82         self.servers += [server]
83         c = threading.Thread(target=server.serve_forever)
84         c.start()
85
86     def handle_input(self, input_, connection_id=None, god_mode=False):
87         """Process input_ to command grammar, call command handler if found.
88
89         Command handlers that have no connectin_i argument in their
90         signature will only be called if god_mode is set.
91
92         """
93         from plomrogue.errors import GameError, ArgError, PlayError
94         from plomrogue.misc import quote
95
96         def answer(connection_id, msg):
97             if connection_id:
98                 self.send(msg, connection_id)
99             else:
100                 print(msg)
101
102         try:
103             command, args = self.parser.parse(input_)
104             if command is None:
105                 answer(connection_id, 'UNHANDLED_INPUT')
106             else:
107                 if 'connection_id' in list(inspect.signature(command).parameters):
108                     command(*args, connection_id=connection_id)
109                 elif god_mode:
110                     command(*args)
111                     # if store and not hasattr(command, 'dont_save'):
112                     #     with open(self.game_file_name, 'a') as f:
113                     #         f.write(input_ + '\n')
114         except ArgError as e:
115             answer(connection_id, 'ARGUMENT_ERROR ' + quote(str(e)))
116         except PlayError as e:
117             answer(connection_id, 'PLAY_ERROR ' + quote(str(e)))
118         except GameError as e:
119             answer(connection_id, 'GAME_ERROR ' + quote(str(e)))
120
121     def send(self, msg, connection_id=None):
122         """Send message msg to servers' client(s).
123
124         If a specific client is identified by connection_id, only
125         sends msg to that one. Else, sends it to all client sessions.
126
127         """
128         if connection_id:
129             for server in self.servers:
130                 if connection_id in server.clients:
131                     client = server.clients[connection_id]
132                     client.put(msg)
133         else:
134             for c_id in self.game.sessions:
135                 for server in self.servers:
136                     if c_id in server.clients:
137                         client = server.clients[c_id]
138                         client.put(msg)
139                         break