DEVS_statecharts_core.py 39 KB

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