include "primitives.alh" include "modelling.alh" include "object_operations.alh" include "utils.alh" include "random.alh" include "library.alh" Void function print_states(model : Element, data : Element): Element classes Element states Element class String state log("Current states:") classes = set_copy(data["classes"]) while (read_nr_out(classes) > 0): class = set_pop(classes) log(string_join(string_join(string_join(" ", class["ID"]), " : "), read_attribute(model, class["type"], "name"))) log(" Attributes: " + dict_to_string(class["attributes"])) states = set_copy(class["states"]) log(" States:") while (read_nr_out(states) > 0): state = set_pop(states) log(string_join(" ", read_attribute(model, state, "name"))) return! Element function filter(model : Element, set : Element, attribute_name : String, attribute_value : Element): Element keys String key Element result result = create_node() while (read_nr_out(set) > 0): key = set_pop(set) if (value_eq(read_attribute(model, key, attribute_name), attribute_value)): set_add(result, key) return result! Element function filter_exists(model : Element, set : Element, attribute_name : String): Element keys String key Element result result = create_node() while (read_nr_out(set) > 0): key = set_pop(set) if (element_neq(read_attribute(model, key, attribute_name), read_root())): set_add(result, key) return result! Element function expand_state(model : Element, state : String): String t t = read_type(model, state) if (t == "SCCD/CompositeState"): // Recurse further in the composite return expand_composite_state(model, state)! elif (t == "SCCD/ParallelState"): // Split up all components return expand_parallel_state(model, state)! else: // Probably just an atomic, so return this one only Element result result = create_node() set_add(result, state) return result! Element function expand_composite_state(model : Element, composite_state : String): // Resolve all initial states from a single composite state String initial // Fetch the initial state initial = set_pop(filter(model, allAssociationDestinations(model, composite_state, "SCCD/composite_children"), "isInitial", True)) // Expand the initial state, depending on what it is return expand_state(model, initial)! Element function expand_parallel_state(model : Element, parallel_state : String): // Resolve all initial states from a single parallel state Element children Element result Element expanded_children children = allAssociationDestinations(model, parallel_state, "SCCD/parallel_children") result = create_node() while (read_nr_out(children) > 0): set_merge(result, expand_state(model, set_pop(children))) return result! Void function start_class(model : Element, data : Element, class : String): // Start up the class and assign its initial state to it // Create the data structure for a running class Element class_handle class_handle = create_node() dict_add(class_handle, "type", class) dict_add(class_handle, "ID", cast_id2s(create_node())) dict_add(class_handle, "timers", create_node()) // Add the current state of the class String initial_state // Should only be one behaviour linked to it! initial_state = set_pop(allAssociationDestinations(model, class, "SCCD/behaviour")) dict_add(class_handle, "states", expand_state(model, initial_state)) // Add all attributes Element attributes attributes = create_node() Element attrs attrs = allAssociationDestinations(model, class, "SCCD/class_attributes") while (read_nr_out(attrs) > 0): dict_add(attributes, read_attribute(model, set_pop(attrs), "name"), read_root()) dict_add(class_handle, "attributes", attributes) dict_add(data["classes"], class_handle["ID"], class_handle) return! Element function get_enabled_transitions(model : Element, state : String, data : Element, class : String): // Returns all enabled transitions // TODO ignore conditions and afters Element result Element to_filter String attr String transition Element cond result = create_node() to_filter = allOutgoingAssociationInstances(model, state, "SCCD/transition") while (read_nr_out(to_filter) > 0): transition = set_pop(to_filter) // Check event attr = read_attribute(model, transition, "event") if (bool_not(bool_or(element_eq(attr, read_root()), set_in(data["events"], attr)))): continue! // Check after // Only an after if there was no event! if (bool_and(element_eq(attr, read_root()), read_attribute(model, transition, "after"))): if (dict_in(data["classes"][class]["timers"], transition)): // Registered timer already, let's check if it has expired if (float_gt(data["classes"][class]["timers"][transition], time())): // Not enabled yet continue! else: // Not registered even, so not enabled either continue! // Check condition cond = read_attribute(model, transition, "cond") if (element_neq(cond, read_root())): cond = get_func_AL_model(import_node(cond)) // Execute condition if (bool_not(cond(data["classes"][class]["attributes"]))): // Condition false, so skip continue! // All is OK for this transition set_add(result, transition) return result! Element function execute_transition(model : Element, data : Element, class : String, transition : String): // Execute the script (if any) Element script script = read_attribute(model, transition, "script") if (element_neq(script, read_root())): script = get_func_AL_model(import_node(script)) script(data["classes"][class]["attributes"]) // Raise events (if any) Element events String event events = allAssociationDestinations(model, transition, "SCCD/transition_raises") while (read_nr_out(events) > 0): event = set_pop(events) set_add(data["events"], read_attribute(model, event, "event")) // Return new set of states return expand_state(model, readAssociationDestination(model, transition))! Boolean function step_class(model : Element, data : Element, class : String): // Find enabled transitions in a class and execute it, updating the state // Iterate over all current states, searching for enabled transitions // Search for enabled transitions in higher levels as well! Element states Element new_states String state Element transitions String transition Boolean transitioned states = set_copy(data["classes"][class]["states"]) new_states = create_node() transitioned = False while (read_nr_out(states) > 0): state = set_pop(states) // Fetch transitions in this state specifically (NO parent) transitions = get_enabled_transitions(model, state, data, class) if (read_nr_out(transitions) != 0): // Found an enabled transition, so store that one transition = random_choice(transitions) // Execute transition set_merge(new_states, execute_transition(model, data, class, transition)) transitioned = True else: // Try going to the parent // TODO // Nothing found, so stay in the current state set_add(new_states, state) // Update states dict_overwrite(data["classes"][class], "states", new_states) reschedule_timeouts(model, data, class) return transitioned! Void function reschedule_timeouts(model : Element, data : Element, class : String): Element timed_transitions Element old_timed_transitions String transition Element states String state timed_transitions = create_node() states = set_copy(data["classes"][class]["states"]) // Collect all timed transitions that are currently active while (read_nr_out(states) > 0): state = set_pop(states) // NOTE this set_merge does not eliminate duplicates, though this should happen later on when adding the timer (see other NOTE) timed_transitions = set_merge(timed_transitions, filter_exists(model, allOutgoingAssociationInstances(model, state, "SCCD/transition"), "after")) // Remove timers that no longer exist old_timed_transitions = dict_keys(data["classes"][class]["timers"]) while (read_nr_out(old_timed_transitions) > 0): transition = set_pop(old_timed_transitions) if (bool_not(set_in(timed_transitions, transition))): // Transition is no longer scheduled for any state, so remove dict_delete(data["classes"][class]["timers"], transition) // Schedule timers that are not already scheduled while (read_nr_out(timed_transitions) > 0): transition = set_pop(timed_transitions) // NOTE Normally, a timer will not be added twice here, as the previous occurence will already find it if (bool_not(dict_in(data["classes"][class]["timers"], transition))): // Not yet scheduled this transition: do so now Element after Float after_duration after = read_attribute(model, transition, "after") after = get_func_AL_model(import_node(after)) after_duration = after(data["classes"][class]["attributes"]) dict_add(data["classes"][class]["timers"], transition, float_addition(data["start_time"], after_duration)) return ! Float function step(model : Element, data : Element): // Step through all classes Element classes Element class Float t_min Float t_current Boolean transitioned Element keys String key t_min = time() + 99999.0 classes = dict_keys(data["classes"]) // TODO this should use simulated time or something dict_overwrite(data, "start_time", time()) transitioned = False while (read_nr_out(classes) > 0): class = set_pop(classes) if (step_class(model, data, class)): transitioned = True if (bool_not(transitioned)): // Find minimum timer for this class, and store that keys = dict_keys(data["classes"][class]["timers"]) while (read_nr_out(keys) > 0): key = set_pop(keys) t_current = data["classes"][class]["timers"][key] if (t_current < t_min): t_min = t_current if (transitioned): // Do another step, as we can transition return 0.0! else: return float_subtraction(t_min, data["start_time"])! Boolean function main(model : Element): // Executes the provided SCCD model Element data data = create_node() dict_add(data, "classes", create_node()) // Prepare for input output("Ready for input!") // Find initial String default_class default_class = set_pop(filter(model, allInstances(model, "SCCD/Class"), "default", True)) // Start up the default class start_class(model, data, default_class) Float timeout Element interrupt timeout = 0.0 while (True): print_states(model, data) interrupt = input_timeout(timeout) if (value_eq(interrupt, "#EXIT#")): // Stop execution return True! dict_overwrite(data, "events", create_node()) if (element_neq(interrupt, read_root())): // Got interrupt log("Got event: " + cast_v2s(interrupt)) set_add(data["events"], interrupt) output("Processed event, ready for more!") timeout = step(model, data) log("Pausing for " + cast_v2s(timeout)) // We should never get here! return False!