home · contact · privacy
Refactor.
[plomrogue2-experiments] / server_ / io.py
index e015b4f6f5cc14cbaeabc398223bf9c0ee7da7aa..7a4c3b0fc5717bca8ece750f5c62d111b3302c4f 100644 (file)
@@ -1,6 +1,9 @@
 import socketserver
 import threading
 import queue
 import socketserver
 import threading
 import queue
+import sys
+sys.path.append('../')
+import parser
 
 
 # Avoid "Address already in use" errors.
 
 
 # Avoid "Address already in use" errors.
@@ -31,9 +34,10 @@ class IO_Handler(socketserver.BaseRequestHandler):
         from the game IO loop via that new queue.
 
         At the same time, loops over socket's recv to get messages
         from the game IO loop via that new queue.
 
         At the same time, loops over socket's recv to get messages
-        from the outside via self.server.queue_out into the game IO
-        loop. Ends connection once a 'QUIT' message is received from
-        socket, and then also calls for a kill of its own queue.
+        from the outside into the game IO loop by way of
+        self.server.queue_out into the game IO. Ends connection once a
+        'QUIT' message is received from socket, and then also calls
+        for a kill of its own queue.
 
         All messages to the game IO loop are tuples, with the first
         element a meta command ('ADD_QUEUE' for queue creation,
 
         All messages to the game IO loop are tuples, with the first
         element a meta command ('ADD_QUEUE' for queue creation,
@@ -85,53 +89,113 @@ class IO_Handler(socketserver.BaseRequestHandler):
         self.request.close()
 
 
         self.request.close()
 
 
-def io_loop(q, game_command_handler):
-    """Handle commands coming through queue q, send results back.
+class GameIO():
 
 
-    Commands from q are expected to be tuples, with the first element
-    either 'ADD_QUEUE', 'COMMAND', or 'KILL_QUEUE', the second element
-    a UUID, and an optional third element of arbitrary type. The UUID
-    identifies a receiver for replies.
+    def __init__(self, game_file_name, game):
+        self.game_file_name = game_file_name
+        self.queues_out = {}
+        self.parser = parser.Parser(game)
 
 
-    An 'ADD_QUEUE' command should contain as third element a queue
-    through which to send messages back to the sender of the
-    command. A 'KILL_QUEUE' command removes the queue for that
-    receiver from the list of queues through which to send replies.
+    def loop(self, q):
+        """Handle commands coming through queue q, send results back.
 
 
-    A 'COMMAND' command is specified in greater detail by a string
-    that is the tuple's third element. The game_command_handler takes
-    care of processing this and sending out replies.
+        Commands from q are expected to be tuples, with the first element
+        either 'ADD_QUEUE', 'COMMAND', or 'KILL_QUEUE', the second element
+        a UUID, and an optional third element of arbitrary type. The UUID
+        identifies a receiver for replies.
 
 
-    """
-    while True:
-        x = q.get()
-        command_type = x[0]
-        connection_id = x[1]
-        content = None if len(x) == 2 else x[2]
-        if command_type == 'ADD_QUEUE':
-            game_command_handler.queues_out[connection_id] = content
-        elif command_type == 'COMMAND':
-            game_command_handler.handle_input(content, connection_id)
-        elif command_type == 'KILL_QUEUE':
-            del game_command_handler.queues_out[connection_id]
-
-
-def run_server_with_io_loop(command_handler):
+        An 'ADD_QUEUE' command should contain as third element a queue
+        through which to send messages back to the sender of the
+        command. A 'KILL_QUEUE' command removes the queue for that
+        receiver from the list of queues through which to send replies.
+
+        A 'COMMAND' command is specified in greater detail by a string
+        that is the tuple's third element. The game_command_handler takes
+        care of processing this and sending out replies.
+
+        """
+        while True:
+            x = q.get()
+            command_type = x[0]
+            connection_id = x[1]
+            content = None if len(x) == 2 else x[2]
+            if command_type == 'ADD_QUEUE':
+                self.queues_out[connection_id] = content
+            elif command_type == 'KILL_QUEUE':
+                del self.queues_out[connection_id]
+            elif command_type == 'COMMAND':
+                self.handle_input(content, connection_id)
+
+    def handle_input(self, input_, connection_id=None, store=True):
+        """Process input_ to command grammar, call command handler if found."""
+        from inspect import signature
+        import server_.game
+
+        def answer(connection_id, msg):
+            if connection_id:
+                self.send(msg, connection_id)
+            else:
+                print(msg)
+
+        try:
+            command = self.parser.parse(input_)
+            if command is None:
+                answer(connection_id, 'UNHANDLED_INPUT')
+            else:
+                if 'connection_id' in list(signature(command).parameters):
+                    command(connection_id=connection_id)
+                else:
+                    command()
+                    if store:
+                        with open(self.game_file_name, 'a') as f:
+                            f.write(input_ + '\n')
+        except parser.ArgError as e:
+            answer(connection_id, 'ARGUMENT_ERROR ' + self.quote(str(e)))
+        except server_.game.GameError as e:
+            answer(connection_id, 'GAME_ERROR ' + self.quote(str(e)))
+
+    def send(self, msg, connection_id=None):
+        """Send message msg to server's client(s) via self.queues_out.
+
+        If a specific client is identified by connection_id, only
+        sends msg to that one. Else, sends it to all clients
+        identified in self.queues_out.
+
+        """
+        if connection_id:
+            self.queues_out[connection_id].put(msg)
+        else:
+            for connection_id in self.queues_out:
+                self.queues_out[connection_id].put(msg)
+
+    def quote(self, string):
+        """Quote & escape string so client interprets it as single token."""
+        # FIXME: Don't do this as a method, makes no sense.
+        quoted = []
+        quoted += ['"']
+        for c in string:
+            if c in {'"', '\\'}:
+                quoted += ['\\']
+            quoted += [c]
+        quoted += ['"']
+        return ''.join(quoted)
+
+
+def run_server_with_io_loop(game):
     """Run connection of server talking to clients and game IO loop.
 
     We have the TCP server (an instance of Server) and we have the
     """Run connection of server talking to clients and game IO loop.
 
     We have the TCP server (an instance of Server) and we have the
-    game IO loop, a thread running io_loop. Both communicate with each
-    other via a queue.Queue. While the TCP server may spawn parallel
-    threads to many clients, the IO loop works sequentially through
-    game commands received from the TCP server's threads (= client
-    connections to the TCP server), calling command_handler to process
-    them. A processed command may trigger messages to the commanding
-    client or to all clients, delivered from the IO loop to the TCP
-    server via the queue.
+    game IO loop, a thread running Game.io.loop. Both communicate with
+    each other via a queue.Queue. While the TCP server may spawn
+    parallel threads to many clients, the IO loop works sequentially
+    through game commands received from the TCP server's threads (=
+    client connections to the TCP server). A processed command may
+    trigger messages to the commanding client or to all clients,
+    delivered from the IO loop to the TCP server via the queue.
 
     """
     q = queue.Queue()
 
     """
     q = queue.Queue()
-    c = threading.Thread(target=io_loop, daemon=True, args=(q, command_handler))
+    c = threading.Thread(target=game.io.loop, daemon=True, args=(q,))
     c.start()
     server = Server(q)
     try:
     c.start()
     server = Server(q)
     try: