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