request_handler.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import modelverse_kernel.primitives as primitive_functions
  2. class KnownRequestHandled(Exception):
  3. """An exception that signifies that a known request was handled."""
  4. pass
  5. class RequestHandler(object):
  6. """A type of object that intercepts logic-related Modelverse requests, and
  7. forwards Modelverse state requests."""
  8. def __init__(self):
  9. # generator_stack is a stack of (generator, pending requests, request replies, has-reply)
  10. # tuples.
  11. self.generator_stack = []
  12. # exception_handlers is a stack of (generator_stack index, exception handler function)
  13. # tuples.
  14. self.exception_handlers = []
  15. self.handlers = {
  16. 'RUN' : self.execute_run
  17. }
  18. def is_active(self):
  19. """Tests if this request handler has a top-of-stack generator."""
  20. return len(self.generator_stack) > 0
  21. def handle_request(self, reply):
  22. """Replies to a request from the top-of-stack generator, and returns a new request."""
  23. if not self.is_active():
  24. raise ValueError('handle_request cannot be called with an empty generator stack.')
  25. # Append the server's replies to the list of replies.
  26. self.extend_replies(reply)
  27. while 1:
  28. try:
  29. if self.has_pending_requests():
  30. try:
  31. # Try to pop a request for the modelverse state.
  32. result = self.pop_requests()
  33. return result
  34. except KnownRequestHandled:
  35. # Carry on.
  36. pass
  37. # Perform a single generator step.
  38. self.step()
  39. except StopIteration:
  40. # Done, so remove the generator
  41. self.generator_stack.pop()
  42. if self.is_active():
  43. # This generator was called from another generator.
  44. # Append 'None' to the caller's list of replies.
  45. self.append_reply(None)
  46. else:
  47. # Looks like we're done here.
  48. return None
  49. except primitive_functions.PrimitiveFinished as ex:
  50. # Done, so remove the generator
  51. self.generator_stack.pop()
  52. if self.is_active():
  53. # This generator was called from another generator.
  54. # Append the callee's result to the caller's list of replies.
  55. self.append_reply(ex.result)
  56. else:
  57. # Looks like we're done here.
  58. return None
  59. except:
  60. raise
  61. def set_finished_requests_flag(self):
  62. """Sets the finished_requests flag in the top-of-stack tuple."""
  63. current_generator, requests, _, replies, has_reply = self.generator_stack[-1]
  64. self.generator_stack[-1] = (current_generator, requests, True, replies, has_reply)
  65. def has_pending_requests(self):
  66. """Tests if the top-of-stack generator has pending requests."""
  67. _, _, finished_requests, _, _ = self.generator_stack[-1]
  68. return not finished_requests
  69. def push_generator(self, gen):
  70. """Pushes a new generator onto the stack."""
  71. self.generator_stack.append((gen, None, True, [], False))
  72. def append_reply(self, new_reply):
  73. """Appends a reply to the top-of-stack generator's list of pending replies."""
  74. current_generator, requests, requests_done, replies, has_reply = self.generator_stack[-1]
  75. replies.append(new_reply)
  76. has_reply = True
  77. self.generator_stack[-1] = (current_generator, requests, requests_done, replies, has_reply)
  78. def extend_replies(self, new_replies):
  79. """Appends a list of replies to the top-of-stack generator's list of pending replies."""
  80. current_generator, requests, requests_done, replies, has_reply = self.generator_stack[-1]
  81. if new_replies is not None:
  82. replies.extend(new_replies)
  83. has_reply = True
  84. self.generator_stack[-1] = (
  85. current_generator, requests, requests_done, replies, has_reply)
  86. def step(self):
  87. """Performs a single step: accumulated replies are fed to the generator,
  88. which then produces requests."""
  89. current_generator, _, _, replies, has_reply = self.generator_stack[-1]
  90. # Send the replies to the generator, and ask for new requests.
  91. requests = current_generator.send(replies if has_reply else None)
  92. # Update the entry on the stack.
  93. self.generator_stack[-1] = (current_generator, requests, False, [], False)
  94. def pop_requests(self):
  95. """Tries to pop a batch of Modelverse _state_ requests from the
  96. current list of requests. Known requests are executed immediately.
  97. A list of requests and a Boolean are returned. The latter is True
  98. if there are no more requests to process, and false otherwise."""
  99. _, requests, _, _, _ = self.generator_stack[-1]
  100. if requests is None or len(requests) == 0:
  101. # Couldn't find a request for the state to handle.
  102. self.set_finished_requests_flag()
  103. return requests
  104. for i, elem in enumerate(requests):
  105. if elem[0] in self.handlers:
  106. # The kernel should handle known requests.
  107. if i > 0:
  108. # Handle any requests that precede the known request first.
  109. pre_requests = requests[:i]
  110. del requests[:i]
  111. return pre_requests
  112. # The known request must be the first element in the list. Pop it.
  113. requests.pop(0)
  114. # The list of requests might be empty now. If so, then flag this
  115. # batch of requests as finished.
  116. if len(requests) == 0:
  117. self.set_finished_requests_flag()
  118. # Handle the request.
  119. _, request_args = elem
  120. self.handlers[elem[0]](request_args)
  121. # We couldn't find a RUN-request in the batch of requests, so we might as well
  122. # handle them all at once then.
  123. self.set_finished_requests_flag()
  124. return requests
  125. def execute_run(self, request_args):
  126. """Executes a RUN-request with the given argument list."""
  127. # The list of requests might be empty now. If so, then flag this
  128. # batch of requests as finished.
  129. if len(request_args) == 1:
  130. # Format: ("RUN", [gen])
  131. gen, = request_args
  132. self.push_generator(gen)
  133. raise KnownRequestHandled()
  134. else:
  135. # Format: ("RUN", [func, kwargs])
  136. # This format is useful because it also works for functions that
  137. # throw an exception but never yield.
  138. func, kwargs = request_args
  139. # We need to be extra careful here, because func(**kwargs) might
  140. # not be a generator at all: it might simply be a method that
  141. # raises an exception. To cope with this we need to push a dummy
  142. # entry onto the stack if a StopIteration or PrimtiveFinished
  143. # exception is thrown. The logic in execute_yields will then pop
  144. # that dummy entry.
  145. try:
  146. self.push_generator(func(**kwargs))
  147. raise KnownRequestHandled()
  148. except StopIteration:
  149. self.push_generator(None)
  150. raise
  151. except primitive_functions.PrimitiveFinished:
  152. self.push_generator(None)
  153. raise