DEVS_statecharts_core.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902
  1. import sys
  2. import re
  3. from sccd.runtime.event_queue import EventQueue
  4. from pypdevs.infinity import INFINITY
  5. from pypdevs.DEVS import *
  6. from sccd.runtime.statecharts_core import StatechartSemantics, State, HistoryState, ShallowHistoryState, DeepHistoryState, ParallelState, Transition, BigStepState, ComboStepState, SmallStepState, RuntimeException, AssociationException, Association
  7. from heapq import heappush, heappop, heapify
  8. import threading
  9. ELSE_GUARD = "ELSE_GUARD"
  10. def get_private_port(text):
  11. match = re.search(r'private_\d+_(\w+)', text)
  12. if match:
  13. result = match.group(1)
  14. return result
  15. class Transition:
  16. def __init__(self, obj, source, targets):
  17. self.guard = None
  18. self.action = None
  19. self.trigger = None
  20. self.source = source
  21. self.targets = targets
  22. self.obj = obj
  23. self.enabled_event = None # the event that enabled this transition
  24. self.optimize()
  25. def isEnabled(self, events, enabled_transitions):
  26. if self.trigger is None:
  27. self.enabled_event = None
  28. return (self.guard is None) or (self.guard == ELSE_GUARD and not enabled_transitions) or self.guard([])
  29. else:
  30. for event in events:
  31. if (self.trigger.name == event.name and (
  32. not self.trigger.port or self.trigger.port == event.port)) and (
  33. (self.guard is None) or (self.guard == ELSE_GUARD and not enabled_transitions) or self.guard(
  34. event.parameters)):
  35. self.enabled_event = event
  36. return True
  37. # @profile
  38. def fire(self):
  39. # exit states...
  40. exit_set = self.__exitSet()
  41. for s in exit_set:
  42. # remember which state(s) we were in if a history state is present
  43. for h in s.history:
  44. f = lambda s0: s0.ancestors and s0.parent == s
  45. if isinstance(h, DeepHistoryState):
  46. f = lambda s0: not s0.descendants and s0 in s.descendants
  47. self.obj.history_values[h.state_id] = list(filter(f, self.obj.configuration))
  48. for s in exit_set:
  49. self.obj.eventless_states -= s.has_eventless_transitions
  50. # execute exit action(s)
  51. if s.exit:
  52. s.exit()
  53. self.obj.configuration_bitmap &= ~2 ** s.state_id
  54. # combo state changed area
  55. self.obj.combo_step.changed_bitmap |= 2 ** self.lca.state_id
  56. self.obj.combo_step.changed_bitmap |= self.lca.descendant_bitmap
  57. # execute transition action(s)
  58. if self.action:
  59. self.action(self.enabled_event.parameters if self.enabled_event else [])
  60. # enter states...
  61. targets = self.__getEffectiveTargetStates()
  62. enter_set = self.__enterSet(targets)
  63. for s in enter_set:
  64. self.obj.eventless_states += s.has_eventless_transitions
  65. self.obj.configuration_bitmap |= 2 ** s.state_id
  66. # execute enter action(s)
  67. if s.enter:
  68. s.enter()
  69. if self.obj.eventless_states:
  70. self.obj.controller.state.eventless.add(self.obj)
  71. else:
  72. self.obj.controller.state.eventless.discard(self.obj)
  73. try:
  74. self.obj.configuration = self.obj.config_mem[self.obj.configuration_bitmap]
  75. except:
  76. self.obj.configuration = self.obj.config_mem[self.obj.configuration_bitmap] = sorted(
  77. [s for s in list(self.obj.states.values()) if 2 ** s.state_id & self.obj.configuration_bitmap],
  78. key=lambda s: s.state_id)
  79. self.enabled_event = None
  80. def __getEffectiveTargetStates(self):
  81. targets = []
  82. for target in self.targets:
  83. for e_t in target.getEffectiveTargetStates():
  84. if not e_t in targets:
  85. targets.append(e_t)
  86. return targets
  87. def __exitSet(self):
  88. return [s for s in reversed(self.lca.descendants) if (s in self.obj.configuration)]
  89. def __enterSet(self, targets):
  90. target = targets[0]
  91. for a in reversed(target.ancestors):
  92. if a in self.source.ancestors:
  93. continue
  94. else:
  95. yield a
  96. for target in targets:
  97. yield target
  98. def setGuard(self, guard):
  99. self.guard = guard
  100. def setAction(self, action):
  101. self.action = action
  102. def setTrigger(self, trigger):
  103. self.trigger = trigger
  104. if self.trigger is None:
  105. self.source.has_eventless_transitions = True
  106. def optimize(self):
  107. # the least-common ancestor can be computed statically
  108. if self.source in self.targets[0].ancestors:
  109. self.lca = self.source
  110. else:
  111. self.lca = self.source.parent
  112. target = self.targets[0]
  113. if self.source.parent != target.parent: # external
  114. for a in self.source.ancestors:
  115. if a in target.ancestors:
  116. self.lca = a
  117. break
  118. def __repr__(self):
  119. return "Transition(%s, %s)" % (self.source, self.targets[0])
  120. class Event(object):
  121. def __init__(self, event_name, port="", parameters=[], instance=None):
  122. self.name = event_name
  123. self.parameters = parameters
  124. self.port = port
  125. self.instance = instance
  126. # for comparisons in heaps
  127. def __lt__(self, other):
  128. s = str(self.name) + str(self.parameters) + str(self.port)
  129. return len(s)
  130. def getName(self):
  131. return self.name
  132. def getPort(self):
  133. return self.port
  134. def getParameters(self):
  135. return self.parameters
  136. def __repr__(self):
  137. representation = "(event name: " + str(self.name) + "; port: " + str(self.port)
  138. if self.parameters:
  139. representation += "; parameters: " + str(self.parameters)
  140. representation += ")"
  141. return representation
  142. class RuntimeClassBase(object):
  143. def __init__(self, controller):
  144. self.events = EventQueue()
  145. self.active = False
  146. # Instead of controller, do the class for which the instanced
  147. self.controller = controller
  148. self.__set_stable(True)
  149. self.inports = {}
  150. self.outports = {}
  151. self.timers = {}
  152. self.states = {}
  153. self.eventless_states = 0
  154. self.configuration_bitmap = 0
  155. self.transition_mem = {}
  156. self.config_mem = {}
  157. self.semantics = StatechartSemantics()
  158. # to break ties in the heap, compare by number of events in the list
  159. def __lt__(self, other):
  160. return len(self.events.event_list) < len(other.events.event_list)
  161. def getChildren(self, link_name):
  162. pass
  163. def getSingleChild(self, link_name):
  164. return self.getChildren(link_name)[0] # assume this will return a single child...
  165. def getOutPortName(self, port_name):
  166. return self.outports[port_name] if port_name in self.outports else port_name
  167. def getInPortName(self, port_name):
  168. return self.inports[port_name] if port_name in self.inports else port_name
  169. def start(self):
  170. self.configuration = []
  171. self.active = True
  172. self.current_state = {}
  173. self.history_values = {}
  174. self.timers = {}
  175. self.timers_to_add = {}
  176. self.big_step = BigStepState()
  177. self.combo_step = ComboStepState()
  178. self.small_step = SmallStepState()
  179. self.__set_stable(False)
  180. self.initializeStatechart()
  181. self.processBigStepOutput()
  182. def stop(self):
  183. self.active = False
  184. self.__set_stable(True)
  185. def updateConfiguration(self, states):
  186. self.configuration.extend(states)
  187. self.configuration_bitmap = sum([2 ** s.state_id for s in states])
  188. def getSimulatedTime(self):
  189. return self.controller.state.simulated_time * 1000
  190. def addTimer(self, index, timeout):
  191. self.timers_to_add[index] = (self.controller.state.simulated_time + timeout, Event("_%iafter" % index))
  192. def removeTimer(self, index):
  193. if index in self.timers_to_add:
  194. del self.timers_to_add[index]
  195. if index in self.timers:
  196. self.events.remove(self.timers[index])
  197. del self.timers[index]
  198. self.earliest_event_time = self.events.getEarliestTime()
  199. def addEvent(self, event_list, time_offset=0):
  200. event_time = self.controller.state.simulated_time + time_offset
  201. if not (event_time, self) in self.controller.state.instance_times:
  202. heappush(self.controller.state.instance_times, (event_time, self))
  203. if event_time < self.earliest_event_time:
  204. self.earliest_event_time = event_time
  205. if not isinstance(event_list, list):
  206. event_list = [event_list]
  207. for e in event_list:
  208. self.events.add(event_time, e)
  209. def processBigStepOutput(self):
  210. for e in self.big_step.output_events_port:
  211. self.controller.state.outputEvent(e)
  212. for e in self.big_step.output_events_om:
  213. self.controller.state.addEvent(e)
  214. def __set_stable(self, is_stable):
  215. self.is_stable = is_stable
  216. # self.earliest_event_time keeps track of the earliest time this instance will execute a transition
  217. if not is_stable:
  218. self.earliest_event_time = self.controller.state.simulated_time
  219. elif not self.active:
  220. self.earliest_event_time = INFINITY
  221. else:
  222. self.earliest_event_time = self.events.getEarliestTime()
  223. if self.earliest_event_time != INFINITY:
  224. if not (self.earliest_event_time, self) in self.controller.state.instance_times:
  225. heappush(self.controller.state.instance_times, (self.earliest_event_time, self))
  226. def step(self):
  227. is_stable = False
  228. while not is_stable:
  229. due = []
  230. if self.events.getEarliestTime() <= self.controller.state.simulated_time:
  231. due = [self.events.pop()]
  232. is_stable = not self.bigStep(due)
  233. self.processBigStepOutput()
  234. for index, entry in list(self.timers_to_add.items()):
  235. self.timers[index] = self.events.add(*entry)
  236. self.timers_to_add = {}
  237. self.__set_stable(True)
  238. def inState(self, state_strings):
  239. state_ids = [self.states[state_string].state_id for state_string in state_strings]
  240. for state_id in state_ids:
  241. for s in self.configuration:
  242. if s.state_id == state_id:
  243. break
  244. else:
  245. return False
  246. return True
  247. def bigStep(self, input_events):
  248. self.big_step.next(input_events)
  249. self.small_step.reset()
  250. self.combo_step.reset()
  251. while self.comboStep():
  252. self.big_step.has_stepped = True
  253. if self.semantics.big_step_maximality == StatechartSemantics.TakeOne:
  254. break # Take One -> only one combo step allowed
  255. return self.big_step.has_stepped
  256. def comboStep(self):
  257. self.combo_step.next()
  258. while self.smallStep():
  259. self.combo_step.has_stepped = True
  260. return self.combo_step.has_stepped
  261. # generate transition candidates for current small step
  262. # @profile
  263. def generateCandidates(self):
  264. changed_bitmap = self.combo_step.changed_bitmap
  265. key = (self.configuration_bitmap, changed_bitmap)
  266. try:
  267. transitions = self.transition_mem[key]
  268. except:
  269. self.transition_mem[key] = transitions = [t for s in self.configuration if
  270. not (2 ** s.state_id & changed_bitmap) for t in s.transitions]
  271. enabledEvents = self.getEnabledEvents()
  272. enabledTransitions = []
  273. for t in transitions:
  274. if t.isEnabled(enabledEvents, enabledTransitions):
  275. enabledTransitions.append(t)
  276. return enabledTransitions
  277. # @profile
  278. def smallStep(self):
  279. def __younger_than(x, y):
  280. if x.source in y.source.ancestors:
  281. return 1
  282. elif y.source in x.source.ancestors:
  283. return -1
  284. else:
  285. return 0
  286. if self.small_step.has_stepped:
  287. self.small_step.next()
  288. candidates = self.generateCandidates()
  289. if candidates:
  290. to_skip = set()
  291. conflicting = []
  292. for c1 in candidates:
  293. if c1 not in to_skip:
  294. conflict = [c1]
  295. for c2 in candidates[candidates.index(c1):]:
  296. if c2.source in c1.source.ancestors or c1.source in c2.source.ancestors:
  297. conflict.append(c2)
  298. to_skip.add(c2)
  299. if sys.version_info[0] < 3:
  300. conflicting.append(sorted(conflict, cmp=__younger_than))
  301. else:
  302. import functools
  303. conflicting.append(sorted(conflict, key=functools.cmp_to_key(__younger_than)))
  304. if self.semantics.concurrency == StatechartSemantics.Single:
  305. candidate = conflicting[0]
  306. if self.semantics.priority == StatechartSemantics.SourceParent:
  307. candidate[-1].fire()
  308. else:
  309. candidate[0].fire()
  310. elif self.semantics.concurrency == StatechartSemantics.Many:
  311. pass # TODO: implement
  312. self.small_step.has_stepped = True
  313. return self.small_step.has_stepped
  314. # @profile
  315. def getEnabledEvents(self):
  316. result = self.small_step.current_events + self.combo_step.current_events
  317. if self.semantics.input_event_lifeline == StatechartSemantics.Whole or (
  318. not self.big_step.has_stepped and
  319. (self.semantics.input_event_lifeline == StatechartSemantics.FirstComboStep or (
  320. not self.combo_step.has_stepped and
  321. self.semantics.input_event_lifeline == StatechartSemantics.FirstSmallStep))):
  322. result += self.big_step.input_events
  323. return result
  324. def raiseInternalEvent(self, event):
  325. if self.semantics.internal_event_lifeline == StatechartSemantics.NextSmallStep:
  326. self.small_step.addNextEvent(event)
  327. elif self.semantics.internal_event_lifeline == StatechartSemantics.NextComboStep:
  328. self.combo_step.addNextEvent(event)
  329. elif self.semantics.internal_event_lifeline == StatechartSemantics.Queue:
  330. self.addEvent(event)
  331. def initializeStatechart(self):
  332. self.updateConfiguration(self.default_targets)
  333. for state in self.default_targets:
  334. self.eventless_states += state.has_eventless_transitions
  335. if state.enter:
  336. state.enter()
  337. if self.eventless_states:
  338. pass
  339. # TODO: Check (untill now no problems)
  340. #self.controller.object_manager.eventless.add(self)
  341. class ClassState():
  342. def __init__(self, name) -> None:
  343. self.name = name
  344. self.next_time = INFINITY
  345. self.port_mappings = {}
  346. self.input_queue = EventQueue()
  347. self.simulated_time = 0
  348. self.to_send = []
  349. self.events = EventQueue()
  350. self.instances = {}
  351. self.next_instance = 0
  352. self.instance_times = []
  353. self.eventless = set()
  354. self.regex_pattern = re.compile("^([a-zA-Z_]\w*)(?:\[(\d+)\])?$")
  355. self.handlers = {"narrow_cast": self.handleNarrowCastEvent,
  356. "broad_cast": self.handleBroadCastEvent,
  357. "create_instance": self.handleCreateEvent,
  358. "associate_instance": self.handleAssociateEvent,
  359. "disassociate_instance": self.handleDisassociateEvent,
  360. "start_instance": self.handleStartInstanceEvent,
  361. "delete_instance": self.handleDeleteInstanceEvent,
  362. "create_and_start_instance": self.handleCreateAndStartEvent}
  363. self.output_listeners = []
  364. self.inports = {}
  365. self.lock = threading.Condition()
  366. def getEarliestEventTime(self):
  367. with self.lock:
  368. while self.instance_times and self.instance_times[0][0] < self.instance_times[0][1].earliest_event_time:
  369. heappop(self.instance_times)
  370. return min(INFINITY if not self.instance_times else self.instance_times[0][0], self.events.getEarliestTime())
  371. def addEvent(self, event, time_offset = 0):
  372. self.events.add(self.simulated_time + time_offset, event)
  373. # broadcast an event to all instances
  374. def broadcast(self, source, new_event, time_offset = 0):
  375. for i in self.instances:
  376. if self.instances[i] != source:
  377. self.instances[i].addEvent(new_event, time_offset)
  378. def stepAll(self):
  379. self.step()
  380. simulated_time = self.simulated_time
  381. self.to_step = set()
  382. if len(self.instance_times) > (4 * len(self.instances)):
  383. new_instance_times = []
  384. for it in self.instances:
  385. if it.earliest_event_time != INFINITY:
  386. new_instance_times.append((it.earliest_event_time, it))
  387. self.instance_times = new_instance_times
  388. heapify(self.instance_times)
  389. while self.instance_times and self.instance_times[0][0] <= simulated_time:
  390. self.to_step.add(heappop(self.instance_times)[1])
  391. for i in self.to_step | self.eventless:
  392. if i.active and (i.earliest_event_time <= simulated_time or i.eventless_states):
  393. i.step()
  394. def step(self):
  395. while self.events.getEarliestTime() <= self.simulated_time:
  396. if self.events:
  397. self.handleEvent(self.events.pop())
  398. def start(self):
  399. for i in self.instances:
  400. i.start()
  401. def handleInput(self):
  402. while not self.input_queue.isEmpty():
  403. event_time = self.input_queue.getEarliestTime()
  404. event = self.input_queue.pop()
  405. #if event.instance is None:
  406. # event.instance = self.processAssociationReference(event.parameters[0])[0][1]
  407. if event.instance is None:
  408. target_instance = None
  409. else:
  410. target_instance = self.instances[event.instance]
  411. if target_instance == None:
  412. self.broadcast(None, event, event_time - self.simulated_time)
  413. else:
  414. target_instance.addEvent(event, event_time - self.simulated_time)
  415. def addInput(self, input_event_list, time_offset = 0):
  416. if not isinstance(input_event_list, list):
  417. input_event_list = [input_event_list]
  418. for e in input_event_list:
  419. if e.getName() == "":
  420. raise InputException("Input event can't have an empty name.")
  421. #if e.getPort() not in self.IPorts:
  422. # raise InputException("Input port mismatch, no such port: " + e.getPort() + ".")
  423. self.input_queue.add((0 if self.simulated_time is None else 0) + time_offset, e)
  424. def handleEvent(self, e):
  425. self.handlers[e.getName()](e.getParameters())
  426. def outputEvent(self, event):
  427. self.to_send.append(event)
  428. def processAssociationReference(self, input_string):
  429. if len(input_string) == 0:
  430. raise AssociationReferenceException("Empty association reference.")
  431. path_string = input_string.split("/")
  432. result = []
  433. for piece in path_string:
  434. match = self.regex_pattern.match(piece)
  435. if match:
  436. name = match.group(1)
  437. index = match.group(2)
  438. if index is None:
  439. index = -1
  440. result.append((name,int(index)))
  441. else:
  442. raise AssociationReferenceException("Invalid entry in association reference. Input string: " + input_string)
  443. return result
  444. def handleStartInstanceEvent(self, parameters):
  445. if len(parameters) != 2:
  446. raise ParameterException ("The start instance event needs 2 parameters.")
  447. else:
  448. source = parameters[0]
  449. source_index = None
  450. try:
  451. source_traversal_list = self.processAssociationReference(source.association_name)
  452. source_index = source_traversal_list[0][1]
  453. except:
  454. source_index = 0
  455. traversal_list = self.processAssociationReference(parameters[1])
  456. for i in self.getInstances(source, traversal_list):
  457. ev = Event("start_instance", None, [i['path']], source_index)
  458. self.to_send.append((self.name, i['to_class'], ev))
  459. def handleBroadCastEvent(self, parameters):
  460. if len(parameters) != 2:
  461. raise ParameterException ("The broadcast event needs 2 parameters (source of event and event name).")
  462. self.broadcast(parameters[0], parameters[1])
  463. def handleCreateEvent(self, parameters):
  464. if len(parameters) < 2:
  465. raise ParameterException ("The create event needs at least 2 parameters.")
  466. source = parameters[0]
  467. source_index = None
  468. try:
  469. source_index = self.processAssociationReference(source.association_name)
  470. source_index = source_index[0][1]
  471. except:
  472. # TODO: I think the else is only for mainapp becuase it would not have an assocation name
  473. source_index = 0
  474. association_name = parameters[1]
  475. traversal_list = self.processAssociationReference(association_name)
  476. instances = self.getInstances(source, traversal_list)
  477. association = source.associations[association_name]
  478. if association.allowedToAdd():
  479. ''' allow subclasses to be instantiated '''
  480. class_name = association.to_class if len(parameters) == 2 else parameters[2]
  481. self.to_send.append((self.name, class_name, Event('create_instance', None, parameters[1:], source_index)))
  482. #else:
  483. # source.addEvent(Event("instance_creation_error", None, [association_name]))
  484. # return []
  485. def handleCreateAndStartEvent(self, parameters):
  486. params = self.handleCreateEvent(parameters)
  487. if params:
  488. self.handleStartInstanceEvent(params)
  489. def handleDeleteInstanceEvent(self, parameters):
  490. if len(parameters) < 2:
  491. raise ParameterException ("The delete event needs at least 2 parameters.")
  492. else:
  493. source = parameters[0]
  494. association_name = parameters[1]
  495. traversal_list = self.processAssociationReference(association_name)
  496. instances = self.getInstances(source, traversal_list)
  497. association = source.associations[traversal_list[0][0]]
  498. index = None
  499. try:
  500. index = self.processAssociationReference(source.association_name)
  501. index = index[0][1]
  502. params = list(association.instances.values())
  503. except:
  504. # TODO: This is only for the default, don't know if it will work always --> beter check source with instances
  505. index = self.processAssociationReference(association_name)
  506. params = [index[0][1]]
  507. index = 0
  508. self.to_send.append((self.name, association.to_class, Event("delete_instance", None, [parameters[1], params], index)))
  509. def handleAssociateEvent(self, parameters):
  510. if len(parameters) != 3:
  511. raise ParameterException ("The associate_instance event needs 3 parameters.")
  512. else:
  513. source = parameters[0]
  514. to_copy_list = self.getInstances(source, self.processAssociationReference(parameters[1]))
  515. if len(to_copy_list) != 1:
  516. raise AssociationReferenceException ("Invalid source association reference.")
  517. wrapped_to_copy_instance = to_copy_list[0]["instance"]
  518. dest_list = self.processAssociationReference(parameters[2])
  519. if len(dest_list) == 0:
  520. raise AssociationReferenceException ("Invalid destination association reference.")
  521. last = dest_list.pop()
  522. if last[1] != -1:
  523. raise AssociationReferenceException ("Last association name in association reference should not be accompanied by an index.")
  524. added_links = []
  525. for i in self.getInstances(source, dest_list):
  526. association = i["instance"].associations[last[0]]
  527. if association.allowedToAdd():
  528. index = association.addInstance(wrapped_to_copy_instance)
  529. added_links.append(i["path"] + ("" if i["path"] == "" else "/") + last[0] + "[" + str(index) + "]")
  530. source.addEvent(Event("instance_associated", parameters = [added_links]))
  531. def handleDisassociateEvent(self, parameters):
  532. if len(parameters) < 2:
  533. raise ParameterException ("The disassociate_instance event needs at least 2 parameters.")
  534. else:
  535. source = parameters[0]
  536. association_name = parameters[1]
  537. if not isinstance(association_name, list):
  538. association_name = [association_name]
  539. deleted_links = []
  540. for a_n in association_name:
  541. traversal_list = self.processAssociationReference(a_n)
  542. instances = self.getInstances(source, traversal_list)
  543. for i in instances:
  544. try:
  545. index = i['ref'].associations[i['assoc_name']].removeInstance(i["instance"])
  546. deleted_links.append(a_n + "[" + str(index) + "]")
  547. except AssociationException as exception:
  548. raise RuntimeException("Error disassociating '" + a_n + "': " + str(exception))
  549. source.addEvent(Event("instance_disassociated", parameters = [deleted_links]))
  550. def handleNarrowCastEvent(self, parameters):
  551. if len(parameters) != 3:
  552. raise ParameterException ("The narrow_cast event needs 3 parameters.")
  553. source = parameters[0]
  554. if not isinstance(parameters[1], list):
  555. targets = [parameters[1]]
  556. else:
  557. targets = parameters[1]
  558. for target in targets:
  559. traversal_list = self.processAssociationReference(target)
  560. cast_event = parameters[2]
  561. for i in self.getInstances(source, traversal_list):
  562. ev = Event(cast_event.name, None, cast_event.parameters, i["instance"])
  563. self.to_send.append((self.name, i['to_class'], ev))
  564. def getInstances(self, source, traversal_list):
  565. currents = [{
  566. "to_class": None,
  567. "instance": source,
  568. "ref": None,
  569. "assoc_name": None,
  570. "assoc_index": None,
  571. "path": ""
  572. }]
  573. for (name, index) in traversal_list:
  574. nexts = []
  575. for current in currents:
  576. association = current["instance"].associations[name]
  577. if (index >= 0 ):
  578. try:
  579. # TODO: check if this check works
  580. check = association.instances_to_ids[index]
  581. nexts.append({
  582. "to_class": association.to_class,
  583. "instance": index,
  584. "ref": current["instance"],
  585. "assoc_name": name,
  586. "assoc_index": index,
  587. "path": current["path"] + ("" if current["path"] == "" else "/") + name + "[" + str(index) + "]"
  588. })
  589. except KeyError:
  590. # Entry was removed, so ignore this request
  591. pass
  592. elif (index == -1):
  593. for i in association.instances:
  594. parent = association.instances[i]
  595. nexts.append({
  596. "to_class": association.to_class,
  597. "instance": parent,
  598. "ref": current["instance"],
  599. "assoc_name": name,
  600. "assoc_index": index,
  601. "path": current["path"] + ("" if current["path"] == "" else "/") + name + "[" + str(index) + "]"
  602. })
  603. #nexts.extend( association.instances.values() )
  604. else:
  605. raise AssociationReferenceException("Incorrect index in association reference.")
  606. currents = nexts
  607. return currents
  608. class ClassBase(AtomicDEVS):
  609. def __init__(self, name):
  610. AtomicDEVS.__init__(self, name)
  611. self.glob_outputs = {}
  612. self.outputs = {}
  613. self.state = ClassState(name)
  614. #self.elapsed = 0
  615. self.obj_manager_in = self.addInPort("obj_manager_in")
  616. self.obj_manager_out = self.addOutPort("obj_manager_out")
  617. def constructObject(self, parameters):
  618. raise "Something went wrong "
  619. def extTransition(self, inputs):
  620. # Update simulated time
  621. self.state.simulated_time += self.elapsed
  622. self.state.next_time = 0
  623. # Collect all inputs
  624. all_inputs = [input for input_list in inputs.values() for input in input_list]
  625. for input in all_inputs:
  626. if isinstance(input, str):
  627. tem = eval(input)
  628. # TODO: This works, the instance does not create a ball in bouncing balls but this is probably normal (port != input_port)
  629. #tem.instance = self.state.port_mappings[tem.port]
  630. tem.instance = self.state.port_mappings.setdefault(tem.port, None)
  631. if tem.instance != None:
  632. tem.port = get_private_port(tem.port)
  633. self.state.addInput(tem)
  634. elif input[2].name == "create_instance":
  635. new_instance = self.constructObject(input[2].parameters)
  636. self.state.instances[self.state.next_instance] = new_instance
  637. p = new_instance.associations.get("parent")
  638. if p:
  639. p.addInstance(input[2].instance)
  640. ev = Event("instance_created", None, [f"{input[2].parameters[0]}[{self.state.next_instance}]"], input[2].instance)
  641. self.state.to_send.append((input[1], input[0], ev))
  642. self.state.next_instance += 1
  643. elif input[2].name == "start_instance":
  644. test = self.state.processAssociationReference(input[2].parameters[0])
  645. index = test[0][1]
  646. instance = self.state.instances[index]
  647. instance.start()
  648. ev = Event("instance_started", None, [input[2].parameters[0]], input[2].instance)
  649. self.state.to_send.append((input[1], input[0], ev))
  650. elif input[2].name == "delete_instance":
  651. for index in input[2].parameters[1]:
  652. i = self.state.instances[index]
  653. for assoc_name in i.associations:
  654. if not (assoc_name == "parent"):
  655. traversal_list = self.state.processAssociationReference(assoc_name)
  656. instances = self.state.getInstances(i, traversal_list)
  657. if len(instances) > 0:
  658. raise RuntimeException("Error removing instance from association %s, still %i children left connected with association %s" % (association_name, len(instances), assoc_name))
  659. i.user_defined_destructor()
  660. i.stop()
  661. self.state.instances = {key: value for key, value in self.state.instances.items() if key not in input[2].parameters[1]}
  662. ev = Event("instance_deleted", None, [input[2].parameters[0], input[2].parameters[1]], input[2].instance)
  663. self.state.to_send.append((input[1], input[0], ev))
  664. elif input[2].name == "instance_created":
  665. instance = self.state.instances[input[2].instance]
  666. test = self.state.processAssociationReference(input[2].parameters[0])
  667. association_name = test[0][0]
  668. association_index = test[0][1]
  669. association = instance.associations[association_name]
  670. if association.allowedToAdd():
  671. ''' allow subclasses to be instantiated '''
  672. class_name = association.to_class # TODO: normally the following is behind this: if len(parameters) == 2 else parameters[2]
  673. try:
  674. new_index = association.addInstance(association_index)
  675. except AssociationException as exception:
  676. raise RuntimeException("Error adding instance to association '" + association_name + "': " + str(exception))
  677. instance.addEvent(input[2])
  678. elif input[2].name == "instance_started":
  679. instance = self.state.instances[input[2].instance]
  680. instance.addEvent(input[2])
  681. elif input[2].name == "instance_deleted":
  682. source = self.state.instances[input[2].instance]
  683. association_name = input[2].parameters[0]
  684. traversal_list = self.state.processAssociationReference(association_name)
  685. instances = self.state.getInstances(source, traversal_list)
  686. association = source.associations[traversal_list[0][0]]
  687. for index, instance in enumerate(instances):
  688. try:
  689. association.removeInstance(instance["instance"])
  690. except AssociationException as exception:
  691. raise RuntimeException("Error removing instance from association '" + association_name + "': " + str(exception))
  692. source.addEvent(Event("instance_deleted", parameters = [input[2].parameters[0]]))
  693. else:
  694. ev = input[2]
  695. self.state.addInput(ev)
  696. return self.state
  697. def intTransition(self):
  698. # Update simulated time and clear previous messages
  699. self.state.simulated_time += self.state.next_time
  700. self.state.to_send = []
  701. # Calculate the next event time, clamp to ensure non-negative result
  702. self.state.next_time = min(self.state.getEarliestEventTime(), self.state.simulated_time + self.state.input_queue.getEarliestTime())
  703. self.state.next_time -= self.state.simulated_time
  704. self.state.next_time = max(self.state.next_time, 0.0)
  705. # Handle incoming inputs and do a step in all statecharts
  706. self.state.handleInput()
  707. self.state.stepAll()
  708. return self.state
  709. def outputFnc(self):
  710. to_dict = {}
  711. for sending in self.state.to_send:
  712. if isinstance(sending, tuple) and sending[2].port == None:
  713. to_dict.setdefault(self.obj_manager_out, []).append(sending)
  714. else:
  715. the_port = next((port for port in self.OPorts if port.name == sending.port), None)
  716. to_dict.setdefault(the_port, []).append(sending)
  717. return to_dict
  718. def timeAdvance(self):
  719. return self.state.next_time
  720. class ObjectManagerBase(AtomicDEVS):
  721. def __init__(self, name):
  722. AtomicDEVS.__init__(self, name)
  723. self.output = {}
  724. def extTransition(self, inputs):
  725. all_inputs = inputs[self.input]
  726. self.state.to_send.extend(all_inputs)
  727. return self.state
  728. def intTransition(self):
  729. self.state.to_send.clear()
  730. return self.state
  731. def outputFnc(self):
  732. out_dict = {}
  733. for source, target, message in self.state.to_send:
  734. out_dict.setdefault(self.output.get(target), []).append((source, target, message))
  735. return out_dict
  736. def timeAdvance(self):
  737. return 0 if self.state.to_send else INFINITY
  738. # TODO: port class as wrapper to define the in and out ports the same as in SCCD
  739. class Ports:
  740. private_port_counter = 0
  741. inports = {}
  742. outports = {}
  743. @classmethod
  744. def addOutputPort(self, virtual_name, instance=None):
  745. if instance == None:
  746. port_name = virtual_name
  747. else:
  748. port_name = "private_" + str(self.private_port_counter) + "_" + virtual_name
  749. self.outports[port_name] = instance
  750. self.private_port_counter += 1
  751. return port_name
  752. @classmethod
  753. def addInputPort(self, virtual_name, instance=None):
  754. if instance == None:
  755. port_name = virtual_name
  756. else:
  757. port_name = "private_" + str(self.private_port_counter) + "_" + virtual_name
  758. self.inports[port_name] = instance
  759. self.private_port_counter += 1
  760. return port_name