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