DEVS_statecharts_core.py 39 KB

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