|
|
@@ -4,163 +4,61 @@ import modelverse_kernel.jit as jit
|
|
|
from collections import defaultdict
|
|
|
|
|
|
class RequestHandler(object):
|
|
|
- """A type of object that intercepts logic-related Modelverse requests, and
|
|
|
- forwards Modelverse state requests."""
|
|
|
def __init__(self):
|
|
|
- # generator_stack is a stack of GeneratorStackEntry values.
|
|
|
self.generator_stack = []
|
|
|
- # exception_handlers is a stack of
|
|
|
- # (generator_stack index, [(exception type, handler function)])
|
|
|
- # tuples.
|
|
|
- self.produce_stack_trace = True
|
|
|
- self.handlers = {
|
|
|
- 'CALL' : self.execute_call,
|
|
|
- 'CALL_ARGS' : self.execute_call_args,
|
|
|
- 'CALL_KWARGS' : self.execute_call_kwargs,
|
|
|
- 'SLEEP' : self.execute_sleep,
|
|
|
- }
|
|
|
+ self.handlers = {"CALL": self.execute_call,
|
|
|
+ "CALL_ARGS": self.execute_call_args,
|
|
|
+ "CALL_KWARGS": self.execute_call_kwargs,
|
|
|
+ "SLEEP": self.execute_sleep}
|
|
|
|
|
|
- def handle_request(self, reply):
|
|
|
- """Replies to a request from the top-of-stack generator, and returns a new request."""
|
|
|
- if not self.generator_stack:
|
|
|
- raise ValueError('handle_request cannot be called with an empty generator stack.')
|
|
|
-
|
|
|
- # Append the server's replies to the list of replies.
|
|
|
- if reply is not None:
|
|
|
- gen = self.generator_stack[-1]
|
|
|
-
|
|
|
- if gen["replies"]:
|
|
|
- gen["replies"].extend(reply)
|
|
|
- else:
|
|
|
- gen["replies"] = reply
|
|
|
+ def push_generator(self, gen):
|
|
|
+ self.generator_stack.append(gen)
|
|
|
|
|
|
- while 1:
|
|
|
- # Silence pylint's warning about catching Exception.
|
|
|
- # pylint: disable=I0011,W0703
|
|
|
+ def handle_request(self, reply):
|
|
|
+ while self.generator_stack:
|
|
|
try:
|
|
|
gen = self.generator_stack[-1]
|
|
|
- if gen["finished_requests"]:
|
|
|
- gen["pending_requests"] = gen["generator"].send(gen["replies"])
|
|
|
- gen["finished_requests"] = False
|
|
|
- gen["replies"] = None
|
|
|
- ret = self.pop_requests()
|
|
|
- if ret[0]:
|
|
|
- return ret[1]
|
|
|
+ requests = gen.send(reply)
|
|
|
+
|
|
|
+ # Generated new request, so process
|
|
|
+ if requests and requests[0][0] in self.handlers:
|
|
|
+ # Known request, so process that one
|
|
|
+ if len(requests) > 1:
|
|
|
+ raise Exception("CALL_* and SLEEP operations MUST be split in individual yields")
|
|
|
+
|
|
|
+ # Try to add a new generator to branch into
|
|
|
+ reply = None
|
|
|
+ self.generator_stack.append(None)
|
|
|
+ # This next command potentially raises a finished message already, meaning that we should stop already
|
|
|
+ # We avoid an extra try/except block by putting the None on the stack already
|
|
|
+ self.generator_stack[-1] = self.handlers[requests[0][0]](requests[0][1])
|
|
|
+ else:
|
|
|
+ # MvS request, so forward that instead
|
|
|
+ return requests
|
|
|
|
|
|
except StopIteration:
|
|
|
- # Done, so remove the generator
|
|
|
+ # Exception, so finished execution of this generator, passing on None to the caller
|
|
|
del self.generator_stack[-1]
|
|
|
- if self.generator_stack:
|
|
|
- # This generator was called from another generator.
|
|
|
- # Append 'None' to the caller's list of replies.
|
|
|
- self.append_reply(None)
|
|
|
- else:
|
|
|
- # Looks like we're done here.
|
|
|
- return None
|
|
|
+ reply = [None]
|
|
|
+
|
|
|
except primitive_functions.PrimitiveFinished as ex:
|
|
|
- # Done, so remove the generator
|
|
|
+ # Exception, so finished execution of this generator, passing on ex.result to the caller
|
|
|
del self.generator_stack[-1]
|
|
|
- if self.generator_stack:
|
|
|
- # This generator was called from another generator.
|
|
|
- # Append the callee's result to the caller's list of replies.
|
|
|
- self.append_reply(ex.result)
|
|
|
- else:
|
|
|
- # Looks like we're done here.
|
|
|
- return None
|
|
|
+ reply = [ex.result]
|
|
|
|
|
|
- def push_generator(self, gen):
|
|
|
- """Pushes a new generator onto the stack."""
|
|
|
- dd = defaultdict(lambda : None)
|
|
|
- dd["generator"] = gen
|
|
|
- dd["finished_requests"] = True
|
|
|
- self.generator_stack.append(dd)
|
|
|
- # print('Pushed generator %s. Generator count: %d' % (gen, len(self.generator_stack)))
|
|
|
-
|
|
|
- def append_reply(self, new_reply):
|
|
|
- """Appends a reply to the top-of-stack generator's list of pending replies."""
|
|
|
- if self.generator_stack[-1]["replies"] is None:
|
|
|
- self.generator_stack[-1]["replies"] = [new_reply]
|
|
|
- else:
|
|
|
- self.generator_stack[-1]["replies"].append(new_reply)
|
|
|
-
|
|
|
- def pop_requests(self):
|
|
|
- """Tries to pop a batch of Modelverse _state_ requests from the
|
|
|
- current list of requests. Known requests are executed immediately.
|
|
|
-
|
|
|
- A list of requests and a Boolean are returned. The latter is True
|
|
|
- if there are no more requests to process, and false otherwise."""
|
|
|
- requests = self.generator_stack[-1]["pending_requests"]
|
|
|
- if requests:
|
|
|
- if requests[0][0] in self.handlers:
|
|
|
- # First element is a known request
|
|
|
- elem = requests.pop(0)
|
|
|
-
|
|
|
- # The list of requests might be empty now. If so, then flag this
|
|
|
- # batch of requests as finished.
|
|
|
- if not requests:
|
|
|
- self.generator_stack[-1]["finished_requests"] = True
|
|
|
-
|
|
|
- # Handle the request.
|
|
|
- self.handlers[elem[0]](elem[1])
|
|
|
- return (False, None)
|
|
|
- else:
|
|
|
- for i, elem in enumerate(requests):
|
|
|
- if elem[0] in self.handlers:
|
|
|
- # Handle any requests that precede the known request first.
|
|
|
- pre_requests = requests[:i]
|
|
|
- del requests[:i]
|
|
|
- return pre_requests
|
|
|
-
|
|
|
- # We couldn't find a known request in the batch of requests, so we might as well
|
|
|
- # handle them all at once then.
|
|
|
- self.generator_stack[-1]["finished_requests"] = True
|
|
|
- #return requests
|
|
|
- return (True, requests)
|
|
|
+ except primitive_functions.SleepKernel:
|
|
|
+ # Processing sleep, so pop its generator and reraise
|
|
|
+ del self.generator_stack[-1]
|
|
|
+ raise
|
|
|
|
|
|
def execute_call(self, request_args):
|
|
|
- """Executes a CALL-request with the given argument list."""
|
|
|
- # Format: ("CALL", [gen])
|
|
|
- gen, = request_args
|
|
|
- self.push_generator(gen)
|
|
|
+ return request_args[0]
|
|
|
|
|
|
def execute_call_kwargs(self, request_args):
|
|
|
- """Executes a CALL_KWARGS-request with the given argument list."""
|
|
|
- # Format: ("CALL_KWARGS", [func, kwargs])
|
|
|
- # This format is useful because it also works for functions that
|
|
|
- # throw an exception but never yield.
|
|
|
- func, kwargs = request_args
|
|
|
- # We need to be extra careful here, because func(**kwargs) might
|
|
|
- # not be a generator at all: it might simply be a method that
|
|
|
- # raises an exception. To cope with this we need to push a dummy
|
|
|
- # entry onto the stack if a StopIteration or PrimtiveFinished
|
|
|
- # exception is thrown. The logic in execute_yields will then pop
|
|
|
- # that dummy entry.
|
|
|
- try:
|
|
|
- self.push_generator(func(**kwargs))
|
|
|
- except primitive_functions.PrimitiveFinished, StopIteration:
|
|
|
- self.push_generator(None)
|
|
|
- raise
|
|
|
- except:
|
|
|
- print("EXCEPTION for " + str(locals()))
|
|
|
- raise
|
|
|
+ return request_args[0](**(request_args[1]))
|
|
|
|
|
|
def execute_call_args(self, request_args):
|
|
|
- """Executes a CALL_ARGS-request with the given argument list."""
|
|
|
- # Format: ("CALL_ARGS", [gen, args])
|
|
|
- func, args = request_args
|
|
|
- # We need to be extra careful here, because func(*args) might
|
|
|
- # not be a generator at all: it might simply be a method that
|
|
|
- # raises an exception. To cope with this we need to push a dummy
|
|
|
- # entry onto the stack if a StopIteration or PrimtiveFinished
|
|
|
- # exception is thrown. The logic in execute_yields will then pop
|
|
|
- # that dummy entry.
|
|
|
- try:
|
|
|
- self.push_generator(func(*args))
|
|
|
- except primitive_functions.PrimitiveFinished, StopIteration:
|
|
|
- self.push_generator(None)
|
|
|
- raise
|
|
|
+ return request_args[0](*(request_args[1]))
|
|
|
|
|
|
def execute_sleep(self, request_args):
|
|
|
- """Executes a SLEEP-request with the given argument list."""
|
|
|
- self.append_reply(None)
|
|
|
raise primitive_functions.SleepKernel(request_args[0], request_args[1])
|