statecharts_core.py 38 KB

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