statecharts_core.py 55 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409
  1. """
  2. The classes and functions needed to run (compiled) SCCD models.
  3. """
  4. from .tracers.tracer import Tracers
  5. import abc
  6. import re
  7. import threading
  8. import sys
  9. try:
  10. import thread
  11. except ImportError:
  12. import threading as thread
  13. import traceback
  14. import math
  15. from heapq import heappush, heappop, heapify
  16. try:
  17. from sccd.runtime.infinity import INFINITY
  18. except ImportError:
  19. from infinity import INFINITY
  20. try:
  21. from queue import Queue, Empty
  22. except ImportError:
  23. from Queue import Queue, Empty
  24. from sccd.runtime.event_queue import EventQueue
  25. from sccd.runtime.accurate_time import AccurateTime
  26. from time import time
  27. DEBUG = False
  28. ELSE_GUARD = "ELSE_GUARD"
  29. def print_debug(msg):
  30. if DEBUG:
  31. print(msg)
  32. class RuntimeException(Exception):
  33. """
  34. Base class for runtime exceptions.
  35. """
  36. def __init__(self, message):
  37. self.message = message
  38. def __str__(self):
  39. return repr(self.message)
  40. class AssociationException(RuntimeException):
  41. """
  42. Exception class thrown when an error occurs in a CRUD operation on associations.
  43. """
  44. pass
  45. class AssociationReferenceException(RuntimeException):
  46. """
  47. Exception class thrown when an error occurs when resolving an association reference.
  48. """
  49. pass
  50. class ParameterException(RuntimeException):
  51. """
  52. Exception class thrown when an error occurs when passing parameters.
  53. """
  54. pass
  55. class InputException(RuntimeException):
  56. """
  57. Exception class thrown when an error occurs during input processing.
  58. """
  59. pass
  60. class Association(object):
  61. """
  62. Class representing an SCCD association.
  63. """
  64. def __init__(self, to_class, min_card, max_card):
  65. """
  66. Constructor
  67. :param to_class: the name of the target class
  68. :param min_card: the minimal cardinality
  69. :param max_card: the maximal cardinality
  70. """
  71. self.to_class = to_class
  72. self.min_card = min_card
  73. self.max_card = max_card
  74. self.instances = {} # maps index (as string) to instance
  75. self.instances_to_ids = {}
  76. self.size = 0
  77. self.next_id = 0
  78. def allowedToAdd(self):
  79. return self.max_card == -1 or self.size < self.max_card
  80. def allowedToRemove(self):
  81. return self.min_card == -1 or self.size > self.min_card
  82. def addInstance(self, instance):
  83. if self.allowedToAdd():
  84. new_id = self.next_id
  85. self.next_id += 1
  86. self.instances[new_id] = instance
  87. self.instances_to_ids[instance] = new_id
  88. self.size += 1
  89. return new_id
  90. else:
  91. raise AssociationException("Not allowed to add the instance to the association.")
  92. def removeInstance(self, instance):
  93. if self.allowedToRemove():
  94. index = self.instances_to_ids[instance]
  95. del self.instances[index]
  96. del self.instances_to_ids[instance]
  97. self.size -= 1
  98. return index
  99. else:
  100. raise AssociationException("Not allowed to remove the instance from the association.")
  101. def getInstance(self, index):
  102. try:
  103. return self.instances[index]
  104. except IndexError:
  105. raise AssociationException("Invalid index for fetching instance(s) from association.")
  106. # TODO: Clean this mess up. Look at all object management operations and see how they can be improved.
  107. class ObjectManagerBase(object):
  108. __metaclass__ = abc.ABCMeta
  109. def __init__(self, controller):
  110. self.controller = controller
  111. self.events = EventQueue()
  112. self.instances = set() # a set of RuntimeClassBase instances
  113. self.instance_times = []
  114. self.eventless = set()
  115. self.regex_pattern = re.compile("^([a-zA-Z_]\w*)(?:\[(\d+)\])?$")
  116. self.handlers = {"narrow_cast": self.handleNarrowCastEvent,
  117. "broad_cast": self.handleBroadCastEvent,
  118. "create_instance": self.handleCreateEvent,
  119. "associate_instance": self.handleAssociateEvent,
  120. "disassociate_instance": self.handleDisassociateEvent,
  121. "start_instance": self.handleStartInstanceEvent,
  122. "delete_instance": self.handleDeleteInstanceEvent,
  123. "create_and_start_instance": self.handleCreateAndStartEvent}
  124. self.lock = threading.Condition()
  125. def addEvent(self, event, time_offset = 0):
  126. self.events.add(self.controller.simulated_time + time_offset, event)
  127. self.controller.tracers.tracesInternalOutput(event)
  128. # broadcast an event to all instances
  129. def broadcast(self, source, new_event, time_offset = 0):
  130. for i in self.instances:
  131. if i != source:
  132. i.addEvent(new_event, time_offset)
  133. def getEarliestEventTime(self):
  134. with self.lock:
  135. while self.instance_times and self.instance_times[0][0] < self.instance_times[0][1].earliest_event_time:
  136. heappop(self.instance_times)
  137. return min(INFINITY if not self.instance_times else self.instance_times[0][0], self.events.getEarliestTime())
  138. def stepAll(self):
  139. self.step()
  140. simulated_time = self.controller.simulated_time
  141. to_step = set()
  142. if len(self.instance_times) > (4 * len(self.instances)):
  143. new_instance_times = []
  144. for it in self.instances:
  145. if it.earliest_event_time != INFINITY:
  146. new_instance_times.append((it.earliest_event_time, it))
  147. self.instance_times = new_instance_times
  148. heapify(self.instance_times)
  149. while self.instance_times and self.instance_times[0][0] <= simulated_time:
  150. to_step.add(heappop(self.instance_times)[1])
  151. for i in to_step | self.eventless:
  152. if i.active and (i.earliest_event_time <= simulated_time or i.eventless_states):
  153. i.step()
  154. def step(self):
  155. while self.events.getEarliestTime() <= self.controller.simulated_time:
  156. if self.events:
  157. self.handleEvent(self.events.pop())
  158. def start(self):
  159. for i in self.instances:
  160. i.start()
  161. def handleEvent(self, e):
  162. self.handlers[e.getName()](e.getParameters())
  163. def processAssociationReference(self, input_string):
  164. if len(input_string) == 0:
  165. raise AssociationReferenceException("Empty association reference.")
  166. path_string = input_string.split("/")
  167. result = []
  168. for piece in path_string:
  169. match = self.regex_pattern.match(piece)
  170. if match:
  171. name = match.group(1)
  172. index = match.group(2)
  173. if index is None:
  174. index = -1
  175. result.append((name,int(index)))
  176. else:
  177. raise AssociationReferenceException("Invalid entry in association reference. Input string: " + input_string)
  178. return result
  179. def handleStartInstanceEvent(self, parameters):
  180. if len(parameters) != 2:
  181. raise ParameterException ("The start instance event needs 2 parameters.")
  182. else:
  183. source = parameters[0]
  184. traversal_list = self.processAssociationReference(parameters[1])
  185. for i in self.getInstances(source, traversal_list):
  186. i["instance"].start()
  187. source.addEvent(Event("instance_started", parameters = [parameters[1]]))
  188. def handleBroadCastEvent(self, parameters):
  189. if len(parameters) != 2:
  190. raise ParameterException ("The broadcast event needs 2 parameters (source of event and event name).")
  191. self.broadcast(parameters[0], parameters[1])
  192. def handleCreateEvent(self, parameters):
  193. if len(parameters) < 2:
  194. raise ParameterException ("The create event needs at least 2 parameters.")
  195. source = parameters[0]
  196. association_name = parameters[1]
  197. traversal_list = self.processAssociationReference(association_name)
  198. instances = self.getInstances(source, traversal_list)
  199. association = source.associations[association_name]
  200. if association.allowedToAdd():
  201. ''' allow subclasses to be instantiated '''
  202. class_name = association.to_class if len(parameters) == 2 else parameters[2]
  203. new_instance = self.createInstance(class_name, parameters[3:])
  204. if not new_instance:
  205. raise ParameterException("Creating instance: no such class: " + class_name)
  206. try:
  207. index = association.addInstance(new_instance)
  208. except AssociationException as exception:
  209. raise RuntimeException("Error adding instance to association '" + association_name + "': " + str(exception))
  210. p = new_instance.associations.get("parent")
  211. if p:
  212. p.addInstance(source)
  213. source.addEvent(Event("instance_created", None, [association_name+"["+str(index)+"]"]))
  214. return [source, association_name+"["+str(index)+"]"]
  215. else:
  216. source.addEvent(Event("instance_creation_error", None, [association_name]))
  217. return []
  218. def handleCreateAndStartEvent(self, parameters):
  219. params = self.handleCreateEvent(parameters)
  220. if params:
  221. self.handleStartInstanceEvent(params)
  222. def handleDeleteInstanceEvent(self, parameters):
  223. if len(parameters) < 2:
  224. raise ParameterException ("The delete event needs at least 2 parameters.")
  225. else:
  226. source = parameters[0]
  227. association_name = parameters[1]
  228. traversal_list = self.processAssociationReference(association_name)
  229. instances = self.getInstances(source, traversal_list)
  230. association = source.associations[traversal_list[0][0]]
  231. for i in instances:
  232. try:
  233. for assoc_name in i["instance"].associations:
  234. if assoc_name != 'parent':
  235. traversal_list = self.processAssociationReference(assoc_name)
  236. instances = self.getInstances(i["instance"], traversal_list)
  237. if len(instances) > 0:
  238. raise RuntimeException("Error removing instance from association %s, still %i children left connected with association %s" % (association_name, len(instances), assoc_name))
  239. del i["instance"].controller.input_ports[i["instance"].narrow_cast_port]
  240. association.removeInstance(i["instance"])
  241. self.instances.discard(i["instance"])
  242. self.eventless.discard(i["instance"])
  243. except AssociationException as exception:
  244. raise RuntimeException("Error removing instance from association '" + association_name + "': " + str(exception))
  245. i["instance"].user_defined_destructor()
  246. i["instance"].stop()
  247. source.addEvent(Event("instance_deleted", parameters = [parameters[1]]))
  248. def handleAssociateEvent(self, parameters):
  249. if len(parameters) != 3:
  250. raise ParameterException ("The associate_instance event needs 3 parameters.")
  251. else:
  252. source = parameters[0]
  253. to_copy_list = self.getInstances(source, self.processAssociationReference(parameters[1]))
  254. if len(to_copy_list) != 1:
  255. raise AssociationReferenceException ("Invalid source association reference.")
  256. wrapped_to_copy_instance = to_copy_list[0]["instance"]
  257. dest_list = self.processAssociationReference(parameters[2])
  258. if len(dest_list) == 0:
  259. raise AssociationReferenceException ("Invalid destination association reference.")
  260. last = dest_list.pop()
  261. if last[1] != -1:
  262. raise AssociationReferenceException ("Last association name in association reference should not be accompanied by an index.")
  263. added_links = []
  264. for i in self.getInstances(source, dest_list):
  265. association = i["instance"].associations[last[0]]
  266. if association.allowedToAdd():
  267. index = association.addInstance(wrapped_to_copy_instance)
  268. added_links.append(i["path"] + ("" if i["path"] == "" else "/") + last[0] + "[" + str(index) + "]")
  269. source.addEvent(Event("instance_associated", parameters = [added_links]))
  270. def handleDisassociateEvent(self, parameters):
  271. if len(parameters) < 2:
  272. raise ParameterException ("The disassociate_instance event needs at least 2 parameters.")
  273. else:
  274. source = parameters[0]
  275. association_name = parameters[1]
  276. if not isinstance(association_name, list):
  277. association_name = [association_name]
  278. deleted_links = []
  279. for a_n in association_name:
  280. traversal_list = self.processAssociationReference(a_n)
  281. instances = self.getInstances(source, traversal_list)
  282. for i in instances:
  283. try:
  284. index = i['ref'].associations[i['assoc_name']].removeInstance(i["instance"])
  285. deleted_links.append(a_n + "[" + str(index) + "]")
  286. except AssociationException as exception:
  287. raise RuntimeException("Error disassociating '" + a_n + "': " + str(exception))
  288. source.addEvent(Event("instance_disassociated", parameters = [deleted_links]))
  289. def handleNarrowCastEvent(self, parameters):
  290. if len(parameters) != 3:
  291. raise ParameterException ("The narrow_cast event needs 3 parameters.")
  292. source = parameters[0]
  293. if not isinstance(parameters[1], list):
  294. targets = [parameters[1]]
  295. else:
  296. targets = parameters[1]
  297. for target in targets:
  298. traversal_list = self.processAssociationReference(target)
  299. cast_event = parameters[2]
  300. for i in self.getInstances(source, traversal_list):
  301. to_send_event = Event(cast_event.name, i["instance"].narrow_cast_port, cast_event.parameters)
  302. i["instance"].controller.addInput(to_send_event, force_internal=True)
  303. def getInstances(self, source, traversal_list):
  304. currents = [{
  305. "instance": source,
  306. "ref": None,
  307. "assoc_name": None,
  308. "assoc_index": None,
  309. "path": ""
  310. }]
  311. # currents = [source]
  312. for (name, index) in traversal_list:
  313. nexts = []
  314. for current in currents:
  315. association = current["instance"].associations[name]
  316. if (index >= 0 ):
  317. try:
  318. nexts.append({
  319. "instance": association.instances[index],
  320. "ref": current["instance"],
  321. "assoc_name": name,
  322. "assoc_index": index,
  323. "path": current["path"] + ("" if current["path"] == "" else "/") + name + "[" + str(index) + "]"
  324. })
  325. except KeyError:
  326. # Entry was removed, so ignore this request
  327. pass
  328. elif (index == -1):
  329. for i in association.instances:
  330. nexts.append({
  331. "instance": association.instances[i],
  332. "ref": current["instance"],
  333. "assoc_name": name,
  334. "assoc_index": index,
  335. "path": current["path"] + ("" if current["path"] == "" else "/") + name + "[" + str(index) + "]"
  336. })
  337. #nexts.extend( association.instances.values() )
  338. else:
  339. raise AssociationReferenceException("Incorrect index in association reference.")
  340. currents = nexts
  341. return currents
  342. @abc.abstractmethod
  343. def instantiate(self, class_name, construct_params):
  344. pass
  345. def createInstance(self, to_class, construct_params = []):
  346. instance = self.instantiate(to_class, construct_params)
  347. self.instances.add(instance)
  348. return instance
  349. class Event(object):
  350. # TODO SAm: fixed the default of port to None as it was not consistent (sometimes none someting empty)
  351. def __init__(self, event_name, port = None, parameters = []):
  352. self.name = event_name
  353. self.parameters = parameters
  354. self.port = port
  355. #for comparisons in heaps
  356. def __lt__(self, other):
  357. s = str(self.name) + str(self.parameters) + str(self.port)
  358. return len(s)
  359. def getName(self):
  360. return self.name
  361. def getPort(self):
  362. return self.port
  363. def getParameters(self):
  364. return self.parameters
  365. def __repr__(self):
  366. representation = "(event name: " + str(self.name) + "; port: " + str(self.port)
  367. if self.parameters:
  368. representation += "; parameters: " + str(self.parameters)
  369. representation += ")"
  370. return representation
  371. class OutputListener(object):
  372. def __init__(self, port_names):
  373. if not isinstance(port_names, list):
  374. port_names = [port_names]
  375. self.port_names = [port_name.port_name if isinstance(port_name, OutputPortEntry) else port_name for port_name in port_names]
  376. self.queue = Queue()
  377. def add(self, event):
  378. if len(self.port_names) == 0 or event.getPort() in self.port_names:
  379. self.queue.put_nowait(event)
  380. """ Tries for timeout seconds to fetch an event, returns None if failed.
  381. 0 as timeout means no waiting (blocking), returns None if queue is empty.
  382. -1 as timeout means blocking until an event can be fetched. """
  383. def fetch(self, timeout = 0):
  384. if timeout < 0:
  385. timeout = INFINITY
  386. while timeout >= 0:
  387. try:
  388. # wait in chunks of 100ms because we
  389. # can't receive (keyboard)interrupts while waiting
  390. return self.queue.get(True, 0.1 if timeout > 0.1 else timeout)
  391. except Empty:
  392. timeout -= 0.1
  393. return None
  394. class InputPortEntry(object):
  395. def __init__(self, virtual_name, instance):
  396. self.virtual_name = virtual_name
  397. self.instance = instance
  398. class OutputPortEntry(object):
  399. def __init__(self, port_name, virtual_name, instance):
  400. self.port_name = port_name
  401. self.virtual_name = virtual_name
  402. self.instance = instance
  403. class ControllerBase(object):
  404. def __init__(self, object_manager):
  405. self.object_manager = object_manager
  406. self.private_port_counter = 0
  407. # keep track of input ports
  408. self.input_ports = {}
  409. self.input_queue = EventQueue()
  410. # keep track of output ports
  411. self.output_ports = {}
  412. self.output_listeners = []
  413. self.simulated_time = None
  414. self.behind = False
  415. # accurate timer
  416. self.accurate_time = AccurateTime()
  417. # # TODO SAM: tracer
  418. self.tracers = []
  419. # TODO SAM: Added here, DEVS does simconfig file, when extending SCDD best look at this
  420. def setVerbose(self, filename=None):
  421. # TODO: Change comments
  422. """
  423. Sets the use of a verbose tracer.
  424. Calling this function multiple times will register a tracer for each of them (thus output to multiple files is possible, though more inefficient than simply (manually) copying the file at the end).
  425. :param filename: string representing the filename to write the trace to, None means stdout
  426. """
  427. if not isinstance(filename, str) and filename is not None:
  428. # TODO: bst not raise a string,
  429. raise "Verbose filename should either be None or a string"
  430. self.setCustomTracer("tracerVerbose", "TracerVerbose", [filename])
  431. def setCustomTracer(self, tracerfile, tracerclass, args):
  432. # TODO: Change comments
  433. """
  434. Sets the use of a custom tracer, loaded at run time.
  435. Calling this function multiple times will register a tracer for each of them (thus output to multiple files is possible, though more inefficient than simply (manually) copying the file at the end).
  436. :param tracerfile: the file containing the tracerclass
  437. :param tracerclass: the class to instantiate
  438. :param args: arguments to be passed to the tracerclass's constructor
  439. """
  440. self.tracers.append((tracerfile, tracerclass, args))
  441. def startTracers(self):
  442. """
  443. Start all tracers
  444. """
  445. self.tracers.startTracers()
  446. def getSimulatedTime(self):
  447. return self.simulated_time
  448. def getWallClockTime(self):
  449. return self.accurate_time.get_wct()
  450. def addInputPort(self, virtual_name, instance = None):
  451. if instance == None:
  452. port_name = virtual_name
  453. else:
  454. port_name = "private_" + str(self.private_port_counter) + "_" + virtual_name
  455. self.private_port_counter += 1
  456. self.input_ports[port_name] = InputPortEntry(virtual_name, instance)
  457. return port_name
  458. def addOutputPort(self, virtual_name, instance = None):
  459. if instance == None:
  460. port_name = virtual_name
  461. else:
  462. port_name = "private_" + str(self.private_port_counter) + "_" + virtual_name
  463. self.private_port_counter += 1
  464. self.output_ports[port_name] = OutputPortEntry(port_name, virtual_name, instance)
  465. return port_name
  466. def broadcast(self, new_event, time_offset = 0):
  467. self.object_manager.broadcast(None, new_event, time_offset)
  468. def start(self):
  469. self.accurate_time.set_start_time()
  470. self.simulated_time = 0
  471. tracers = Tracers()
  472. for tracer in self.tracers:
  473. tracers.registerTracer(tracer, None, None)
  474. self.tracers = tracers
  475. self.object_manager.start()
  476. def stop(self):
  477. self.tracers.stopTracers()
  478. def addInput(self, input_event_list, time_offset = 0, force_internal=False):
  479. # force_internal is for narrow_cast events, otherwise these would arrive as external events (on the current wall-clock time)
  480. if not isinstance(input_event_list, list):
  481. input_event_list = [input_event_list]
  482. for e in input_event_list:
  483. if e.getName() == "":
  484. raise InputException("Input event can't have an empty name.")
  485. if e.getPort() not in self.input_ports:
  486. raise InputException("Input port mismatch, no such port: " + e.getPort() + ".")
  487. if force_internal:
  488. self.input_queue.add((0 if self.simulated_time is None else self.simulated_time) + time_offset, e)
  489. else:
  490. self.input_queue.add((0 if self.simulated_time is None else self.accurate_time.get_wct()) + time_offset, e)
  491. def getEarliestEventTime(self):
  492. return min(self.object_manager.getEarliestEventTime(), self.input_queue.getEarliestTime())
  493. def handleInput(self):
  494. while not self.input_queue.isEmpty():
  495. event_time = self.input_queue.getEarliestTime()
  496. # SAM: Changed the handleInput function
  497. # The function now waits to input the event when the simulated time is equal to the event first event time
  498. # This is because the addINput fnuction adds input at time zero to an instance with a delay to the real time
  499. # the event should trigger, but at time zero this instance may not exist
  500. if event_time == self.simulated_time:
  501. e = self.input_queue.pop()
  502. input_port = self.input_ports[e.getPort()]
  503. # e.port = input_port.virtual_name
  504. target_instance = input_port.instance
  505. if target_instance == None:
  506. self.broadcast(e, event_time - self.simulated_time)
  507. self.tracers.tracesInput(input_port, e)
  508. else:
  509. target_instance.addEvent(e, event_time - self.simulated_time)
  510. self.tracers.tracesInput(input_port, e)
  511. else:
  512. break
  513. '''
  514. def handleInput(self):
  515. while not self.input_queue.isEmpty():
  516. event_time = self.input_queue.getEarliestTime()
  517. e = self.input_queue.pop()
  518. input_port = self.input_ports[e.getPort()]
  519. # e.port = input_port.virtual_name
  520. target_instance = input_port.instance
  521. if target_instance == None:
  522. self.broadcast(e, event_time - self.simulated_time)
  523. self.tracers.tracesInput(input_port, e)
  524. else:
  525. target_instance.addEvent(e, event_time - self.simulated_time)
  526. self.tracers.tracesInput(input_port, e)
  527. '''
  528. def outputEvent(self, event):
  529. #TODO: This is the output event, needs to be traced
  530. self.tracers.tracesOutput(event)
  531. for listener in self.output_listeners:
  532. listener.add(event)
  533. def addOutputListener(self, ports):
  534. listener = OutputListener(ports)
  535. self.output_listeners.append(listener)
  536. return listener
  537. def addMyOwnOutputListener(self, listener):
  538. self.output_listeners.append(listener)
  539. def getObjectManager(self):
  540. return self.object_manager
  541. class GameLoopControllerBase(ControllerBase):
  542. def __init__(self, object_manager):
  543. ControllerBase.__init__(self, object_manager)
  544. def update(self):
  545. self.handleInput()
  546. earliest_event_time = self.getEarliestEventTime()
  547. if earliest_event_time > time():
  548. self.simulated_time = earliest_event_time
  549. self.object_manager.stepAll()
  550. class EventLoop:
  551. # parameters:
  552. # schedule - a callback scheduling another callback in the event loop
  553. # this callback should take 2 parameters: (callback, timeout) and return an ID
  554. # clear - a callback that clears a scheduled callback
  555. # this callback should take an ID that was returned by 'schedule'
  556. def __init__(self, schedule, clear):
  557. self.schedule_callback = schedule
  558. self.clear_callback = clear
  559. self.scheduled_id = None
  560. self.last_print = 0.0
  561. # schedule relative to last_time
  562. #
  563. # argument 'wait_time' is the amount of virtual (simulated) time to wait
  564. #
  565. # NOTE: if the next wakeup (in simulated time) is in the past, the timeout is '0',
  566. # but because scheduling '0' timeouts hurts performance, we don't schedule anything
  567. # and return False instead
  568. def schedule(self, f, wait_time, behind = False):
  569. if self.scheduled_id is not None:
  570. # if the following error occurs, it is probably due to a flaw in the logic of EventLoopControllerBase
  571. raise RuntimeException("EventLoop class intended to maintain at most 1 scheduled callback.")
  572. if wait_time != INFINITY:
  573. self.scheduled_id = self.schedule_callback(f, wait_time, behind)
  574. def clear(self):
  575. if self.scheduled_id is not None:
  576. self.clear_callback(self.scheduled_id)
  577. self.scheduled_id = None
  578. def bind_controller(self, controller):
  579. pass
  580. class EventLoopControllerBase(ControllerBase):
  581. def __init__(self, object_manager, event_loop, finished_callback = None, behind_schedule_callback = None):
  582. ControllerBase.__init__(self, object_manager)
  583. if not isinstance(event_loop, EventLoop):
  584. raise RuntimeException("Event loop argument must be an instance of the EventLoop class!")
  585. self.event_loop = event_loop
  586. self.finished_callback = finished_callback
  587. self.behind_schedule_callback = behind_schedule_callback
  588. self.last_print_time = 0
  589. self.running = False
  590. self.input_condition = threading.Condition()
  591. self.behind = False
  592. self.event_loop.bind_controller(self)
  593. self.event_queue = []
  594. self.main_thread = thread.get_ident()
  595. def addInput(self, input_event, time_offset = 0, force_internal=False):
  596. # import pdb; pdb.set_trace()
  597. if self.main_thread == thread.get_ident():
  598. # Running on the main thread, so just execute what we want
  599. self.simulated_time = self.accurate_time.get_wct()
  600. ControllerBase.addInput(self, input_event, time_offset, force_internal)
  601. else:
  602. # Not on the main thread, so we have to queue these events for the main thread instead
  603. self.event_queue.append((input_event, time_offset, force_internal))
  604. self.event_loop.clear()
  605. self.event_loop.schedule(self.run, 0, True)
  606. def start(self):
  607. ControllerBase.start(self)
  608. self.run()
  609. def stop(self):
  610. self.event_loop.clear()
  611. ControllerBase.stop(self)
  612. def run(self, tkinter_event=None):
  613. start_time = self.accurate_time.get_wct()
  614. try:
  615. self.running = True
  616. # Process external events first
  617. while 1:
  618. while self.event_queue:
  619. self.addInput(*self.event_queue.pop(0))
  620. if self.accurate_time.get_wct() >= self.getEarliestEventTime():
  621. self.simulated_time = self.getEarliestEventTime()
  622. else:
  623. return
  624. # clear existing timeout
  625. self.event_loop.clear()
  626. self.handleInput()
  627. self.object_manager.stepAll()
  628. self.tracers.traces(self.getSimulatedTime())
  629. # schedule next timeout
  630. earliest_event_time = self.getEarliestEventTime()
  631. if earliest_event_time == INFINITY:
  632. if self.finished_callback: self.finished_callback() # TODO: This is not necessarily correct (keep_running necessary?)
  633. return
  634. now = self.accurate_time.get_wct()
  635. if earliest_event_time - now > 0:
  636. if self.behind:
  637. self.behind = False
  638. self.event_loop.schedule(self.run, earliest_event_time - now, now - start_time > 10)
  639. else:
  640. if now - earliest_event_time > 10 and now - self.last_print_time >= 1000:
  641. if self.behind_schedule_callback:
  642. self.behind_schedule_callback(self, now - earliest_event_time)
  643. print_debug('\rrunning %ims behind schedule' % (now - earliest_event_time))
  644. self.last_print_time = now
  645. self.behind = True
  646. if not self.behind:
  647. return
  648. finally:
  649. self.running = False
  650. if self.event_queue:
  651. self.event_loop.clear()
  652. self.event_loop.schedule(self.run, 0, True)
  653. class ThreadsControllerBase(ControllerBase):
  654. def __init__(self, object_manager, keep_running, behind_schedule_callback = None):
  655. ControllerBase.__init__(self, object_manager)
  656. self.keep_running = keep_running
  657. self.behind_schedule_callback = behind_schedule_callback
  658. self.input_condition = threading.Condition()
  659. self.stop_thread = False
  660. self.last_print_time = 0
  661. def addInput(self, input_event, time_offset = 0, force_internal=False):
  662. with self.input_condition:
  663. ControllerBase.addInput(self, input_event, time_offset, force_internal)
  664. self.input_condition.notifyAll()
  665. def start(self):
  666. self.run()
  667. def stop(self):
  668. with self.input_condition:
  669. self.stop_thread = True
  670. self.input_condition.notifyAll()
  671. def run(self):
  672. ControllerBase.start(self)
  673. while 1:
  674. # simulate
  675. with self.input_condition:
  676. self.handleInput()
  677. self.object_manager.stepAll()
  678. self.tracers.traces(self.getSimulatedTime())
  679. # wait until next timeout
  680. earliest_event_time = self.getEarliestEventTime()
  681. if earliest_event_time == INFINITY and not self.keep_running:
  682. return
  683. now = self.accurate_time.get_wct()
  684. if earliest_event_time - now > 0:
  685. if self.behind:
  686. self.behind = False
  687. with self.input_condition:
  688. if earliest_event_time == self.getEarliestEventTime() and not earliest_event_time == INFINITY:
  689. self.input_condition.wait((earliest_event_time - now) / 1000.0)
  690. else:
  691. # Something happened that made the queue fill up already, but we were not yet waiting for the Condition...
  692. pass
  693. else:
  694. if now - earliest_event_time > 10 and now - self.last_print_time >= 1000:
  695. if self.behind_schedule_callback:
  696. self.behind_schedule_callback(self, now - earliest_event_time)
  697. print_debug('\rrunning %ims behind schedule' % (now - earliest_event_time))
  698. self.last_print_time = now
  699. self.behind = True
  700. with self.input_condition:
  701. earliest_event_time = self.getEarliestEventTime()
  702. if earliest_event_time == INFINITY:
  703. if self.keep_running:
  704. self.input_condition.wait()
  705. earliest_event_time = self.getEarliestEventTime()
  706. else:
  707. self.stop_thread = True
  708. if self.stop_thread:
  709. break
  710. self.simulated_time = earliest_event_time
  711. class StatechartSemantics:
  712. # Big Step Maximality
  713. TakeOne = 0
  714. TakeMany = 1
  715. # Concurrency - not implemented yet
  716. Single = 0
  717. Many = 1
  718. # Preemption - not implemented yet
  719. NonPreemptive = 0
  720. Preemptive = 1
  721. # Internal Event Lifeline
  722. Queue = 0
  723. NextSmallStep = 1
  724. NextComboStep = 2
  725. # Input Event Lifeline
  726. Whole = 0
  727. FirstSmallStep = 1
  728. FirstComboStep = 2
  729. # Priority
  730. SourceParent = 0
  731. SourceChild = 1
  732. # TODO: add Memory Protocol options
  733. def __init__(self):
  734. # default semantics:
  735. self.big_step_maximality = self.TakeMany
  736. self.internal_event_lifeline = self.Queue
  737. self.input_event_lifeline = self.FirstComboStep
  738. self.priority = self.SourceParent
  739. self.concurrency = self.Single
  740. class State:
  741. def __init__(self, state_id, name, obj):
  742. self.state_id = state_id
  743. self.name = name
  744. self.obj = obj
  745. self.ancestors = []
  746. self.descendants = []
  747. self.descendant_bitmap = 0
  748. self.children = []
  749. self.parent = None
  750. self.enter = None
  751. self.exit = None
  752. self.default_state = None
  753. self.transitions = []
  754. self.history = []
  755. self.has_eventless_transitions = False
  756. def getEffectiveTargetStates(self):
  757. targets = [self]
  758. if self.default_state:
  759. targets.extend(self.default_state.getEffectiveTargetStates())
  760. return targets
  761. def fixTree(self):
  762. for c in self.children:
  763. if isinstance(c, HistoryState):
  764. self.history.append(c)
  765. c.parent = self
  766. c.ancestors.append(self)
  767. c.ancestors.extend(self.ancestors)
  768. c.fixTree()
  769. self.descendants.extend(self.children)
  770. for c in self.children:
  771. self.descendants.extend(c.descendants)
  772. for d in self.descendants:
  773. self.descendant_bitmap |= 2**d.state_id
  774. def addChild(self, child):
  775. self.children.append(child)
  776. def addTransition(self, transition):
  777. self.transitions.append(transition)
  778. def setEnter(self, enter):
  779. self.enter = enter
  780. def setExit(self, exit):
  781. self.exit = exit
  782. def __repr__(self):
  783. return "State(%s)" % (self.state_id)
  784. class HistoryState(State):
  785. def __init__(self, state_id, name, obj):
  786. State.__init__(self, state_id, name, obj)
  787. class ShallowHistoryState(HistoryState):
  788. def __init__(self, state_id, name, obj):
  789. HistoryState.__init__(self, state_id, name, obj)
  790. def getEffectiveTargetStates(self):
  791. if self.state_id in self.obj.history_values:
  792. targets = []
  793. for hv in self.obj.history_values[self.state_id]:
  794. targets.extend(hv.getEffectiveTargetStates())
  795. return targets
  796. else:
  797. # TODO: is it correct that in this case, the parent itself is also entered?
  798. return self.parent.getEffectiveTargetStates()
  799. class DeepHistoryState(HistoryState):
  800. def __init__(self, state_id, name, obj):
  801. HistoryState.__init__(self, state_id, name, obj)
  802. def getEffectiveTargetStates(self):
  803. if self.state_id in self.obj.history_values:
  804. return self.obj.history_values[self.state_id]
  805. else:
  806. # TODO: is it correct that in this case, the parent itself is also entered?
  807. return self.parent.getEffectiveTargetStates()
  808. class ParallelState(State):
  809. def __init__(self, state_id, name, obj):
  810. State.__init__(self, state_id, name, obj)
  811. def getEffectiveTargetStates(self):
  812. targets = [self]
  813. for c in self.children:
  814. if not isinstance(c, HistoryState):
  815. targets.extend(c.getEffectiveTargetStates())
  816. return targets
  817. class Transition:
  818. def __init__(self, obj, source, targets):
  819. self.guard = None
  820. self.action = None
  821. self.trigger = None
  822. self.source = source
  823. self.targets = targets
  824. self.obj = obj
  825. self.enabled_event = None # the event that enabled this transition
  826. self.optimize()
  827. def isEnabled(self, events, enabled_transitions):
  828. if self.trigger is None:
  829. self.enabled_event = None
  830. return (self.guard is None) or (self.guard == ELSE_GUARD and not enabled_transitions) or self.guard([])
  831. else:
  832. for event in events:
  833. if (self.trigger.name == event.name and (not self.trigger.port or self.trigger.port == event.port)) and ((self.guard is None) or (self.guard == ELSE_GUARD and not enabled_transitions) or self.guard(event.parameters)):
  834. self.enabled_event = event
  835. return True
  836. # @profile
  837. def fire(self):
  838. # exit states...
  839. exit_set = self.__exitSet()
  840. for s in exit_set:
  841. # remember which state(s) we were in if a history state is present
  842. for h in s.history:
  843. f = lambda s0: s0.ancestors and s0.parent == s
  844. if isinstance(h, DeepHistoryState):
  845. f = lambda s0: not s0.descendants and s0 in s.descendants
  846. self.obj.history_values[h.state_id] = list(filter(f, self.obj.configuration))
  847. for s in exit_set:
  848. #########################################
  849. # TODO, here trace for exit state
  850. print_debug('EXIT: %s::%s' % (self.obj.__class__.__name__, s.name))
  851. self.obj.controller.tracers.tracesExitState(self.obj.__class__.__name__, s.name)
  852. #########################################
  853. self.obj.eventless_states -= s.has_eventless_transitions
  854. # execute exit action(s)
  855. if s.exit:
  856. s.exit()
  857. self.obj.configuration_bitmap &= ~2**s.state_id
  858. # combo state changed area
  859. self.obj.combo_step.changed_bitmap |= 2**self.lca.state_id
  860. self.obj.combo_step.changed_bitmap |= self.lca.descendant_bitmap
  861. #########################################
  862. # TODO, here trace for fired transition
  863. self.obj.controller.tracers.tracesTransition(self.obj.__class__.__name__, str(self))
  864. #########################################
  865. # execute transition action(s)
  866. if self.action:
  867. self.action(self.enabled_event.parameters if self.enabled_event else [])
  868. # enter states...
  869. targets = self.__getEffectiveTargetStates()
  870. enter_set = self.__enterSet(targets)
  871. for s in enter_set:
  872. print_debug('ENTER: %s::%s' % (self.obj.__class__.__name__, s.name))
  873. self.obj.controller.tracers.tracesEnterState(self.obj.__class__.__name__, s.name)
  874. self.obj.eventless_states += s.has_eventless_transitions
  875. self.obj.configuration_bitmap |= 2**s.state_id
  876. # execute enter action(s)
  877. if s.enter:
  878. s.enter()
  879. if self.obj.eventless_states:
  880. self.obj.controller.object_manager.eventless.add(self.obj)
  881. else:
  882. self.obj.controller.object_manager.eventless.discard(self.obj)
  883. try:
  884. self.obj.configuration = self.obj.config_mem[self.obj.configuration_bitmap]
  885. except:
  886. self.obj.configuration = self.obj.config_mem[self.obj.configuration_bitmap] = sorted([s for s in list(self.obj.states.values()) if 2**s.state_id & self.obj.configuration_bitmap], key=lambda s: s.state_id)
  887. self.enabled_event = None
  888. def __getEffectiveTargetStates(self):
  889. targets = []
  890. for target in self.targets:
  891. for e_t in target.getEffectiveTargetStates():
  892. if not e_t in targets:
  893. targets.append(e_t)
  894. return targets
  895. def __exitSet(self):
  896. return [s for s in reversed(self.lca.descendants) if (s in self.obj.configuration)]
  897. def __enterSet(self, targets):
  898. target = targets[0]
  899. for a in reversed(target.ancestors):
  900. if a in self.source.ancestors:
  901. continue
  902. else:
  903. yield a
  904. for target in targets:
  905. yield target
  906. def setGuard(self, guard):
  907. self.guard = guard
  908. def setAction(self, action):
  909. self.action = action
  910. def setTrigger(self, trigger):
  911. self.trigger = trigger
  912. if self.trigger is None:
  913. self.source.has_eventless_transitions = True
  914. def optimize(self):
  915. # the least-common ancestor can be computed statically
  916. if self.source in self.targets[0].ancestors:
  917. self.lca = self.source
  918. else:
  919. self.lca = self.source.parent
  920. target = self.targets[0]
  921. if self.source.parent != target.parent: # external
  922. for a in self.source.ancestors:
  923. if a in target.ancestors:
  924. self.lca = a
  925. break
  926. def __repr__(self):
  927. return "Transition(%s -> %s)" % (self.source.name, self.targets[0].name)
  928. class RuntimeClassBase(object):
  929. __metaclass__ = abc.ABCMeta
  930. def __init__(self, controller):
  931. self.events = EventQueue()
  932. self.active = False
  933. self.controller = controller
  934. self.__set_stable(True)
  935. self.inports = {}
  936. self.outports = {}
  937. self.timers = {}
  938. self.states = {}
  939. self.eventless_states = 0
  940. self.configuration_bitmap = 0
  941. self.transition_mem = {}
  942. self.config_mem = {}
  943. self.narrow_cast_port = self.controller.addInputPort("<narrow_cast>", self)
  944. self.semantics = StatechartSemantics()
  945. # TODO: SAM Tracer INIT
  946. # 2nd is time
  947. # TODO: Init cannot be done on the default class because it is in the init of the controller where the tracer is not yet created --> needs fix maybe in SCCD (don't want to touch that)
  948. #controller.tracers.tracesInit(self, self.getSimulatedTime())
  949. #to break ties in the heap,
  950. #compare by number of events in the list
  951. def __lt__(self, other):
  952. return len(self.events.event_list) < len(other.events.event_list)
  953. def getChildren(self, link_name):
  954. traversal_list = self.controller.object_manager.processAssociationReference(link_name)
  955. return [i["instance"] for i in self.controller.object_manager.getInstances(self, traversal_list)]
  956. def getSingleChild(self, link_name):
  957. return self.getChildren(link_name)[0] # assume this will return a single child...
  958. def getOutPortName(self, port_name):
  959. return self.outports[port_name] if port_name in self.outports else port_name
  960. def getInPortName(self, port_name):
  961. return self.inports[port_name] if port_name in self.inports else port_name
  962. def start(self):
  963. self.configuration = []
  964. self.active = True
  965. self.current_state = {}
  966. self.history_values = {}
  967. self.timers = {}
  968. self.timers_to_add = {}
  969. self.big_step = BigStepState()
  970. self.combo_step = ComboStepState()
  971. self.small_step = SmallStepState()
  972. self.__set_stable(False)
  973. self.initializeStatechart()
  974. self.processBigStepOutput()
  975. def stop(self):
  976. self.active = False
  977. self.__set_stable(True)
  978. def sccd_yield(self):
  979. return max(0, (self.controller.accurate_time.get_wct() - self.controller.simulated_time) / 1000.0)
  980. def getSimulatedTime(self):
  981. return self.controller.getSimulatedTime()
  982. def getWallClockTime(self):
  983. return self.controller.getWallClockTime()
  984. def updateConfiguration(self, states):
  985. self.configuration.extend(states)
  986. self.configuration_bitmap = sum([2**s.state_id for s in states])
  987. def addTimer(self, index, timeout):
  988. self.timers_to_add[index] = (self.controller.simulated_time + int(timeout * 1000), Event("_%iafter" % index))
  989. def removeTimer(self, index):
  990. if index in self.timers_to_add:
  991. del self.timers_to_add[index]
  992. if index in self.timers:
  993. self.events.remove(self.timers[index])
  994. del self.timers[index]
  995. self.earliest_event_time = self.events.getEarliestTime()
  996. def addEvent(self, event_list, time_offset = 0):
  997. event_time = self.controller.simulated_time + time_offset
  998. if not (event_time, self) in self.controller.object_manager.instance_times:
  999. heappush(self.controller.object_manager.instance_times, (event_time, self))
  1000. if event_time < self.earliest_event_time:
  1001. self.earliest_event_time = event_time
  1002. if not isinstance(event_list, list):
  1003. event_list = [event_list]
  1004. for e in event_list:
  1005. self.controller.tracers.tracesInternalInput(e)
  1006. self.events.add(event_time, e)
  1007. def processBigStepOutput(self):
  1008. for e in self.big_step.output_events_port:
  1009. self.controller.outputEvent(e)
  1010. for e in self.big_step.output_events_om:
  1011. self.controller.object_manager.addEvent(e)
  1012. def __set_stable(self, is_stable):
  1013. self.is_stable = is_stable
  1014. # self.earliest_event_time keeps track of the earliest time this instance will execute a transition
  1015. if not is_stable:
  1016. self.earliest_event_time = self.controller.simulated_time
  1017. elif not self.active:
  1018. self.earliest_event_time = INFINITY
  1019. else:
  1020. self.earliest_event_time = self.events.getEarliestTime()
  1021. if self.earliest_event_time != INFINITY:
  1022. if not (self.earliest_event_time, self) in self.controller.object_manager.instance_times:
  1023. heappush(self.controller.object_manager.instance_times, (self.earliest_event_time, self))
  1024. def step(self):
  1025. is_stable = False
  1026. while not is_stable:
  1027. due = []
  1028. if self.events.getEarliestTime() <= self.controller.simulated_time:
  1029. due = [self.events.pop()]
  1030. is_stable = not self.bigStep(due)
  1031. self.processBigStepOutput()
  1032. for index, entry in list(self.timers_to_add.items()):
  1033. self.timers[index] = self.events.add(*entry)
  1034. self.timers_to_add = {}
  1035. self.__set_stable(True)
  1036. def inState(self, state_strings):
  1037. state_ids = [self.states[state_string].state_id for state_string in state_strings]
  1038. for state_id in state_ids:
  1039. for s in self.configuration:
  1040. if s.state_id == state_id:
  1041. break
  1042. else:
  1043. return False
  1044. return True
  1045. def bigStep(self, input_events):
  1046. self.big_step.next(input_events)
  1047. self.small_step.reset()
  1048. self.combo_step.reset()
  1049. while self.comboStep():
  1050. self.big_step.has_stepped = True
  1051. if self.semantics.big_step_maximality == StatechartSemantics.TakeOne:
  1052. break # Take One -> only one combo step allowed
  1053. return self.big_step.has_stepped
  1054. def comboStep(self):
  1055. self.combo_step.next()
  1056. while self.smallStep():
  1057. self.combo_step.has_stepped = True
  1058. return self.combo_step.has_stepped
  1059. # generate transition candidates for current small step
  1060. # @profile
  1061. def generateCandidates(self):
  1062. changed_bitmap = self.combo_step.changed_bitmap
  1063. key = (self.configuration_bitmap, changed_bitmap)
  1064. try:
  1065. transitions = self.transition_mem[key]
  1066. except:
  1067. self.transition_mem[key] = transitions = [t for s in self.configuration if not (2**s.state_id & changed_bitmap) for t in s.transitions]
  1068. enabledEvents = self.getEnabledEvents()
  1069. enabledTransitions = []
  1070. for t in transitions:
  1071. if t.isEnabled(enabledEvents, enabledTransitions):
  1072. enabledTransitions.append(t)
  1073. return enabledTransitions
  1074. # @profile
  1075. def smallStep(self):
  1076. def __younger_than(x, y):
  1077. if x.source in y.source.ancestors:
  1078. return 1
  1079. elif y.source in x.source.ancestors:
  1080. return -1
  1081. else:
  1082. return 0
  1083. if self.small_step.has_stepped:
  1084. self.small_step.next()
  1085. candidates = self.generateCandidates()
  1086. if candidates:
  1087. to_skip = set()
  1088. conflicting = []
  1089. for c1 in candidates:
  1090. if c1 not in to_skip:
  1091. conflict = [c1]
  1092. for c2 in candidates[candidates.index(c1):]:
  1093. if c2.source in c1.source.ancestors or c1.source in c2.source.ancestors:
  1094. conflict.append(c2)
  1095. to_skip.add(c2)
  1096. if sys.version_info[0] < 3:
  1097. conflicting.append(sorted(conflict, cmp=__younger_than))
  1098. else:
  1099. import functools
  1100. conflicting.append(sorted(conflict, key=functools.cmp_to_key(__younger_than)))
  1101. if self.semantics.concurrency == StatechartSemantics.Single:
  1102. candidate = conflicting[0]
  1103. if self.semantics.priority == StatechartSemantics.SourceParent:
  1104. candidate[-1].fire()
  1105. else:
  1106. candidate[0].fire()
  1107. elif self.semantics.concurrency == StatechartSemantics.Many:
  1108. pass # TODO: implement
  1109. self.small_step.has_stepped = True
  1110. return self.small_step.has_stepped
  1111. # @profile
  1112. def getEnabledEvents(self):
  1113. result = self.small_step.current_events + self.combo_step.current_events
  1114. if self.semantics.input_event_lifeline == StatechartSemantics.Whole or (
  1115. not self.big_step.has_stepped and
  1116. (self.semantics.input_event_lifeline == StatechartSemantics.FirstComboStep or (
  1117. not self.combo_step.has_stepped and
  1118. self.semantics.input_event_lifeline == StatechartSemantics.FirstSmallStep))):
  1119. result += self.big_step.input_events
  1120. return result
  1121. def raiseInternalEvent(self, event):
  1122. if self.semantics.internal_event_lifeline == StatechartSemantics.NextSmallStep:
  1123. self.small_step.addNextEvent(event)
  1124. elif self.semantics.internal_event_lifeline == StatechartSemantics.NextComboStep:
  1125. self.combo_step.addNextEvent(event)
  1126. elif self.semantics.internal_event_lifeline == StatechartSemantics.Queue:
  1127. self.addEvent(event)
  1128. def initializeStatechart(self):
  1129. self.updateConfiguration(self.default_targets)
  1130. for state in self.default_targets:
  1131. self.eventless_states += state.has_eventless_transitions
  1132. if state.enter:
  1133. state.enter()
  1134. if self.eventless_states:
  1135. self.controller.object_manager.eventless.add(self)
  1136. class BigStepState(object):
  1137. def __init__(self):
  1138. self.input_events = [] # input events received from environment before beginning of big step (e.g. from object manager, from input port)
  1139. self.output_events_port = [] # output events to be sent to output port after big step ends.
  1140. self.output_events_om = [] # output events to be sent to object manager after big step ends.
  1141. self.has_stepped = True
  1142. def next(self, input_events):
  1143. self.input_events = input_events
  1144. self.output_events_port = []
  1145. self.output_events_om = []
  1146. self.has_stepped = False
  1147. def outputEvent(self, event):
  1148. self.output_events_port.append(event)
  1149. def outputEventOM(self, event):
  1150. self.output_events_om.append(event)
  1151. class ComboStepState(object):
  1152. def __init__(self):
  1153. self.current_events = [] # set of enabled events during combo step
  1154. self.next_events = [] # internal events that were raised during combo step
  1155. self.changed_bitmap = 0 # set of all or-states that were the arena of a triggered transition during big step.
  1156. self.has_stepped = True
  1157. def reset(self):
  1158. self.current_events = []
  1159. self.next_events = []
  1160. def next(self):
  1161. self.current_events = self.next_events
  1162. self.next_events = []
  1163. self.changed_bitmap = 0
  1164. self.has_stepped = False
  1165. def addNextEvent(self, event):
  1166. self.next_events.append(event)
  1167. class SmallStepState(object):
  1168. def __init__(self):
  1169. self.current_events = [] # set of enabled events during small step
  1170. self.next_events = [] # events to become 'current' in the next small step
  1171. 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.
  1172. self.has_stepped = True
  1173. def reset(self):
  1174. self.current_events = []
  1175. self.next_events = []
  1176. def next(self):
  1177. self.current_events = self.next_events # raised events from previous small step
  1178. self.next_events = []
  1179. self.candidates = []
  1180. self.has_stepped = False
  1181. def addNextEvent(self, event):
  1182. self.next_events.append(event)
  1183. def addCandidate(self, t, p):
  1184. self.candidates.append((t, p))
  1185. def hasCandidates(self):
  1186. return len(self.candidates) > 0