Repository: ocadotechnology/aimmo

Issue:
Add an `aimmo-game` implementation that spawns workers locally
i.e. replace the current method of speculatively starting 4 workers.
Some work here: https://github.com/jishi9/aimmo/commit/9c8629135654b1940e5a925bb88dc02e79c1cca7



Discussion:

Resolving patch:
diff --git a/aimmo-game-worker/service.py b/aimmo-game-worker/service.py
--- a/aimmo-game-worker/service.py
+++ b/aimmo-game-worker/service.py
@@ -44,4 +44,7 @@ def process_turn():
     logging.basicConfig(level=logging.DEBUG)
 
     app.config['DEBUG'] = True
-    app.run(port=int(sys.argv[1]))
+    app.run(
+        host=sys.argv[1],
+        port=int(sys.argv[2]),
+    )
diff --git a/aimmo-game/service.py b/aimmo-game/service.py
--- a/aimmo-game/service.py
+++ b/aimmo-game/service.py
@@ -9,7 +9,7 @@
 from simulation.avatar.avatar_manager import AvatarManager
 from simulation.game_state import GameState
 from simulation.turn_manager import TurnManager
-from simulation.worker_manager import WorkerManager
+from simulation.worker_manager import LocalWorkerManager
 
 app = flask.Flask(__name__)
 CORS(app)
@@ -71,7 +71,7 @@ def run_game():
     player_manager = AvatarManager()
     game_state = GameState(my_map, player_manager)
     turn_manager = TurnManager(game_state=game_state)
-    worker_manager = WorkerManager(game_state=game_state, users_url='http://localhost:8000/players/api/games/')
+    worker_manager = LocalWorkerManager(game_state=game_state, users_url='http://localhost:8000/players/api/games/')
     worker_manager.start()
     turn_manager.start()
 
diff --git a/aimmo-game/simulation/worker_manager.py b/aimmo-game/simulation/worker_manager.py
--- a/aimmo-game/simulation/worker_manager.py
+++ b/aimmo-game/simulation/worker_manager.py
@@ -1,8 +1,10 @@
 import logging
+import os
+import requests
+import subprocess
 import threading
 import time
 
-import requests
 
 LOGGER = logging.getLogger(__name__)
 
@@ -16,6 +18,18 @@ def __init__(self, game_state, users_url):
         self.user_codes = {}
         super(WorkerManager, self).__init__()
 
+    def get_persistent_state(self, player_id):
+        '''Get the persistent state for a worker.'''
+        return None
+
+    def create_worker(self, player_id):
+        '''Create a worker.'''
+        raise NotImplemented
+
+    def remove_worker(self, player_id):
+        '''Remove a worker for the given player.'''
+        raise NotImplemented
+
     def run(self):
         while True:
             try:
@@ -30,22 +44,63 @@ def run(self):
                         # for turns
                         self.game_state.remove_avatar(user['id'])
                         # Get persistent state from worker
-                        # TODO
+                        persistent_state = self.get_persistent_state(user['id'])
                         # Kill worker
-                        # TODO
+                        self.remove_worker(user['id'])
                         # Spawn worker
-                        # Comes from spawning
-                        worker_url = 'http://localhost:%d' % (
-                            5000 + int(user['id']))
-                        # TODO
+                        worker_url = self.create_worker(
+                            user['id'],
+                        )
                         # Initialise worker
                         requests.post("%s/initialise/" % worker_url, json={
                             'code': user['code'],
                             'options': {},
+                            'persistent_state': persistent_state,
                         })
                         # Add avatar back into game
                         self.game_state.add_avatar(
                             user_id=user['id'], worker_url="%s/turn/" % worker_url)
                         self.user_codes[user['id']] = user['code']
+                removed_user_ids = set(self.user_codes) - set(user['id'] for user in game['users'])
+                for removed_user in removed_user_ids:
+                    self.game_state.remove_avatar(removed_user)
+                    self.remove_worker(removed_user)
+                    del self.user_codes[removed_user]
 
             time.sleep(10)
+
+class LocalWorkerManager(WorkerManager):
+    '''Relies on them already being created already.'''
+    host = '127.0.0.1'
+    worker_path = os.path.join(
+        os.path.dirname(__file__),
+        '../../aimmo-game-worker/service.py' ,
+    )
+
+    def __init__(self, *args, **kwargs):
+        self.workers = {}
+        self.next_port = 1989
+        super(LocalWorkerManager, self).__init__(*args, **kwargs)
+
+    def create_worker(self, player_id):
+        assert(player_id not in self.workers)
+        self.next_port += 1
+        self.workers[player_id] = subprocess.Popen(
+            [
+                self.worker_path,
+                self.host,
+                str(self.next_port),
+            ],
+        )
+        worker_url = 'http://%s:%d' % (
+            self.host,
+            self.next_port,
+        )
+        LOGGER.info("Worker started for %s, listening at %s", player_id, worker_url)
+        time.sleep(5)
+        return worker_url
+
+    def remove_worker(self, player_id):
+        if player_id in self.workers:
+            self.workers[player_id].kill()
+            del self.workers[player_id]


Test patch: