statecharts_core.py 28 KB

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