SCCD_execute.alc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. include "primitives.alh"
  2. include "modelling.alh"
  3. include "object_operations.alh"
  4. include "utils.alh"
  5. include "random.alh"
  6. include "library.alh"
  7. Void function print_states(model : Element, data : Element):
  8. Element classes
  9. Element states
  10. Element class
  11. String state
  12. log("Current states:")
  13. classes = set_copy(data["classes"])
  14. while (read_nr_out(classes) > 0):
  15. class = set_pop(classes)
  16. log(string_join(string_join(string_join(" ", class["ID"]), " : "), read_attribute(model, class["type"], "name")))
  17. log(" Attributes: " + dict_to_string(class["attributes"]))
  18. states = set_copy(class["states"])
  19. log(" States:")
  20. while (read_nr_out(states) > 0):
  21. state = set_pop(states)
  22. log(string_join(" ", read_attribute(model, state, "name")))
  23. return!
  24. Element function filter(model : Element, set : Element, attribute_name : String, attribute_value : Element):
  25. Element keys
  26. String key
  27. Element result
  28. result = create_node()
  29. while (read_nr_out(set) > 0):
  30. key = set_pop(set)
  31. if (value_eq(read_attribute(model, key, attribute_name), attribute_value)):
  32. set_add(result, key)
  33. return result!
  34. Element function filter_exists(model : Element, set : Element, attribute_name : String):
  35. Element keys
  36. String key
  37. Element result
  38. result = create_node()
  39. while (read_nr_out(set) > 0):
  40. key = set_pop(set)
  41. if (element_neq(read_attribute(model, key, attribute_name), read_root())):
  42. set_add(result, key)
  43. return result!
  44. Element function expand_current_state(model : Element, state : String, current_states : Element):
  45. // Find the hierarchy of all current states, and select those that contain the currently selected state
  46. Element result
  47. result = create_node()
  48. current_states = set_copy(current_states)
  49. Element hierarchy
  50. String deep_state
  51. while (read_nr_out(current_states) > 0):
  52. deep_state = set_pop(current_states)
  53. hierarchy = find_hierarchy(model, deep_state)
  54. // Got the hierarchy of one of the states
  55. if (set_in(hierarchy, state)):
  56. // This hierarchy contains the root state we are checking for, so add to set
  57. set_add(result, deep_state)
  58. return result!
  59. Element function expand_initial_state(model : Element, state : String):
  60. String t
  61. t = read_type(model, state)
  62. if (t == "SCCD/CompositeState"):
  63. // Recurse further in the composite
  64. return expand_composite_state(model, state)!
  65. elif (t == "SCCD/ParallelState"):
  66. // Split up all components
  67. return expand_parallel_state(model, state)!
  68. else:
  69. // Probably just an atomic, so return this one only
  70. Element result
  71. result = create_node()
  72. set_add(result, state)
  73. return result!
  74. Element function expand_composite_state(model : Element, composite_state : String):
  75. // Resolve all initial states from a single composite state
  76. String initial
  77. // Fetch the initial state
  78. initial = set_pop(filter(model, allAssociationDestinations(model, composite_state, "SCCD/composite_children"), "isInitial", True))
  79. // Expand the initial state, depending on what it is
  80. return expand_initial_state(model, initial)!
  81. Element function expand_parallel_state(model : Element, parallel_state : String):
  82. // Resolve all initial states from a single parallel state
  83. Element children
  84. Element result
  85. Element expanded_children
  86. children = allAssociationDestinations(model, parallel_state, "SCCD/parallel_children")
  87. result = create_node()
  88. while (read_nr_out(children) > 0):
  89. set_merge(result, expand_initial_state(model, set_pop(children)))
  90. return result!
  91. Void function start_class(model : Element, data : Element, class : String):
  92. // Start up the class and assign its initial state to it
  93. // Create the data structure for a running class
  94. Element class_handle
  95. class_handle = create_node()
  96. dict_add(class_handle, "type", class)
  97. dict_add(class_handle, "ID", cast_id2s(create_node()))
  98. dict_add(class_handle, "timers", create_node())
  99. // Add the current state of the class
  100. String initial_state
  101. // Should only be one behaviour linked to it!
  102. initial_state = set_pop(allAssociationDestinations(model, class, "SCCD/behaviour"))
  103. dict_add(class_handle, "states", expand_initial_state(model, initial_state))
  104. // Add all attributes
  105. Element attributes
  106. attributes = create_node()
  107. Element attrs
  108. attrs = allAssociationDestinations(model, class, "SCCD/class_attributes")
  109. while (read_nr_out(attrs) > 0):
  110. dict_add(attributes, read_attribute(model, set_pop(attrs), "name"), read_root())
  111. dict_add(class_handle, "attributes", attributes)
  112. // Execute all entry actions
  113. Element init
  114. init = create_node()
  115. set_add(init, "")
  116. // Initial state before initialization is the set with an empty hierarchy
  117. // Empty set would not find any difference between the source and target
  118. execute_actions(model, init, set_copy(class_handle["states"]), attributes)
  119. dict_add(data["classes"], class_handle["ID"], class_handle)
  120. return!
  121. Element function get_enabled_transitions(model : Element, state : String, data : Element, class : String):
  122. // Returns all enabled transitions
  123. Element result
  124. Element to_filter
  125. String attr
  126. String transition
  127. Element cond
  128. result = create_node()
  129. to_filter = allOutgoingAssociationInstances(model, state, "SCCD/transition")
  130. while (read_nr_out(to_filter) > 0):
  131. transition = set_pop(to_filter)
  132. // Check event
  133. attr = read_attribute(model, transition, "event")
  134. if (bool_not(bool_or(element_eq(attr, read_root()), set_in(data["events"], attr)))):
  135. continue!
  136. // Check after
  137. // Only an after if there was no event!
  138. if (bool_and(element_eq(attr, read_root()), read_attribute(model, transition, "after"))):
  139. if (dict_in(data["classes"][class]["timers"], transition)):
  140. // Registered timer already, let's check if it has expired
  141. if (float_gt(data["classes"][class]["timers"][transition], time())):
  142. // Not enabled yet
  143. continue!
  144. else:
  145. // Not registered even, so not enabled either
  146. continue!
  147. // Check condition
  148. cond = read_attribute(model, transition, "cond")
  149. if (element_neq(cond, read_root())):
  150. cond = get_func_AL_model(import_node(cond))
  151. // Execute condition
  152. if (bool_not(cond(data["classes"][class]["attributes"]))):
  153. // Condition false, so skip
  154. continue!
  155. // All is OK for this transition
  156. set_add(result, transition)
  157. return result!
  158. Void function execute_actions(model : Element, source_states : Element, target_states : Element, attributes : Element):
  159. Element actions
  160. actions = get_actions_to_execute(model, source_states, target_states)
  161. while (read_nr_out(actions) > 0):
  162. Element action
  163. action = list_pop(actions, 0)
  164. action(attributes)
  165. return!
  166. Element function execute_transition(model : Element, data : Element, class : String, transition : String):
  167. // Execute the script (if any)
  168. Element script
  169. script = read_attribute(model, transition, "script")
  170. if (element_neq(script, read_root())):
  171. script = get_func_AL_model(import_node(script))
  172. script(data["classes"][class]["attributes"])
  173. // Raise events (if any)
  174. Element events
  175. String event
  176. events = allAssociationDestinations(model, transition, "SCCD/transition_raises")
  177. while (read_nr_out(events) > 0):
  178. event = set_pop(events)
  179. set_add(data["events"], read_attribute(model, event, "event"))
  180. // Find new set of states
  181. Element target_states
  182. Element source_states
  183. source_states = expand_current_state(model, readAssociationSource(model, transition), data["classes"][class]["states"])
  184. target_states = expand_initial_state(model, readAssociationDestination(model, transition))
  185. execute_actions(model, source_states, target_states, data["classes"][class]["attributes"])
  186. return target_states!
  187. Boolean function step_class(model : Element, data : Element, class : String):
  188. // Find enabled transitions in a class and execute it, updating the state
  189. // Iterate over all current states, searching for enabled transitions
  190. // Search for enabled transitions in higher levels as well!
  191. Element states
  192. Element new_states
  193. String state
  194. Element transitions
  195. String transition
  196. Boolean transitioned
  197. Element hierarchy
  198. String current_state
  199. Boolean found
  200. states = set_copy(data["classes"][class]["states"])
  201. new_states = create_node()
  202. transitioned = False
  203. while (read_nr_out(states) > 0):
  204. state = set_pop(states)
  205. found = False
  206. // Loop over the hierarchy of this state and try to apply transitions
  207. hierarchy = find_hierarchy(model, state)
  208. while (read_nr_out(hierarchy) > 0):
  209. current_state = list_pop(hierarchy, 0)
  210. transitions = get_enabled_transitions(model, current_state, data, class)
  211. if (read_nr_out(transitions) > 0):
  212. // Found an enabled transition, so store that one
  213. transition = random_choice(transitions)
  214. // Execute transition
  215. set_merge(new_states, execute_transition(model, data, class, transition))
  216. // When leaving an orthogonal component, we must also pop all related states that might be processed in the future!
  217. Element leaving
  218. leaving = expand_current_state(model, current_state, data["classes"][class]["states"])
  219. set_difference(states, leaving)
  220. transitioned = True
  221. found = True
  222. break!
  223. if (bool_not(found)):
  224. // Nothing found, so stay in the current state
  225. set_add(new_states, state)
  226. // Update states
  227. dict_overwrite(data["classes"][class], "states", new_states)
  228. reschedule_timeouts(model, data, class)
  229. return transitioned!
  230. Void function reschedule_timeouts(model : Element, data : Element, class : String):
  231. Element timed_transitions
  232. Element old_timed_transitions
  233. String transition
  234. Element states
  235. String state
  236. timed_transitions = create_node()
  237. states = set_copy(data["classes"][class]["states"])
  238. // Collect all timed transitions that are currently active
  239. while (read_nr_out(states) > 0):
  240. state = set_pop(states)
  241. // NOTE this set_merge does not eliminate duplicates, though this should happen later on when adding the timer (see other NOTE)
  242. set_merge(timed_transitions, filter_exists(model, allOutgoingAssociationInstances(model, state, "SCCD/transition"), "after"))
  243. // Remove timers that no longer exist
  244. old_timed_transitions = dict_keys(data["classes"][class]["timers"])
  245. while (read_nr_out(old_timed_transitions) > 0):
  246. transition = set_pop(old_timed_transitions)
  247. if (bool_not(set_in(timed_transitions, transition))):
  248. // Transition is no longer scheduled for any state, so remove
  249. dict_delete(data["classes"][class]["timers"], transition)
  250. // Schedule timers that are not already scheduled
  251. while (read_nr_out(timed_transitions) > 0):
  252. transition = set_pop(timed_transitions)
  253. // NOTE Normally, a timer will not be added twice here, as the previous occurence will already find it
  254. if (bool_not(dict_in(data["classes"][class]["timers"], transition))):
  255. // Not yet scheduled this transition: do so now
  256. Element after
  257. Float after_duration
  258. after = read_attribute(model, transition, "after")
  259. after = get_func_AL_model(import_node(after))
  260. after_duration = after(data["classes"][class]["attributes"])
  261. dict_add(data["classes"][class]["timers"], transition, float_addition(data["start_time"], after_duration))
  262. return !
  263. String function get_parent(model : Element, state : String):
  264. Element tmp_set
  265. tmp_set = allAssociationOrigins(model, state, "SCCD/composite_children")
  266. set_merge(tmp_set, allAssociationOrigins(model, state, "SCCD/parallel_children"))
  267. if (read_nr_out(tmp_set) > 0):
  268. return set_pop(tmp_set)!
  269. else:
  270. return ""!
  271. Element function find_hierarchy(model : Element, state : String):
  272. if (state == ""):
  273. return create_node()!
  274. else:
  275. Element result
  276. String parent
  277. parent = get_parent(model, state)
  278. // We have a parent, so take the parent list first
  279. result = find_hierarchy(model, parent)
  280. list_append(result, state)
  281. return result!
  282. Element function get_actions_to_execute(model : Element, source_states : Element, target_states : Element):
  283. Element result
  284. result = create_node()
  285. source_states = set_copy(source_states)
  286. target_states = set_copy(target_states)
  287. // Add all exit and entry actions to the list of actions to execute
  288. // Do this by finding the common parent, and then doing all exit actions up to that node, and all entry actions up to the target_state
  289. // First, find the hierarchy!
  290. Element hierarchy_sources
  291. Element hierarchy_targets
  292. Element all_hierarchies
  293. hierarchy_sources = create_node()
  294. while (read_nr_out(source_states) > 0):
  295. set_add(hierarchy_sources, find_hierarchy(model, set_pop(source_states)))
  296. hierarchy_targets = create_node()
  297. while (read_nr_out(target_states) > 0):
  298. set_add(hierarchy_targets, find_hierarchy(model, set_pop(target_states)))
  299. all_hierarchies = set_copy(hierarchy_sources)
  300. set_merge(all_hierarchies, hierarchy_targets)
  301. // Difference these all lists, finding the first common entry
  302. Element iter_hierarchies
  303. Integer i
  304. String current
  305. Element hierarchy
  306. Boolean finished
  307. i = 0
  308. finished = False
  309. while (bool_not(finished)):
  310. // Check the i-th element in both and see if they are equal
  311. current = ""
  312. iter_hierarchies = set_copy(all_hierarchies)
  313. while (read_nr_out(iter_hierarchies) > 0):
  314. hierarchy = set_pop(iter_hierarchies)
  315. // Exhausted one of the lists
  316. if (i >= list_len(hierarchy)):
  317. finished = True
  318. break!
  319. // First entry, so read out value as reference
  320. if (current == ""):
  321. current = list_read(hierarchy, i)
  322. // Check with reference element
  323. if (bool_not(value_eq(list_read(hierarchy, i), current))):
  324. finished = True
  325. break!
  326. // i-th element equal for all hierarchies, so go to next element
  327. if (bool_not(finished)):
  328. i = i + 1
  329. // Found the first differing element at position i
  330. // All elements remaining in hierarchy_source are to be traversed in REVERSE order for the exit actions
  331. // All elements remaining in hierarchy_target are to be traversed in NORMAL order for the entry actions
  332. // This is not that simple either, as we need to consider that some actions might already have been added to the list...
  333. // Add hierarchy_sources actions
  334. String state
  335. Element visited
  336. Element action
  337. Element spliced_hierarchy
  338. Element hierarchy_source
  339. Element hierarchy_target
  340. visited = create_node()
  341. while (read_nr_out(hierarchy_sources) > 0):
  342. // Get one of these hierarchies
  343. hierarchy_source = set_pop(hierarchy_sources)
  344. spliced_hierarchy = list_splice(hierarchy_source, i, list_len(hierarchy_source))
  345. while (list_len(spliced_hierarchy) > 0):
  346. state = list_pop(spliced_hierarchy, list_len(spliced_hierarchy) - 1)
  347. if (set_in(visited, state)):
  348. // Already added this state, so don't bother
  349. continue!
  350. else:
  351. // New state, so prepend it to the list
  352. // Prepend, instead of append, as we want to do these operations in reverse order!
  353. // First check if there is any exit action at all
  354. action = read_attribute(model, state, "onExitScript")
  355. if (element_neq(action, read_root())):
  356. // An exit action is found!
  357. list_insert(result, get_func_AL_model(import_node(action)), 0)
  358. // Add this state as visited, even though there might not have been an associated action
  359. set_add(visited, state)
  360. // Add hierarchy_targets actions
  361. // Clear visited, just to be safe, though it should not matter
  362. visited = create_node()
  363. while (read_nr_out(hierarchy_targets) > 0):
  364. // Get one of these hierarchies
  365. hierarchy_target = set_pop(hierarchy_targets)
  366. spliced_hierarchy = list_splice(hierarchy_target, i, list_len(hierarchy_target))
  367. while (list_len(spliced_hierarchy) > 0):
  368. state = list_pop(spliced_hierarchy, list_len(spliced_hierarchy) - 1)
  369. if (set_in(visited, state)):
  370. // Already added this state, so don't bother
  371. continue!
  372. else:
  373. // New state, so append it to the list
  374. // Append, instead of prepend, as we want to do these operations in normal order!
  375. // First check if there is any entry action at all
  376. action = read_attribute(model, state, "onEntryScript")
  377. if (element_neq(action, read_root())):
  378. // An entry action is found!
  379. list_append(result, get_func_AL_model(import_node(action)))
  380. // Add this state as visited, even though there might not have been an associated action
  381. set_add(visited, state)
  382. return result!
  383. Float function step(model : Element, data : Element):
  384. // Step through all classes
  385. Element classes
  386. Element class
  387. Float t_min
  388. Float t_current
  389. Boolean transitioned
  390. Element keys
  391. String key
  392. t_min = time() + 99999.0
  393. classes = dict_keys(data["classes"])
  394. // TODO this should use simulated time or something
  395. dict_overwrite(data, "start_time", time())
  396. transitioned = False
  397. while (read_nr_out(classes) > 0):
  398. class = set_pop(classes)
  399. if (step_class(model, data, class)):
  400. transitioned = True
  401. if (bool_not(transitioned)):
  402. // Find minimum timer for this class, and store that
  403. keys = dict_keys(data["classes"][class]["timers"])
  404. while (read_nr_out(keys) > 0):
  405. key = set_pop(keys)
  406. t_current = data["classes"][class]["timers"][key]
  407. if (t_current < t_min):
  408. t_min = t_current
  409. if (transitioned):
  410. // Do another step, as we can transition
  411. return 0.0!
  412. else:
  413. return float_subtraction(t_min, data["start_time"])!
  414. Boolean function main(model : Element):
  415. // Executes the provided SCCD model
  416. Element data
  417. data = create_node()
  418. dict_add(data, "classes", create_node())
  419. // Prepare for input
  420. output("Ready for input!")
  421. // Find initial
  422. String default_class
  423. default_class = set_pop(filter(model, allInstances(model, "SCCD/Class"), "default", True))
  424. // Start up the default class
  425. start_class(model, data, default_class)
  426. Float timeout
  427. Element interrupt
  428. timeout = 0.0
  429. while (True):
  430. print_states(model, data)
  431. interrupt = input_timeout(timeout)
  432. if (value_eq(interrupt, "#EXIT#")):
  433. // Stop execution
  434. return True!
  435. dict_overwrite(data, "events", create_node())
  436. if (element_neq(interrupt, read_root())):
  437. // Got interrupt
  438. log("Got event: " + cast_v2s(interrupt))
  439. set_add(data["events"], interrupt)
  440. output("Processed event, ready for more!")
  441. timeout = step(model, data)
  442. log("Pausing for " + cast_v2s(timeout))
  443. // We should never get here!
  444. return False!