statecharts_core.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. import abc
  2. import re
  3. from kernel.mvk_server.python_runtime.accurate_time import time
  4. import threading
  5. import traceback
  6. import math
  7. from kernel.mvk_server.python_runtime.nextafter import nextafter
  8. from kernel.mvk_server.python_runtime.infinity import INFINITY
  9. from kernel.mvk_server.python_runtime.event_queue import EventQueue
  10. try:
  11. from Queue import Queue, Empty
  12. except ImportError:
  13. from queue import Queue, Empty
  14. class RuntimeException(Exception):
  15. def __init__(self, message):
  16. self.message = message
  17. def __str__(self):
  18. return repr(self.message)
  19. class AssociationException(RuntimeException):
  20. pass
  21. class AssociationReferenceException(RuntimeException):
  22. pass
  23. class ParameterException(RuntimeException):
  24. pass
  25. class InputException(RuntimeException):
  26. pass
  27. class Association(object):
  28. #wrapper object for one association relation
  29. def __init__(self, to_class, min_card, max_card):
  30. self.to_class = to_class
  31. self.min_card = min_card
  32. self.max_card = max_card
  33. self.instances = {} # maps index (as string) to instance
  34. self.instances_to_ids = {}
  35. self.size = 0
  36. self.next_id = 0
  37. def allowedToAdd(self):
  38. return self.max_card == -1 or self.size < self.max_card
  39. def allowedToRemove(self):
  40. return self.min_card == -1 or self.size > self.min_card
  41. def addInstance(self, instance):
  42. if self.allowedToAdd() :
  43. new_id = self.next_id
  44. self.next_id += 1
  45. self.instances[new_id] = instance
  46. self.instances_to_ids[instance] = new_id
  47. self.size += 1
  48. return new_id
  49. else :
  50. raise AssociationException("Not allowed to add the instance to the association.")
  51. def removeInstance(self, instance):
  52. if self.allowedToRemove() :
  53. del self.instances[self.instances_to_ids[instance]]
  54. del self.instances_to_ids[instance]
  55. self.size -= 1
  56. else :
  57. raise AssociationException("Not allowed to remove the instance from the association.")
  58. def getInstance(self, index):
  59. try :
  60. return self.instances[index]
  61. except IndexError :
  62. raise AssociationException("Invalid index for fetching instance(s) from association.")
  63. """class InstanceWrapper(object):
  64. #wrapper object for an instance and its relevant information needed in the object manager
  65. def __init__(self, instance, associations):
  66. self.instance = instance
  67. self.associations = {}
  68. for association in associations :
  69. self.associations[association.getName()] = association
  70. def getAssociation(self, name):
  71. try :
  72. return self.associations[name]
  73. except KeyError :
  74. raise AssociationReferenceException("Unknown association %s." % name)
  75. def getInstance(self):
  76. return self.instance"""
  77. class ObjectManagerBase(object):
  78. __metaclass__ = abc.ABCMeta
  79. def __init__(self, controller):
  80. self.controller = controller
  81. self.events = EventQueue()
  82. self.instances = set() #a dictionary that maps RuntimeClassBase to InstanceWrapper
  83. def addEvent(self, event, time_offset = 0.0):
  84. self.events.add(event, time_offset)
  85. # Broadcast an event to all instances
  86. def broadcast(self, new_event):
  87. for i in self.instances:
  88. i.addEvent(new_event)
  89. def getWaitTime(self):
  90. #first get waiting time of the object manager's events
  91. smallest_time = self.events.getEarliestTime()
  92. #check all the instances
  93. for instance in self.instances:
  94. smallest_time = min(smallest_time, instance.getEarliestEventTime())
  95. return smallest_time
  96. def stepAll(self, delta):
  97. self.step(delta)
  98. for i in self.instances:
  99. i.step(delta)
  100. def step(self, delta):
  101. self.events.decreaseTime(delta)
  102. for event in self.events.popDueEvents() :
  103. self.handleEvent(event)
  104. def start(self):
  105. for i in self.instances:
  106. i.start()
  107. def handleEvent(self, e):
  108. if e.getName() == "narrow_cast" :
  109. self.handleNarrowCastEvent(e.getParameters())
  110. elif e.getName() == "broad_cast" :
  111. self.handleBroadCastEvent(e.getParameters())
  112. elif e.getName() == "create_instance" :
  113. self.handleCreateEvent(e.getParameters())
  114. elif e.getName() == "associate_instance" :
  115. self.handleAssociateEvent(e.getParameters())
  116. elif e.getName() == "start_instance" :
  117. self.handleStartInstanceEvent(e.getParameters())
  118. elif e.getName() == "delete_instance" :
  119. self.handleDeleteInstanceEvent(e.getParameters())
  120. def processAssociationReference(self, input_string):
  121. if len(input_string) == 0 :
  122. raise AssociationReferenceException("Empty association reference.")
  123. regex_pattern = re.compile("^([a-zA-Z_]\w*)(?:\[(\d+)\])?$")
  124. path_string = input_string.split("/")
  125. result = []
  126. for piece in path_string :
  127. match = regex_pattern.match(piece)
  128. if match :
  129. name = match.group(1)
  130. index = match.group(2)
  131. if index is None :
  132. index = -1
  133. result.append((name,int(index)))
  134. else :
  135. raise AssociationReferenceException("Invalid entry in association reference. Input string: " + input_string)
  136. return result
  137. def handleStartInstanceEvent(self, parameters):
  138. if len(parameters) != 2 :
  139. raise ParameterException ("The start instance event needs 2 parameters.")
  140. else :
  141. source = parameters[0]
  142. traversal_list = self.processAssociationReference(parameters[1])
  143. for i in self.getInstances(source, traversal_list) :
  144. i["instance"].start()
  145. source.addEvent(Event("instance_started", parameters = [parameters[1]]))
  146. def handleBroadCastEvent(self, parameters):
  147. if len(parameters) != 1 :
  148. raise ParameterException ("The broadcast event needs 1 parameter.")
  149. self.broadcast(parameters[0])
  150. def handleCreateEvent(self, parameters):
  151. if len(parameters) < 2 :
  152. raise ParameterException ("The create event needs at least 2 parameters.")
  153. source = parameters[0]
  154. association_name = parameters[1]
  155. association = source.associations[association_name]
  156. #association = self.instances_map[source].getAssociation(association_name)
  157. if association.allowedToAdd() :
  158. ''' allow subclasses to be instantiated '''
  159. class_name = association.to_class if len(parameters) == 2 else parameters[2]
  160. new_instance = self.createInstance(class_name, parameters[3:])
  161. if not new_instance:
  162. raise ParameterException("Creating instance: no such class: " + class_name)
  163. #index = association.addInstance(new_instance)
  164. try:
  165. index = association.addInstance(new_instance)
  166. except AssociationException as exception:
  167. raise RuntimeException("Error adding instance to association '" + association_name + "': " + str(exception))
  168. p = new_instance.associations.get("parent")
  169. if p:
  170. p.addInstance(source)
  171. source.addEvent(Event("instance_created", None, [association_name+"["+str(index)+"]"]))
  172. else :
  173. source.addEvent(Event("instance_creation_error", None, [association_name]))
  174. def handleDeleteInstanceEvent(self, parameters):
  175. if len(parameters) < 2 :
  176. raise ParameterException ("The delete event needs at least 2 parameters.")
  177. else :
  178. source = parameters[0]
  179. association_name = parameters[1]
  180. traversal_list = self.processAssociationReference(association_name)
  181. instances = self.getInstances(source, traversal_list)
  182. #association = self.instances_map[source].getAssociation(traversal_list[0][0])
  183. association = source.associations[traversal_list[0][0]]
  184. for i in instances:
  185. try:
  186. association.removeInstance(i["instance"])
  187. self.instances.discard(i["instance"])
  188. except AssociationException as exception:
  189. raise RuntimeException("Error removing instance from association '" + association_name + "': " + str(exception))
  190. i["instance"].stop()
  191. #if hasattr(i.instance, 'user_defined_destructor'):
  192. i["instance"].user_defined_destructor()
  193. source.addEvent(Event("instance_deleted", parameters = [parameters[1]]))
  194. def handleAssociateEvent(self, parameters):
  195. if len(parameters) != 3 :
  196. raise ParameterException ("The associate_instance event needs 3 parameters.")
  197. else :
  198. source = parameters[0]
  199. to_copy_list = self.getInstances(source,self.processAssociationReference(parameters[1]))
  200. if len(to_copy_list) != 1 :
  201. raise AssociationReferenceException ("Invalid source association reference.")
  202. wrapped_to_copy_instance = to_copy_list[0]["instance"]
  203. dest_list = self.processAssociationReference(parameters[2])
  204. if len(dest_list) == 0 :
  205. raise AssociationReferenceException ("Invalid destination association reference.")
  206. last = dest_list.pop()
  207. if last[1] != -1 :
  208. raise AssociationReferenceException ("Last association name in association reference should not be accompanied by an index.")
  209. for i in self.getInstances(source, dest_list) :
  210. i["instance"].associations[last[0]].addInstance(wrapped_to_copy_instance)
  211. def handleNarrowCastEvent(self, parameters):
  212. if len(parameters) != 3 :
  213. raise ParameterException ("The associate_instance event needs 3 parameters.")
  214. source = parameters[0]
  215. traversal_list = self.processAssociationReference(parameters[1])
  216. cast_event = parameters[2]
  217. for i in self.getInstances(source, traversal_list) :
  218. i["instance"].addEvent(cast_event)
  219. def getInstances(self, source, traversal_list):
  220. currents = [{
  221. "instance" : source,
  222. "ref" : None,
  223. "assoc_name" : None,
  224. "assoc_index" : None
  225. }]
  226. #currents = [source]
  227. for (name, index) in traversal_list :
  228. nexts = []
  229. for current in currents :
  230. association = current["instance"].associations[name]
  231. if (index >= 0 ) :
  232. nexts.append({
  233. "instance" : association.instances[index],
  234. "ref" : current["instance"],
  235. "assoc_name" : name,
  236. "assoc_index" : index
  237. })
  238. elif (index == -1) :
  239. for i in association.instances:
  240. nexts.append({
  241. "instance" : association.instances[i],
  242. "ref" : current["instance"],
  243. "assoc_name" : name,
  244. "assoc_index" : index
  245. })
  246. #nexts.extend( association.instances.values() )
  247. else :
  248. raise AssociationReferenceException("Incorrect index in association reference.")
  249. currents = nexts
  250. return currents
  251. @abc.abstractmethod
  252. def instantiate(self, class_name, construct_params):
  253. pass
  254. def createInstance(self, to_class, construct_params = []):
  255. instance = self.instantiate(to_class, construct_params)
  256. self.instances.add(instance)
  257. return instance
  258. class Event(object):
  259. def __init__(self, event_name, port = "", parameters = []):
  260. self.name = event_name
  261. self.parameters = parameters
  262. self.port = port
  263. def getName(self):
  264. return self.name
  265. def getPort(self):
  266. return self.port
  267. def getParameters(self):
  268. return self.parameters
  269. def __repr__(self):
  270. representation = "(event name : " + str(self.name) + "; port : " + str(self.port)
  271. if self.parameters :
  272. representation += "; parameters : " + str(self.parameters)
  273. representation += ")"
  274. return representation
  275. class OutputListener(object):
  276. def __init__(self, port_names):
  277. self.port_names = port_names
  278. self.queue = Queue()
  279. def add(self, event):
  280. if len(self.port_names) == 0 or event.getPort() in self.port_names :
  281. self.queue.put_nowait(event)
  282. """ Tries for timeout seconds to fetch an event, returns None if failed.
  283. 0 as timeout means no waiting (blocking), returns None if queue is empty.
  284. -1 as timeout means blocking until an event can be fetched. """
  285. def fetch(self, timeout = 0):
  286. if timeout < 0:
  287. timeout = INFINITY
  288. while timeout >= 0:
  289. try:
  290. # wait in chunks of 100ms because we
  291. # can't receive (keyboard)interrupts while waiting
  292. return self.queue.get(True, 0.1 if timeout > 0.1 else timeout)
  293. except Empty:
  294. timeout -= 0.1
  295. return None
  296. class InputPortEntry(object):
  297. def __init__(self, virtual_name, instance):
  298. self.virtual_name = virtual_name
  299. self.instance = instance
  300. class ControllerBase(object):
  301. def __init__(self, object_manager):
  302. self.object_manager = object_manager
  303. self.private_port_counter = 0
  304. # Keep track of input ports
  305. self.input_ports = {}
  306. self.input_queue = EventQueue()
  307. # Keep track of output ports
  308. self.output_ports = []
  309. self.output_listeners = []
  310. # Let statechart run one last time before stopping
  311. self.done = False
  312. def addInputPort(self, virtual_name, instance = None):
  313. if instance == None :
  314. port_name = virtual_name
  315. else:
  316. port_name = "private_" + str(self.private_port_counter) + "_" + virtual_name
  317. self.private_port_counter += 1
  318. self.input_ports[port_name] = InputPortEntry(virtual_name, instance)
  319. return port_name
  320. def addOutputPort(self, port_name):
  321. self.output_ports.append(port_name)
  322. def broadcast(self, new_event):
  323. self.object_manager.broadcast(new_event)
  324. def start(self):
  325. self.object_manager.start()
  326. def stop(self):
  327. pass
  328. def addInput(self, input_event_list, time_offset = 0.0):
  329. if not isinstance(input_event_list, list):
  330. input_event_list = [input_event_list]
  331. for e in input_event_list:
  332. if e.getName() == "" :
  333. raise InputException("Input event can't have an empty name.")
  334. if e.getPort() not in self.input_ports :
  335. raise InputException("Input port mismatch, no such port: " + e.getPort() + ".")
  336. self.input_queue.add(input_event_list, time_offset)
  337. def getWaitTime(self):
  338. return min(self.object_manager.getWaitTime(), self.input_queue.getEarliestTime())
  339. def handleInput(self, delta):
  340. self.input_queue.decreaseTime(delta)
  341. for events in self.input_queue.popDueEvents():
  342. for e in events:
  343. input_port = self.input_ports[e.getPort()]
  344. e.port = input_port.virtual_name
  345. target_instance = input_port.instance
  346. if target_instance == None:
  347. self.broadcast(e)
  348. else:
  349. target_instance.addEvent(e)
  350. def outputEvent(self, event):
  351. for listener in self.output_listeners :
  352. listener.add(event)
  353. def addOutputListener(self, ports):
  354. listener = OutputListener(ports)
  355. self.output_listeners.append(listener)
  356. return listener
  357. def addMyOwnOutputListener(self, listener):
  358. self.output_listeners.append(listener)
  359. # deprecated, to add multiple events, use addInput instead
  360. def addEventList(self, event_list):
  361. for (event, time_offset) in event_list :
  362. self.addInput(event, time_offset)
  363. def getObjectManager(self):
  364. return self.object_manager
  365. class GameLoopControllerBase(ControllerBase):
  366. def __init__(self, object_manager):
  367. ControllerBase.__init__(self, object_manager)
  368. def update(self, delta):
  369. self.handleInput(delta)
  370. self.object_manager.stepAll(delta)
  371. class EventLoop:
  372. # parameters:
  373. # schedule - a callback scheduling another callback in the event loop
  374. # this callback should take 2 parameters: (callback, timeout) and return an ID
  375. # clear - a callback that clears a scheduled callback
  376. # this callback should take an ID that was returned by 'schedule'
  377. def __init__(self, schedule, clear):
  378. self.schedule_callback = schedule
  379. self.clear_callback = clear
  380. self.scheduled_id = None
  381. self.last_time = None
  382. self.next_wakeup = None
  383. self.last_print = 0.0
  384. def getScheduledTimeout(self):
  385. if self.last_time and self.next_wakeup:
  386. return self.next_wakeup - self.last_time
  387. else:
  388. return INFINITY
  389. # schedule relative to last_time
  390. #
  391. # argument 'wait_time' is the amount of virtual (simulated) time to wait
  392. #
  393. # NOTE: if the next wakeup (in simulated time) is in the past, the timeout is '0',
  394. # but because scheduling '0' timeouts hurts performance, we don't schedule anything
  395. # and return False instead
  396. def schedule(self, f, wait_time):
  397. if self.scheduled_id:
  398. # if the following error occurs, it is probably due to a flaw in the logic of EventLoopControllerBase
  399. raise RuntimeException("EventLoop class intended to maintain at most 1 scheduled callback.")
  400. if wait_time == INFINITY:
  401. self.last_time = None
  402. self.next_wakeup = None
  403. is_scheduled = True
  404. else:
  405. now = time()
  406. if not self.last_time:
  407. self.last_time = now
  408. self.next_wakeup = self.last_time + wait_time
  409. # self.last_time is a very large value, and wait_time can be very small, so
  410. if self.next_wakeup - self.last_time < wait_time:
  411. # due to floating point imprecision, it is possible for a nonzero wait-time to advance simulated time not enough to pop the next event, potentially even causing the model to hang, so we always take the ceil of the exact result of the addition self.last_time + wait_time.
  412. self.next_wakeup = nextafter(self.next_wakeup, INFINITY)
  413. remaining = max(self.next_wakeup - now, 0.0)
  414. is_scheduled, self.scheduled_id = self.schedule_callback(f, remaining)
  415. return is_scheduled
  416. def clear(self):
  417. if self.scheduled_id:
  418. self.clear_callback(self.scheduled_id)
  419. self.scheduled_id = None
  420. def nextDelta(self):
  421. now = time()
  422. if self.next_wakeup:
  423. simulated_now = self.next_wakeup
  424. else:
  425. simulated_now = now
  426. if now - self.last_print > 1.0:
  427. behind_schedule = now - simulated_now
  428. if behind_schedule > 0.1:
  429. print("Warning: running %.f ms behind schedule" % (behind_schedule*1000.0))
  430. self.last_print = now
  431. if self.last_time:
  432. delta = simulated_now - self.last_time
  433. else:
  434. delta = 0.0
  435. self.last_time = simulated_now
  436. self.next_wakeup = None
  437. return delta
  438. # returns elapsed time since delta
  439. def elapsed(self):
  440. if self.last_time:
  441. return time() - self.last_time
  442. else:
  443. return 0.0
  444. class EventLoopControllerBase(ControllerBase):
  445. def __init__(self, object_manager, event_loop, finished_callback = None):
  446. ControllerBase.__init__(self, object_manager)
  447. self.event_loop = event_loop
  448. self.finished_callback = finished_callback
  449. def addInput(self, input_event, time_offset = 0.0):
  450. elapsed = self.event_loop.elapsed()
  451. controller_timeout = time_offset + elapsed
  452. ControllerBase.addInput(self, input_event, controller_timeout)
  453. if controller_timeout < self.event_loop.getScheduledTimeout():
  454. # added event's timeout is sooner than existing timeout -> re-schedule
  455. self.event_loop.clear()
  456. if not self.event_loop.schedule(self.run, controller_timeout):
  457. self.run()
  458. def start(self):
  459. ControllerBase.start(self)
  460. self.run()
  461. def stop(self):
  462. self.event_loop.clear()
  463. ControllerBase.stop(self)
  464. def run(self):
  465. while True:
  466. # clear existing timeout
  467. self.event_loop.clear()
  468. # calculate last time since simulation
  469. delta = self.event_loop.nextDelta()
  470. # simulate
  471. self.handleInput(delta)
  472. self.object_manager.stepAll(delta)
  473. # schedule next timeout
  474. wait_time = self.getWaitTime()
  475. scheduled = self.event_loop.schedule(self.run, wait_time)
  476. if wait_time == INFINITY:
  477. if self.finished_callback:
  478. self.finished_callback()
  479. if scheduled:
  480. break
  481. class ThreadsControllerBase(ControllerBase):
  482. def __init__(self, object_manager, keep_running):
  483. ControllerBase.__init__(self, object_manager)
  484. self.keep_running = keep_running
  485. self.input_condition = threading.Condition()
  486. self.stop_thread = False
  487. self.thread = threading.Thread(target=self.run)
  488. def handleInput(self, delta):
  489. with self.input_condition:
  490. ControllerBase.handleInput(self, delta)
  491. def start(self):
  492. self.thread.start()
  493. def stop(self):
  494. with self.input_condition:
  495. self.stop_thread = True
  496. self.input_condition.notifyAll()
  497. def getWaitTime(self):
  498. """Compute time untill earliest next event"""
  499. with self.input_condition:
  500. wait_time = ControllerBase.getWaitTime(self)
  501. if wait_time == INFINITY :
  502. if self.done :
  503. self.done = False
  504. else :
  505. self.done = True
  506. return 0.0
  507. return wait_time
  508. def handleWaiting(self):
  509. with self.input_condition:
  510. wait_time = self.getWaitTime()
  511. if(wait_time <= 0.0):
  512. return
  513. if wait_time == INFINITY :
  514. if self.keep_running :
  515. self.input_condition.wait() #Wait for a signals
  516. else :
  517. self.stop_thread = True
  518. elif wait_time != 0.0 :
  519. reduced_wait_time = wait_time - (time() - self.last_recorded_time)
  520. if reduced_wait_time > 0.0 :
  521. self.input_condition.wait(reduced_wait_time)
  522. def run(self):
  523. self.last_recorded_time = time()
  524. super(ThreadsControllerBase, self).start()
  525. last_iteration_time = 0.0
  526. while True:
  527. with self.input_condition:
  528. self.handleInput(last_iteration_time)
  529. #Compute the new state based on internal events
  530. self.object_manager.stepAll(last_iteration_time)
  531. self.handleWaiting()
  532. with self.input_condition:
  533. if self.stop_thread :
  534. break
  535. previous_recorded_time = self.last_recorded_time
  536. self.last_recorded_time = time()
  537. last_iteration_time = self.last_recorded_time - previous_recorded_time
  538. def join(self):
  539. self.thread.join()
  540. def addInput(self, input_event, time_offset = 0.0):
  541. with self.input_condition:
  542. super(ThreadsControllerBase, self).addInput(input_event, time_offset)
  543. self.input_condition.notifyAll()
  544. def addEventList(self, event_list):
  545. with self.input_condition:
  546. super(ThreadsControllerBase, self).addEventList(event_list)
  547. class StatechartSemantics:
  548. # Big Step Maximality
  549. TakeOne = 0
  550. TakeMany = 1
  551. # Concurrency - not implemented yet
  552. Single = 0
  553. Many = 1
  554. # Preemption - not implemented yet
  555. NonPreemptive = 0
  556. Preemptive = 1
  557. # Internal Event Lifeline
  558. Queue = 0
  559. NextSmallStep = 1
  560. NextComboStep = 2
  561. # Input Event Lifeline
  562. Whole = 0
  563. FirstSmallStep = 1
  564. FirstComboStep = 2
  565. # Priority
  566. SourceParent = 0
  567. SourceChild = 1
  568. # TODO: add Memory Protocol options
  569. def __init__(self):
  570. # default semantics:
  571. self.big_step_maximality = self.TakeMany
  572. self.concurrency = self.Single
  573. self.internal_event_lifeline = self.Queue
  574. #self.input_event_lifeline = self.FirstComboStep
  575. self.input_event_lifeline = self.FirstSmallStep
  576. self.priority = self.SourceParent
  577. class RuntimeClassBase(object):
  578. __metaclass__ = abc.ABCMeta
  579. def __init__(self, controller):
  580. self.active = False
  581. self.is_stable = True
  582. self.events = EventQueue()
  583. self.controller = controller
  584. self.timers = None
  585. self.inports = {}
  586. self.semantics = StatechartSemantics()
  587. def start(self):
  588. self.current_state = {}
  589. self.history_state = {}
  590. self.timers = {}
  591. self.big_step = BigStepState()
  592. self.combo_step = ComboStepState()
  593. self.small_step = SmallStepState()
  594. self.active = True
  595. self.is_stable = False
  596. self.initializeStatechart()
  597. self.processBigStepOutput()
  598. def stop(self):
  599. self.active = False
  600. def addEvent(self, event_list, time_offset = 0.0):
  601. if not isinstance(event_list, list):
  602. event_list = [event_list]
  603. self.events.add(event_list, time_offset)
  604. def getEarliestEventTime(self) :
  605. if not self.active:
  606. return INFINITY
  607. if not self.is_stable:
  608. return 0.0
  609. if self.timers:
  610. return min(self.events.getEarliestTime(), min(self.timers.values()))
  611. return self.events.getEarliestTime()
  612. def processBigStepOutput(self):
  613. for e in self.big_step.getOutputEvents():
  614. self.controller.outputEvent(e)
  615. for e in self.big_step.getOutputEventsOM():
  616. self.controller.object_manager.addEvent(e)
  617. def step(self, delta):
  618. if not self.active :
  619. return
  620. # decrease event queue time
  621. self.events.decreaseTime(delta)
  622. # decrease timers time
  623. next_timers = {}
  624. for (key,value) in list(self.timers.items()):
  625. time = value - delta
  626. if time <= 0.0 :
  627. self.addEvent( Event("_" + str(key) + "after"), time)
  628. else :
  629. next_timers[key] = time
  630. self.timers = next_timers
  631. # execute big step(s)
  632. due = self.events.popDueEvents()
  633. if not due and not self.is_stable:
  634. due = [[]]
  635. for input_events in due:
  636. # perform 1 big step per slot in 'due'
  637. self.is_stable = not self.bigStep(input_events)
  638. self.processBigStepOutput()
  639. def inState(self, nodes):
  640. for c in list(self.current_state.values()):
  641. new_nodes = []
  642. for n in nodes:
  643. if not (n in c):
  644. new_nodes.append(n)
  645. nodes = new_nodes
  646. if len(nodes) == 0:
  647. return True
  648. return False
  649. def bigStep(self, input_events):
  650. #print "new big step"
  651. self.big_step.next(input_events)
  652. self.small_step.reset()
  653. self.combo_step.reset()
  654. while self.comboStep():
  655. self.big_step.setStepped()
  656. if self.semantics.big_step_maximality == StatechartSemantics.TakeOne:
  657. break # Take One -> only one combo step allowed
  658. return self.big_step.hasStepped()
  659. def comboStep(self):
  660. #print "new combo step"
  661. self.combo_step.next()
  662. while self.smallStep():
  663. self.combo_step.setStepped()
  664. return self.combo_step.hasStepped()
  665. def smallStep(self):
  666. if self.small_step.hasStepped():
  667. self.small_step.next()
  668. self.generateCandidates()
  669. if self.small_step.hasCandidates():
  670. #print "new small step, have " + str(len(self.small_step.getCandidates())) + " candidates"
  671. if self.semantics.concurrency == StatechartSemantics.Single:
  672. transition, parameters = self.small_step.getCandidates()[0] # execute first of candidates
  673. transition(parameters)
  674. elif self.semantics.concurrency == StatechartSemantics.Many:
  675. pass # TODO: implement
  676. self.small_step.setStepped()
  677. return self.small_step.hasStepped()
  678. def getEnabledEvents(self):
  679. result = self.small_step.getCurrentEvents() + self.combo_step.getCurrentEvents()
  680. if self.semantics.input_event_lifeline == StatechartSemantics.Whole or (
  681. not self.big_step.hasStepped() and
  682. (self.semantics.input_event_lifeline == StatechartSemantics.FirstComboStep or (
  683. not self.combo_step.hasStepped() and
  684. self.semantics.input_event_lifeline == StatechartSemantics.FirstSmallStep))):
  685. result += self.big_step.getInputEvents()
  686. return result
  687. def raiseInternalEvent(self, event):
  688. if self.semantics.internal_event_lifeline == StatechartSemantics.NextSmallStep:
  689. self.small_step.addNextEvent(event)
  690. elif self.semantics.internal_event_lifeline == StatechartSemantics.NextComboStep:
  691. self.combo_step.addNextEvent(event)
  692. elif self.semantics.internal_event_lifeline == StatechartSemantics.Queue:
  693. self.events.add([event], 0.0)
  694. @abc.abstractmethod
  695. def initializeStatechart(self):
  696. pass
  697. @abc.abstractmethod
  698. def generateCandidates(self):
  699. pass
  700. class BigStepState(object):
  701. def __init__(self):
  702. self.input_events = [] # input events received from environment before beginning of big step (e.g. from object manager, from input port)
  703. self.output_events_port = [] # output events to be sent to output port after big step ends.
  704. self.output_events_om = [] # output events to be sent to object manager after big step ends.
  705. self.has_stepped = True
  706. def next(self, input_events):
  707. self.input_events = input_events
  708. self.output_events_port = []
  709. self.output_events_om = []
  710. self.has_stepped = False
  711. def getInputEvents(self):
  712. return self.input_events
  713. def getOutputEvents(self):
  714. return self.output_events_port
  715. def getOutputEventsOM(self):
  716. return self.output_events_om
  717. def outputEvent(self, event):
  718. self.output_events_port.append(event)
  719. def outputEventOM(self, event):
  720. self.output_events_om.append(event)
  721. def setStepped(self):
  722. self.has_stepped = True
  723. def hasStepped(self):
  724. return self.has_stepped
  725. class ComboStepState(object):
  726. def __init__(self):
  727. self.current_events = [] # set of enabled events during combo step
  728. self.next_events = [] # internal events that were raised during combo step
  729. self.changed = [] # set of all or-states that were the arena of a triggered transition during big step.
  730. self.has_stepped = True
  731. def reset(self):
  732. self.current_events = []
  733. self.next_events = []
  734. def next(self):
  735. self.current_events = self.next_events
  736. self.next_events = []
  737. self.changed = []
  738. self.has_stepped = False
  739. def addNextEvent(self, event):
  740. self.next_events.append(event)
  741. def getCurrentEvents(self):
  742. return self.current_events
  743. def setArenaChanged(self, arena):
  744. self.changed.append(arena)
  745. def isArenaChanged(self, arena):
  746. return (arena in self.changed)
  747. def isStable(self):
  748. return (len(self.changed) == 0)
  749. def setStepped(self):
  750. self.has_stepped = True
  751. def hasStepped(self):
  752. return self.has_stepped
  753. class SmallStepState(object):
  754. def __init__(self):
  755. self.current_events = [] # set of enabled events during small step
  756. self.next_events = [] # events to become 'current' in the next small step
  757. self.candidates = [] # document-ordered(!) list of transitions that can potentially be executed concurrently, or preempt each other, depending on concurrency semantics. If no concurrency is used and there are multiple candidates, the first one is chosen. Source states of candidates are *always* orthogonal to each other.
  758. self.has_stepped = True
  759. def reset(self):
  760. self.current_events = []
  761. self.next_events = []
  762. def next(self):
  763. self.current_events = self.next_events # raised events from previous small step
  764. self.next_events = []
  765. self.candidates = []
  766. self.has_stepped = False
  767. def addNextEvent(self, event):
  768. self.next_events.append(event)
  769. def getCurrentEvents(self):
  770. return self.current_events
  771. def addCandidate(self, t, p):
  772. self.candidates.append((t, p))
  773. def getCandidates(self):
  774. return self.candidates
  775. def hasCandidates(self):
  776. return len(self.candidates) > 0
  777. def setStepped(self):
  778. self.has_stepped = True
  779. def hasStepped(self):
  780. return self.has_stepped