Просмотр исходного кода

fixed balls, todo fix adding multiple instances of a class

sampieters 1 год назад
Родитель
Сommit
21b9b0b339

+ 6 - 1
README.md

@@ -94,4 +94,9 @@ To finally run the sccd in pypDEVS, run the following command:
 python3 runner.py
 ```
 
-### Timer
+### Timer
+
+## TODO
+fill parameters in constructor (e.g. Button, Ball).
+some "if" should be "elif"
+self.inports["elem"] should only be "elem"

+ 156 - 86
examples/BouncingBalls/PyDEVS/new_tester.py

@@ -15,6 +15,9 @@ from sccd.runtime.libs import ui_v2 as ui
 from sccd.runtime.libs.utils import utils
 import random
 
+CANVAS_WIDTH = 800
+CANVAS_HEIGHT = 550
+
 # package "Bouncing_Balls_Python_Version"
 
 class MainAppInstance(RuntimeClassBase):
@@ -220,10 +223,10 @@ class MainApp(AtomicDEVS, ObjectManagerBase):
             if isinstance(input, str):
                 tem = eval(input)
                 self.addInput(tem)
-            if input[3].name == "create_instance":
+            elif input[3].name == "create_instance":
                 self.instances.add(MainAppInstance(self))
                 ev = Event("instance_created", None, parameters=[f"{input[3].parameters[0]}[{len(self.instances)-1}]"])
-                self.to_send.append((self.name, input[1], input[2], ev))
+                self.to_send.append((input[1], input[0], input[2], ev))
             elif input[3].name == "start_instance":
                 instance = list(self.instances)[input[2]]
                 instance.start()
@@ -267,8 +270,7 @@ class MainApp(AtomicDEVS, ObjectManagerBase):
         self.handleInput()
         self.stepAll()
         next_earliest = min(self.getEarliestEventTime(), self.input_queue.getEarliestTime())
-        
-        if len(self.to_send) != 0:
+        if not (len(self.to_send) == 0):
             self.next_time = 0
         elif next_earliest == INFINITY:
             self.next_time = INFINITY
@@ -323,19 +325,9 @@ class FieldInstance(RuntimeClassBase):
         FieldInstance.user_defined_constructor(self)
     
     def user_defined_constructor(self):
-        # All of these TkInter calls have been converted to in/out-events:
-        #self.field_window = ui.new_window(800,600,"BouncingBalls");
-        #self.canvas = ui.append_canvas(self.field_window,800,550,{'background':'#eee'});
-        #ui.bind_event(self.field_window, ui.EVENTS.WINDOW_CLOSE, self.controller, 'window_close', self.inports['field_ui']);
-        #ui.bind_event(self.field_window, ui.EVENTS.KEY_PRESS, self.controller, 'key_press', self.inports['field_ui']);
-        #ui.bind_event(self.canvas.element, ui.EVENTS.MOUSE_RIGHT_CLICK,    self.controller, 'right_click', self.inports['field_ui']);
-        #ui.bind_event(self.canvas.element, ui.EVENTS.MOUSE_MOVE, self.controller, 'mouse_move', self.inports['field_ui']);
-        #ui.bind_event(self.canvas.element, ui.EVENTS.MOUSE_RELEASE, self.controller, 'mouse_release', self.inports['field_ui']);
-        print("created field")
         pass
     
     def user_defined_destructor(self):
-        # ui.close_window(self.field_window);
         pass
     
     
@@ -493,10 +485,10 @@ class FieldInstance(RuntimeClassBase):
         self.states["/root/running"].addTransition(_root_running_0)
     
     def _root_creating_window_enter(self):
-        self.big_step.outputEvent(Event("create_window", self.getOutPortName("ui"), [800, 600, "BouncingBalls", self.inports['field_ui']]))
+        self.big_step.outputEvent(Event("create_window", self.getOutPortName("ui"), [800, 600, "BouncingBalls", 'field_ui']))
     
     def _root_creating_canvas_enter(self):
-        self.big_step.outputEvent(Event("create_canvas", self.getOutPortName("ui"), [self.window_id, 800, 550, {'background':'#eee'}, self.inports['field_ui']]))
+        self.big_step.outputEvent(Event("create_canvas", self.getOutPortName("ui"), [self.window_id, CANVAS_WIDTH, CANVAS_HEIGHT, {'background':'#eee'}, 'field_ui']))
     
     def _root_creating_button_enter(self):
         self.big_step.outputEventOM(Event("create_instance", None, [self, "buttons", "Button", self.window_id, 'create_new_field', 'Spawn New Window']))
@@ -512,15 +504,15 @@ class FieldInstance(RuntimeClassBase):
     def _root_creating_window_0_exec(self, parameters):
         window_id = parameters[0]
         self.window_id = window_id
-        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [window_id, ui.EVENTS.WINDOW_CLOSE, 'window_close', self.inports['field_ui']]))
-        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [window_id, ui.EVENTS.KEY_PRESS, 'key_press', self.inports['field_ui']]))
+        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [window_id, ui.EVENTS.WINDOW_CLOSE, 'window_close', 'field_ui']))
+        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [window_id, ui.EVENTS.KEY_PRESS, 'key_press', 'field_ui']))
     
     def _root_creating_canvas_0_exec(self, parameters):
         canvas_id = parameters[0]
         self.canvas_id = canvas_id
-        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [canvas_id, ui.EVENTS.MOUSE_RIGHT_CLICK, 'right_click', self.inports['field_ui']]))
-        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [canvas_id, ui.EVENTS.MOUSE_MOVE, 'mouse_move', self.inports['field_ui']]))
-        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [canvas_id, ui.EVENTS.MOUSE_RELEASE, 'mouse_release', self.inports['field_ui']]))
+        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [canvas_id, ui.EVENTS.MOUSE_RIGHT_CLICK, 'right_click', 'field_ui']))
+        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [canvas_id, ui.EVENTS.MOUSE_MOVE, 'mouse_move', 'field_ui']))
+        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [canvas_id, ui.EVENTS.MOUSE_RELEASE, 'mouse_release', 'field_ui']))
     
     def _root_creating_button_0_exec(self, parameters):
         association_name = parameters[0]
@@ -530,7 +522,7 @@ class FieldInstance(RuntimeClassBase):
         x = parameters[0]
         y = parameters[1]
         button = parameters[2]
-        self.big_step.outputEventOM(Event("create_instance", None, [self, "balls", "Ball", self.canvas, x, y]))
+        self.big_step.outputEventOM(Event("create_instance", None, [self, "balls", "Ball", self.canvas_id, x, y]))
     
     def _root_running_main_behaviour_creating_ball_0_exec(self, parameters):
         association_name = parameters[0]
@@ -576,6 +568,9 @@ class Field(AtomicDEVS, ObjectManagerBase):
         self.field_ui = self.addInPort("field_ui")
         self.obj_manager_in = self.addInPort("obj_manager_in")
         self.input = self.addInPort("input")
+
+        self.inports["field_ui"] = self.addInputPort("field_ui", self)
+
         self.next_time = INFINITY
     
     def extTransition(self, inputs):
@@ -591,7 +586,7 @@ class Field(AtomicDEVS, ObjectManagerBase):
             if isinstance(input, str):
                 tem = eval(input)
                 self.addInput(tem)
-            if input[3].name == "create_instance":
+            elif input[3].name == "create_instance":
                 self.instances.add(FieldInstance(self))
                 ev = Event("instance_created", None, parameters=[f"{input[3].parameters[0]}[{len(self.instances)-1}]"])
                 self.to_send.append((input[1], input[0], input[2], ev))
@@ -610,9 +605,12 @@ class Field(AtomicDEVS, ObjectManagerBase):
                 ev = Event("instance_disassociated", None, parameters=[TODO])
                 self.to_send.append((self.name, TODO, TODO, ev))
             elif input[3].name == "instance_created":
+                association = self.processAssociationReference(input[3].parameters[0])[0]
                 instance = list(self.instances)[input[2]]
+                p = instance.associations.get(association[0])
+                if p:
+                    p.addInstance(input[3].parameters[0])
                 instance.addEvent(input[3])
-                #instance.associations['fields'].instances[0] = input[3].parameters[0]
             elif input[3].name == "instance_started":
                 instance = list(self.instances)[input[2]]
                 instance.addEvent(input[3])
@@ -628,6 +626,9 @@ class Field(AtomicDEVS, ObjectManagerBase):
             elif input[3].name == "set_association_name":
                 ev = input[3]
                 self.addInput(ev, force_internal=True)
+            else:
+                ev = input[3]
+                self.addInput(ev)
         return self.instances
     
     def intTransition(self):
@@ -638,7 +639,10 @@ class Field(AtomicDEVS, ObjectManagerBase):
         self.handleInput()
         self.stepAll()
         next_earliest = min(self.getEarliestEventTime(), self.input_queue.getEarliestTime())
-        if next_earliest == INFINITY:
+
+        if not (len(self.to_send) == 0):
+            self.next_time = 0
+        elif next_earliest == INFINITY:
             self.next_time = INFINITY
         else:
             self.next_time = next_earliest - earliest
@@ -692,10 +696,6 @@ class ButtonInstance(RuntimeClassBase):
     def user_defined_constructor(self, window_id, event_name, button_text):
         self.window_id = window_id;
         self.event_name = event_name;
-        
-        # Translated to events:
-        #button = ui.append_button(tkparent, event_name);
-        #ui.bind_event(button.element, ui.EVENTS.MOUSE_CLICK, self.controller, 'mouse_click', self.inports['button_ui']);
     
     def user_defined_destructor(self):
         pass
@@ -734,12 +734,12 @@ class ButtonInstance(RuntimeClassBase):
         self.states["/running"].addTransition(_running_0)
     
     def _creating_button_enter(self):
-        self.big_step.outputEvent(Event("create_button", self.getOutPortName("ui"), [self.window_id, self.event_name, self.inports['button_ui']]))
+        self.big_step.outputEvent(Event("create_button", self.getOutPortName("ui"), [self.window_id, self.event_name, 'button_ui']))
     
     def _creating_button_0_exec(self, parameters):
         button_id = parameters[0]
         self.button_id = button_id
-        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [button_id, ui.EVENTS.MOUSE_CLICK, "mouse_click", self.inports['button_ui']]))
+        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [button_id, ui.EVENTS.MOUSE_CLICK, "mouse_click", 'button_ui']))
     
     def _running_0_exec(self, parameters):
         x = parameters[0]
@@ -785,18 +785,61 @@ class Button(AtomicDEVS, ObjectManagerBase):
             if isinstance(input, str):
                 tem = eval(input)
                 self.addInput(tem)
-            if input[3].name == "create_instance":
-                self.instances.add(ButtonInstance(self))
+            elif input[3].name == "create_instance":
+                '''
+                if len(parameters) < 2:
+                    raise ParameterException ("The create event needs at least 2 parameters.")
+
+                source = parameters[0]
+                association_name = parameters[1]
+        
+                traversal_list = self.processAssociationReference(association_name)
+                instances = self.getInstances(source, traversal_list)
+                
+                association = source.associations[association_name]
+                # association = self.instances_map[source].getAssociation(association_name)
+                if association.allowedToAdd():
+                    class_name = association.to_class if len(parameters) == 2 else parameters[2]
+                    new_instance = self.createInstance(class_name, parameters[3:])
+                    if not new_instance:
+                        raise ParameterException("Creating instance: no such class: " + class_name)
+                    # index = association.addInstance(new_instance)
+                    try:
+                        index = association.addInstance(new_instance)
+                    except AssociationException as exception:
+                        raise RuntimeException("Error adding instance to association '" + association_name + "': " + str(exception))
+                    p = new_instance.associations.get("parent")
+                    if p:
+                        p.addInstance(source)
+                    source.addEvent(Event("instance_created", None, [association_name+"["+str(index)+"]"]))
+                    return [source, association_name+"["+str(index)+"]"]
+                else:
+                    source.addEvent(Event("instance_creation_error", None, [association_name]))
+                    return []
+                '''
+
+                new_instance = ButtonInstance(self, input[3].parameters[1], input[3].parameters[2], input[3].parameters[3])
+                self.instances.add(new_instance)
+
+                #p = new_instance.associations.get("parent")
+                #if p:
+                #    p.addInstance(f"{input[3].parameters[0]}[{input[3].parameters[1]}]")
+
+
+
                 ev = Event("instance_created", None, parameters=[f"{input[3].parameters[0]}[{len(self.instances)-1}]"])
-                self.to_send.append((self.name, input[1], input[2], ev))
+                self.to_send.append((input[1], input[0], input[2], ev))
             elif input[3].name == "start_instance":
                 instance = list(self.instances)[input[2]]
                 instance.start()
                 ev = Event("instance_started", None, parameters=[f"{input[0]}[{input[2]}]"])
                 self.to_send.append((input[0], input[1], input[2], ev))
             elif input[3].name == "delete_instance":
-                ev = Event("instance_deleted", None, parameters=[TODO])
-                self.to_send.append((self.name, TODO, TODO, ev))
+                instance = list(self.instances)[input[2]]
+                instance.user_defined_destructor()
+                instance.stop()
+                ev = Event("instance_deleted", None, parameters=[self, input[0]])
+                self.to_send.append((self.name, input[1], input[2], ev))
             elif input[3].name == "associate_instance":
                 ev = Event("instance_associated", None, parameters=[TODO])
                 self.to_send.append((self.name, TODO, TODO, ev))
@@ -832,7 +875,9 @@ class Button(AtomicDEVS, ObjectManagerBase):
         self.handleInput()
         self.stepAll()
         next_earliest = min(self.getEarliestEventTime(), self.input_queue.getEarliestTime())
-        if next_earliest == INFINITY:
+        if not (len(self.to_send) == 0):
+            self.next_time = 0
+        elif next_earliest == INFINITY:
             self.next_time = INFINITY
         else:
             self.next_time = next_earliest - earliest
@@ -861,7 +906,7 @@ class Button(AtomicDEVS, ObjectManagerBase):
         return self.next_time
 
 class BallInstance(RuntimeClassBase):
-    def __init__(self, atomdevs, canvas, x, y):
+    def __init__(self, atomdevs, canvas_id, x, y):
         RuntimeClassBase.__init__(self, atomdevs)
         self.associations = {}
         self.associations["parent"] = Association("Field", 1, 1)
@@ -876,17 +921,18 @@ class BallInstance(RuntimeClassBase):
         self.build_statechart_structure()
         
         # user defined attributes
-        self.canvas = None
+        self.canvas_id = None
+        self.pos = None
         
         # call user defined constructor
-        BallInstance.user_defined_constructor(self, canvas, x, y)
+        BallInstance.user_defined_constructor(self, canvas_id, x, y)
     
-    def user_defined_constructor(self, canvas, x, y):
-        self.canvas = canvas;
+    def user_defined_constructor(self, canvas_id, x, y):
+        self.canvas_id = canvas_id;
         self.r = 20.0;
         self.vel = {'x': random.uniform(-5.0, 5.0), 'y': random.uniform(-5.0, 5.0)};
-        self.mouse_pos = {};
-        self.smooth = 0.4; # value between 0 and 1
+        self.pos = {'x': x, 'y': y};
+        self.smooth = 0.6; # value between 0 and 1
         
         # TODO:
         #circle = self.canvas.add_circle(x, y, self.r, {'fill':'#000'});
@@ -896,7 +942,8 @@ class BallInstance(RuntimeClassBase):
         #self.element = circle;
     
     def user_defined_destructor(self):
-        self.canvas.remove_element(self.element);
+        #self.canvas.remove_element(self.element);
+        pass
     
     
     # builds Statechart structure
@@ -911,24 +958,29 @@ class BallInstance(RuntimeClassBase):
         # state /main_behaviour/initializing
         self.states["/main_behaviour/initializing"] = State(2, "/main_behaviour/initializing", self)
         
+        # state /main_behaviour/creating_circle
+        self.states["/main_behaviour/creating_circle"] = State(3, "/main_behaviour/creating_circle", self)
+        self.states["/main_behaviour/creating_circle"].setEnter(self._main_behaviour_creating_circle_enter)
+        
         # state /main_behaviour/bouncing
-        self.states["/main_behaviour/bouncing"] = State(3, "/main_behaviour/bouncing", self)
+        self.states["/main_behaviour/bouncing"] = State(4, "/main_behaviour/bouncing", self)
         self.states["/main_behaviour/bouncing"].setEnter(self._main_behaviour_bouncing_enter)
         self.states["/main_behaviour/bouncing"].setExit(self._main_behaviour_bouncing_exit)
         
         # state /main_behaviour/dragging
-        self.states["/main_behaviour/dragging"] = State(4, "/main_behaviour/dragging", self)
+        self.states["/main_behaviour/dragging"] = State(5, "/main_behaviour/dragging", self)
         
         # state /main_behaviour/selected
-        self.states["/main_behaviour/selected"] = State(5, "/main_behaviour/selected", self)
+        self.states["/main_behaviour/selected"] = State(6, "/main_behaviour/selected", self)
         
         # state /deleted
-        self.states["/deleted"] = State(6, "/deleted", self)
+        self.states["/deleted"] = State(7, "/deleted", self)
         
         # add children
         self.states[""].addChild(self.states["/main_behaviour"])
         self.states[""].addChild(self.states["/deleted"])
         self.states["/main_behaviour"].addChild(self.states["/main_behaviour/initializing"])
+        self.states["/main_behaviour"].addChild(self.states["/main_behaviour/creating_circle"])
         self.states["/main_behaviour"].addChild(self.states["/main_behaviour/bouncing"])
         self.states["/main_behaviour"].addChild(self.states["/main_behaviour/dragging"])
         self.states["/main_behaviour"].addChild(self.states["/main_behaviour/selected"])
@@ -937,11 +989,17 @@ class BallInstance(RuntimeClassBase):
         self.states["/main_behaviour"].default_state = self.states["/main_behaviour/initializing"]
         
         # transition /main_behaviour/initializing
-        _main_behaviour_initializing_0 = Transition(self, self.states["/main_behaviour/initializing"], [self.states["/main_behaviour/bouncing"]])
+        _main_behaviour_initializing_0 = Transition(self, self.states["/main_behaviour/initializing"], [self.states["/main_behaviour/creating_circle"]])
         _main_behaviour_initializing_0.setAction(self._main_behaviour_initializing_0_exec)
         _main_behaviour_initializing_0.setTrigger(Event("set_association_name", None))
         self.states["/main_behaviour/initializing"].addTransition(_main_behaviour_initializing_0)
         
+        # transition /main_behaviour/creating_circle
+        _main_behaviour_creating_circle_0 = Transition(self, self.states["/main_behaviour/creating_circle"], [self.states["/main_behaviour/bouncing"]])
+        _main_behaviour_creating_circle_0.setAction(self._main_behaviour_creating_circle_0_exec)
+        _main_behaviour_creating_circle_0.setTrigger(Event("circle_created", None))
+        self.states["/main_behaviour/creating_circle"].addTransition(_main_behaviour_creating_circle_0)
+        
         # transition /main_behaviour/bouncing
         _main_behaviour_bouncing_0 = Transition(self, self.states["/main_behaviour/bouncing"], [self.states["/main_behaviour/bouncing"]])
         _main_behaviour_bouncing_0.setAction(self._main_behaviour_bouncing_0_exec)
@@ -974,8 +1032,11 @@ class BallInstance(RuntimeClassBase):
         _main_behaviour_selected_1.setTrigger(Event("delete_self", None))
         self.states["/main_behaviour/selected"].addTransition(_main_behaviour_selected_1)
     
+    def _main_behaviour_creating_circle_enter(self):
+        self.big_step.outputEvent(Event("create_circle", self.getOutPortName("ui"), [self.canvas_id, self.pos['x'], self.pos['y'], self.r, {'fill':'#000'}, 'ball_ui']))
+    
     def _main_behaviour_bouncing_enter(self):
-        self.addTimer(0, (20 - self.getSimulatedTime() % 20) / 1000.0)
+        self.addTimer(0, 0.02)
     
     def _main_behaviour_bouncing_exit(self):
         self.removeTimer(0)
@@ -984,19 +1045,29 @@ class BallInstance(RuntimeClassBase):
         association_name = parameters[0]
         self.association_name = association_name
     
+    def _main_behaviour_creating_circle_0_exec(self, parameters):
+        canvas_id = parameters[0]
+        circle_id = parameters[1]
+        self.circle_id = circle_id
+        self.big_step.outputEvent(Event("bind_canvas_event", self.getOutPortName("ui"), [self.canvas_id, circle_id, ui.EVENTS.MOUSE_PRESS, 'mouse_press', 'ball_ui']))
+        self.big_step.outputEvent(Event("bind_canvas_event", self.getOutPortName("ui"), [self.canvas_id, circle_id, ui.EVENTS.MOUSE_MOVE, 'mouse_move', 'ball_ui']))
+        self.big_step.outputEvent(Event("bind_canvas_event", self.getOutPortName("ui"), [self.canvas_id, circle_id, ui.EVENTS.MOUSE_RELEASE, 'mouse_release', 'ball_ui']))
+    
     def _main_behaviour_bouncing_0_exec(self, parameters):
-        pos = self.element.get_position();    
-        if pos.x-self.r <= 0 or pos.x+self.r >= self.canvas.get_width():
+        # Invert velocity when colliding with canvas border:
+        if self.pos['x']-self.r <= 0 or self.pos['x']+self.r >= CANVAS_WIDTH:
             self.vel['x'] = -self.vel['x'];
-        if pos.y-self.r <= 0 or pos.y+self.r >= self.canvas.get_height():
+        if self.pos['y']-self.r <= 0 or self.pos['y']+self.r >= CANVAS_HEIGHT:
             self.vel['y'] = -self.vel['y'];
-        self.element.move(self.vel['x'], self.vel['y']);
+        self.big_step.outputEvent(Event("move_element", self.getOutPortName("ui"), [self.canvas_id, self.circle_id, self.vel['x'], self.vel['y']]))
+        self.pos['x'] += self.vel['x']
+        self.pos['y'] += self.vel['y']
     
     def _main_behaviour_bouncing_1_exec(self, parameters):
         x = parameters[0]
         y = parameters[1]
         button = parameters[2]
-        self.element.set_color("#ff0");
+        self.big_step.outputEvent(Event("set_element_color", self.getOutPortName("ui"), [self.canvas_id, self.circle_id, '#ff0']))
     
     def _main_behaviour_bouncing_1_guard(self, parameters):
         x = parameters[0]
@@ -1008,32 +1079,25 @@ class BallInstance(RuntimeClassBase):
         x = parameters[0]
         y = parameters[1]
         button = parameters[2]
-        dx = x - self.mouse_pos['x'];
-        dy = y - self.mouse_pos['y'];
-        
-        self.element.move(dx, dy);
-        
-        # keep ball within boundaries
-        pos = self.element.get_position();
-        if pos.x-self.r <= 0 :
-            pos.x = self.r + 1;
-        elif pos.x+self.r >= self.canvas.width :
-            pos.x = self.canvas.width-self.r-1;
-        if pos.y-self.r <= 0 :
-            pos.y = self.r + 1;
-        elif pos.y+self.r >= self.canvas.height :
-            pos.y = self.canvas.height-self.r-1;
-        self.element.set_position(pos.x, pos.y);
-        self.mouse_pos = {'x':x, 'y':y};
+        # Always keep ball within canvas:
+        x = min(max(0+self.r, x), CANVAS_WIDTH-self.r)
+        y = min(max(0+self.r, y), CANVAS_HEIGHT-self.r)
+        
+        dx = x - self.pos['x']
+        dy = y - self.pos['y']
+        
         self.vel = {
             'x': (1-self.smooth)*dx + self.smooth*self.vel['x'],
             'y': (1-self.smooth)*dy + self.smooth*self.vel['y']
-        };
+        }
+        
+        self.pos = {'x': x, 'y': y}
+        self.big_step.outputEvent(Event("set_element_pos", self.getOutPortName("ui"), [self.canvas_id, self.circle_id, x-self.r, y-self.r]))
     
     def _main_behaviour_dragging_1_exec(self, parameters):
         x = parameters[0]
         y = parameters[1]
-        self.element.set_color("#f00");
+        self.big_step.outputEvent(Event("set_element_color", self.getOutPortName("ui"), [self.canvas_id, self.circle_id, '#f00']))
     
     def _main_behaviour_selected_0_exec(self, parameters):
         x = parameters[0]
@@ -1049,6 +1113,7 @@ class BallInstance(RuntimeClassBase):
     
     def _main_behaviour_selected_1_exec(self, parameters):
         self.big_step.outputEventOM(Event("narrow_cast", None, [self, 'parent', Event("delete_ball", None, [self.association_name])]))
+        self.big_step.outputEvent(Event("destroy_element", self.getOutPortName("ui"), [self.canvas_id, self.element_id]))
     
     def initializeStatechart(self):
         # enter default state
@@ -1082,18 +1147,19 @@ class Ball(AtomicDEVS, ObjectManagerBase):
             if isinstance(input, str):
                 tem = eval(input)
                 self.addInput(tem)
-            if input[3].name == "create_instance":
-                self.instances.add(BallInstance(self))
+            elif input[3].name == "create_instance":
+                self.instances.add(BallInstance(self, input[3].parameters[1], input[3].parameters[2], input[3].parameters[3]))
                 ev = Event("instance_created", None, parameters=[f"{input[3].parameters[0]}[{len(self.instances)-1}]"])
-                self.to_send.append((self.name, input[1], input[2], ev))
+                self.to_send.append((self.name, input[0], input[2], ev))
             elif input[3].name == "start_instance":
-                instance = list(self.instances)[input[2]]
+                association = self.processAssociationReference(input[3].parameters[0])[0]
+                instance = list(self.instances)[association[1]]
                 instance.start()
-                ev = Event("instance_started", None, parameters=[f"{input[0]}[{input[2]}]"])
-                self.to_send.append((input[0], input[1], input[2], ev))
+                ev = Event("instance_started", None, parameters=[input[3].parameters[0]])
+                self.to_send.append((input[0], input[1], association[1], ev))
             elif input[3].name == "delete_instance":
-                ev = Event("instance_deleted", None, parameters=[TODO])
-                self.to_send.append((self.name, TODO, TODO, ev))
+                ev = Event("instance_deleted", None, parameters=[])
+                self.to_send.append((self.name, input[0], input[2], ev))
             elif input[3].name == "associate_instance":
                 ev = Event("instance_associated", None, parameters=[TODO])
                 self.to_send.append((self.name, TODO, TODO, ev))
@@ -1101,11 +1167,13 @@ class Ball(AtomicDEVS, ObjectManagerBase):
                 ev = Event("instance_disassociated", None, parameters=[TODO])
                 self.to_send.append((self.name, TODO, TODO, ev))
             elif input[3].name == "instance_created":
-                instance = list(self.instances)[input[2]]
+                association = self.processAssociationReference(input[3].parameters[0])[0]
+                instance = list(self.instances)[association[1]]
                 instance.addEvent(input[3])
                 instance.associations['fields'].instances[0] = input[3].parameters[0]
             elif input[3].name == "instance_started":
-                instance = list(self.instances)[input[2]]
+                association = self.processAssociationReference(input[3].parameters[0])[0]
+                instance = list(self.instances)[association[1]]
                 instance.addEvent(input[3])
             elif input[3].name == "instance_deleted":
                 instance = list(self.instances)[input[2]]
@@ -1129,7 +1197,9 @@ class Ball(AtomicDEVS, ObjectManagerBase):
         self.handleInput()
         self.stepAll()
         next_earliest = min(self.getEarliestEventTime(), self.input_queue.getEarliestTime())
-        if next_earliest == INFINITY:
+        if not (len(self.to_send) == 0):
+            self.next_time = 0
+        elif next_earliest == INFINITY:
             self.next_time = INFINITY
         else:
             self.next_time = next_earliest - earliest

+ 5 - 3
examples/BouncingBalls/PyDEVS/runner.py

@@ -1,5 +1,5 @@
 from pypdevs.simulator import Simulator
-import target as target
+import examples.BouncingBalls.PyDEVS.best_target as best_target
 
 from tkinter import *
 from sccd.runtime.libs.DEVui_v2 import UI
@@ -14,8 +14,8 @@ class OutputListener:
 			method(*event.parameters)
 
 if __name__ == '__main__':
-	model = target.Controller(name="controller")
-	refs = {"ui": model.ui, "field_ui": model.atomic1.field_ui}
+	model = best_target.Controller(name="controller")
+	refs = {"ui": model.ui, "field_ui": model.atomic1.field_ui, "button_ui": model.atomic2.button_ui, "ball_ui": model.atomic3.ball_ui}
 
 	tkroot = Tk()
 	tkroot.withdraw()
@@ -28,5 +28,7 @@ if __name__ == '__main__':
 
 	ui = UI(tkroot, sim)
 	model.atomic1.addMyOwnOutputListener(OutputListener(ui))
+	model.atomic2.addMyOwnOutputListener(OutputListener(ui))
+	model.atomic3.addMyOwnOutputListener(OutputListener(ui))
 	sim.simulate()
 	tkroot.mainloop()

+ 77 - 69
examples/BouncingBalls/PyDEVS/target.py

@@ -11,10 +11,13 @@ from sccd.runtime.DEVS_statecharts_core import *
 from pypdevs.DEVS import *
 from pypdevs.infinity import *
 from pypdevs.simulator import *
-from sccd.runtime.libs import DEVui_v2 as ui
+from sccd.runtime.libs import ui_v2 as ui
 from sccd.runtime.libs.utils import utils
 import random
 
+CANVAS_WIDTH = 800
+CANVAS_HEIGHT = 550
+
 # package "Bouncing_Balls_Python_Version"
 
 class MainAppInstance(RuntimeClassBase):
@@ -269,7 +272,7 @@ class MainApp(AtomicDEVS, ObjectManagerBase):
         next_earliest = min(self.getEarliestEventTime(), self.input_queue.getEarliestTime())
         if not (len(self.to_send) == 0):
             self.next_time = 0
-        elif next_earliest == INFINITY:
+        if next_earliest == INFINITY:
             self.next_time = INFINITY
         else:
             self.next_time = next_earliest - earliest
@@ -322,19 +325,9 @@ class FieldInstance(RuntimeClassBase):
         FieldInstance.user_defined_constructor(self)
     
     def user_defined_constructor(self):
-        # All of these TkInter calls have been converted to in/out-events:
-        #self.field_window = ui.new_window(800,600,"BouncingBalls");
-        #self.canvas = ui.append_canvas(self.field_window,800,550,{'background':'#eee'});
-        #ui.bind_event(self.field_window, ui.EVENTS.WINDOW_CLOSE, self.controller, 'window_close', self.inports['field_ui']);
-        #ui.bind_event(self.field_window, ui.EVENTS.KEY_PRESS, self.controller, 'key_press', self.inports['field_ui']);
-        #ui.bind_event(self.canvas.element, ui.EVENTS.MOUSE_RIGHT_CLICK,    self.controller, 'right_click', self.inports['field_ui']);
-        #ui.bind_event(self.canvas.element, ui.EVENTS.MOUSE_MOVE, self.controller, 'mouse_move', self.inports['field_ui']);
-        #ui.bind_event(self.canvas.element, ui.EVENTS.MOUSE_RELEASE, self.controller, 'mouse_release', self.inports['field_ui']);
-        print("created field")
         pass
     
     def user_defined_destructor(self):
-        # ui.close_window(self.field_window);
         pass
     
     
@@ -492,10 +485,10 @@ class FieldInstance(RuntimeClassBase):
         self.states["/root/running"].addTransition(_root_running_0)
     
     def _root_creating_window_enter(self):
-        self.big_step.outputEvent(Event("create_window", self.getOutPortName("ui"), [800, 600, "BouncingBalls", 'field_ui']))
+        self.big_step.outputEvent(Event("create_window", self.getOutPortName("ui"), [800, 600, "BouncingBalls", self.inports['field_ui']]))
     
     def _root_creating_canvas_enter(self):
-        self.big_step.outputEvent(Event("create_canvas", self.getOutPortName("ui"), [self.window_id, 800, 550, {'background':'#eee'}, 'field_ui']))
+        self.big_step.outputEvent(Event("create_canvas", self.getOutPortName("ui"), [self.window_id, CANVAS_WIDTH, CANVAS_HEIGHT, {'background':'#eee'}, self.inports['field_ui']]))
     
     def _root_creating_button_enter(self):
         self.big_step.outputEventOM(Event("create_instance", None, [self, "buttons", "Button", self.window_id, 'create_new_field', 'Spawn New Window']))
@@ -511,15 +504,15 @@ class FieldInstance(RuntimeClassBase):
     def _root_creating_window_0_exec(self, parameters):
         window_id = parameters[0]
         self.window_id = window_id
-        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [window_id, ui.EVENTS.WINDOW_CLOSE, 'window_close', 'field_ui']))
-        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [window_id, ui.EVENTS.KEY_PRESS, 'key_press', 'field_ui']))
+        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [window_id, ui.EVENTS.WINDOW_CLOSE, 'window_close', self.inports['field_ui']]))
+        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [window_id, ui.EVENTS.KEY_PRESS, 'key_press', self.inports['field_ui']]))
     
     def _root_creating_canvas_0_exec(self, parameters):
         canvas_id = parameters[0]
         self.canvas_id = canvas_id
-        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [canvas_id, ui.EVENTS.MOUSE_RIGHT_CLICK, 'right_click', 'field_ui']))
-        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [canvas_id, ui.EVENTS.MOUSE_MOVE, 'mouse_move', 'field_ui']))
-        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [canvas_id, ui.EVENTS.MOUSE_RELEASE, 'mouse_release', 'field_ui']))
+        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [canvas_id, ui.EVENTS.MOUSE_RIGHT_CLICK, 'right_click', self.inports['field_ui']]))
+        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [canvas_id, ui.EVENTS.MOUSE_MOVE, 'mouse_move', self.inports['field_ui']]))
+        self.big_step.outputEvent(Event("bind_event", self.getOutPortName("ui"), [canvas_id, ui.EVENTS.MOUSE_RELEASE, 'mouse_release', self.inports['field_ui']]))
     
     def _root_creating_button_0_exec(self, parameters):
         association_name = parameters[0]
@@ -529,7 +522,7 @@ class FieldInstance(RuntimeClassBase):
         x = parameters[0]
         y = parameters[1]
         button = parameters[2]
-        self.big_step.outputEventOM(Event("create_instance", None, [self, "balls", "Ball", self.canvas, x, y]))
+        self.big_step.outputEventOM(Event("create_instance", None, [self, "balls", "Ball", self.canvas_id, x, y]))
     
     def _root_running_main_behaviour_creating_ball_0_exec(self, parameters):
         association_name = parameters[0]
@@ -590,7 +583,7 @@ class Field(AtomicDEVS, ObjectManagerBase):
             if isinstance(input, str):
                 tem = eval(input)
                 self.addInput(tem)
-            elif input[3].name == "create_instance":
+            if input[3].name == "create_instance":
                 self.instances.add(FieldInstance(self))
                 ev = Event("instance_created", None, parameters=[f"{input[3].parameters[0]}[{len(self.instances)-1}]"])
                 self.to_send.append((input[1], input[0], input[2], ev))
@@ -611,7 +604,7 @@ class Field(AtomicDEVS, ObjectManagerBase):
             elif input[3].name == "instance_created":
                 instance = list(self.instances)[input[2]]
                 instance.addEvent(input[3])
-                instance.associations['buttons'].instances[0] = input[3].parameters[0]
+                instance.associations['fields'].instances[0] = input[3].parameters[0]
             elif input[3].name == "instance_started":
                 instance = list(self.instances)[input[2]]
                 instance.addEvent(input[3])
@@ -639,7 +632,7 @@ class Field(AtomicDEVS, ObjectManagerBase):
         next_earliest = min(self.getEarliestEventTime(), self.input_queue.getEarliestTime())
         if not (len(self.to_send) == 0):
             self.next_time = 0
-        elif next_earliest == INFINITY:
+        if next_earliest == INFINITY:
             self.next_time = INFINITY
         else:
             self.next_time = next_earliest - earliest
@@ -693,10 +686,6 @@ class ButtonInstance(RuntimeClassBase):
     def user_defined_constructor(self, window_id, event_name, button_text):
         self.window_id = window_id;
         self.event_name = event_name;
-        
-        # Translated to events:
-        #button = ui.append_button(tkparent, event_name);
-        #ui.bind_event(button.element, ui.EVENTS.MOUSE_CLICK, self.controller, 'mouse_click', self.inports['button_ui']);
     
     def user_defined_destructor(self):
         pass
@@ -786,9 +775,8 @@ class Button(AtomicDEVS, ObjectManagerBase):
             if isinstance(input, str):
                 tem = eval(input)
                 self.addInput(tem)
-            elif input[3].name == "create_instance":
-                # TODO: In generator add parameters
-                self.instances.add(ButtonInstance(self, input[3].parameters[1], input[3].parameters[2], input[3].parameters[3]))
+            if input[3].name == "create_instance":
+                self.instances.add(ButtonInstance(self))
                 ev = Event("instance_created", None, parameters=[f"{input[3].parameters[0]}[{len(self.instances)-1}]"])
                 self.to_send.append((input[1], input[0], input[2], ev))
             elif input[3].name == "start_instance":
@@ -836,7 +824,7 @@ class Button(AtomicDEVS, ObjectManagerBase):
         next_earliest = min(self.getEarliestEventTime(), self.input_queue.getEarliestTime())
         if not (len(self.to_send) == 0):
             self.next_time = 0
-        elif next_earliest == INFINITY:
+        if next_earliest == INFINITY:
             self.next_time = INFINITY
         else:
             self.next_time = next_earliest - earliest
@@ -865,7 +853,7 @@ class Button(AtomicDEVS, ObjectManagerBase):
         return self.next_time
 
 class BallInstance(RuntimeClassBase):
-    def __init__(self, atomdevs, canvas, x, y):
+    def __init__(self, atomdevs, canvas_id, x, y):
         RuntimeClassBase.__init__(self, atomdevs)
         self.associations = {}
         self.associations["parent"] = Association("Field", 1, 1)
@@ -880,17 +868,18 @@ class BallInstance(RuntimeClassBase):
         self.build_statechart_structure()
         
         # user defined attributes
-        self.canvas = None
+        self.canvas_id = None
+        self.pos = None
         
         # call user defined constructor
-        BallInstance.user_defined_constructor(self, canvas, x, y)
+        BallInstance.user_defined_constructor(self, canvas_id, x, y)
     
-    def user_defined_constructor(self, canvas, x, y):
-        self.canvas = canvas;
+    def user_defined_constructor(self, canvas_id, x, y):
+        self.canvas_id = canvas_id;
         self.r = 20.0;
         self.vel = {'x': random.uniform(-5.0, 5.0), 'y': random.uniform(-5.0, 5.0)};
-        self.mouse_pos = {};
-        self.smooth = 0.4; # value between 0 and 1
+        self.pos = {'x': x, 'y': y};
+        self.smooth = 0.6; # value between 0 and 1
         
         # TODO:
         #circle = self.canvas.add_circle(x, y, self.r, {'fill':'#000'});
@@ -900,7 +889,8 @@ class BallInstance(RuntimeClassBase):
         #self.element = circle;
     
     def user_defined_destructor(self):
-        self.canvas.remove_element(self.element);
+        #self.canvas.remove_element(self.element);
+        pass
     
     
     # builds Statechart structure
@@ -915,24 +905,29 @@ class BallInstance(RuntimeClassBase):
         # state /main_behaviour/initializing
         self.states["/main_behaviour/initializing"] = State(2, "/main_behaviour/initializing", self)
         
+        # state /main_behaviour/creating_circle
+        self.states["/main_behaviour/creating_circle"] = State(3, "/main_behaviour/creating_circle", self)
+        self.states["/main_behaviour/creating_circle"].setEnter(self._main_behaviour_creating_circle_enter)
+        
         # state /main_behaviour/bouncing
-        self.states["/main_behaviour/bouncing"] = State(3, "/main_behaviour/bouncing", self)
+        self.states["/main_behaviour/bouncing"] = State(4, "/main_behaviour/bouncing", self)
         self.states["/main_behaviour/bouncing"].setEnter(self._main_behaviour_bouncing_enter)
         self.states["/main_behaviour/bouncing"].setExit(self._main_behaviour_bouncing_exit)
         
         # state /main_behaviour/dragging
-        self.states["/main_behaviour/dragging"] = State(4, "/main_behaviour/dragging", self)
+        self.states["/main_behaviour/dragging"] = State(5, "/main_behaviour/dragging", self)
         
         # state /main_behaviour/selected
-        self.states["/main_behaviour/selected"] = State(5, "/main_behaviour/selected", self)
+        self.states["/main_behaviour/selected"] = State(6, "/main_behaviour/selected", self)
         
         # state /deleted
-        self.states["/deleted"] = State(6, "/deleted", self)
+        self.states["/deleted"] = State(7, "/deleted", self)
         
         # add children
         self.states[""].addChild(self.states["/main_behaviour"])
         self.states[""].addChild(self.states["/deleted"])
         self.states["/main_behaviour"].addChild(self.states["/main_behaviour/initializing"])
+        self.states["/main_behaviour"].addChild(self.states["/main_behaviour/creating_circle"])
         self.states["/main_behaviour"].addChild(self.states["/main_behaviour/bouncing"])
         self.states["/main_behaviour"].addChild(self.states["/main_behaviour/dragging"])
         self.states["/main_behaviour"].addChild(self.states["/main_behaviour/selected"])
@@ -941,11 +936,17 @@ class BallInstance(RuntimeClassBase):
         self.states["/main_behaviour"].default_state = self.states["/main_behaviour/initializing"]
         
         # transition /main_behaviour/initializing
-        _main_behaviour_initializing_0 = Transition(self, self.states["/main_behaviour/initializing"], [self.states["/main_behaviour/bouncing"]])
+        _main_behaviour_initializing_0 = Transition(self, self.states["/main_behaviour/initializing"], [self.states["/main_behaviour/creating_circle"]])
         _main_behaviour_initializing_0.setAction(self._main_behaviour_initializing_0_exec)
         _main_behaviour_initializing_0.setTrigger(Event("set_association_name", None))
         self.states["/main_behaviour/initializing"].addTransition(_main_behaviour_initializing_0)
         
+        # transition /main_behaviour/creating_circle
+        _main_behaviour_creating_circle_0 = Transition(self, self.states["/main_behaviour/creating_circle"], [self.states["/main_behaviour/bouncing"]])
+        _main_behaviour_creating_circle_0.setAction(self._main_behaviour_creating_circle_0_exec)
+        _main_behaviour_creating_circle_0.setTrigger(Event("circle_created", None))
+        self.states["/main_behaviour/creating_circle"].addTransition(_main_behaviour_creating_circle_0)
+        
         # transition /main_behaviour/bouncing
         _main_behaviour_bouncing_0 = Transition(self, self.states["/main_behaviour/bouncing"], [self.states["/main_behaviour/bouncing"]])
         _main_behaviour_bouncing_0.setAction(self._main_behaviour_bouncing_0_exec)
@@ -978,8 +979,11 @@ class BallInstance(RuntimeClassBase):
         _main_behaviour_selected_1.setTrigger(Event("delete_self", None))
         self.states["/main_behaviour/selected"].addTransition(_main_behaviour_selected_1)
     
+    def _main_behaviour_creating_circle_enter(self):
+        self.big_step.outputEvent(Event("create_circle", self.getOutPortName("ui"), [self.canvas_id, self.pos['x'], self.pos['y'], self.r, {'fill':'#000'}, self.inports['ball_ui']]))
+    
     def _main_behaviour_bouncing_enter(self):
-        self.addTimer(0, (20 - self.getSimulatedTime() % 20) / 1000.0)
+        self.addTimer(0, 0.02)
     
     def _main_behaviour_bouncing_exit(self):
         self.removeTimer(0)
@@ -988,19 +992,29 @@ class BallInstance(RuntimeClassBase):
         association_name = parameters[0]
         self.association_name = association_name
     
+    def _main_behaviour_creating_circle_0_exec(self, parameters):
+        canvas_id = parameters[0]
+        circle_id = parameters[1]
+        self.circle_id = circle_id
+        self.big_step.outputEvent(Event("bind_canvas_event", self.getOutPortName("ui"), [self.canvas_id, circle_id, ui.EVENTS.MOUSE_PRESS, 'mouse_press', self.inports['ball_ui']]))
+        self.big_step.outputEvent(Event("bind_canvas_event", self.getOutPortName("ui"), [self.canvas_id, circle_id, ui.EVENTS.MOUSE_MOVE, 'mouse_move', self.inports['ball_ui']]))
+        self.big_step.outputEvent(Event("bind_canvas_event", self.getOutPortName("ui"), [self.canvas_id, circle_id, ui.EVENTS.MOUSE_RELEASE, 'mouse_release', self.inports['ball_ui']]))
+    
     def _main_behaviour_bouncing_0_exec(self, parameters):
-        pos = self.element.get_position();    
-        if pos.x-self.r <= 0 or pos.x+self.r >= self.canvas.get_width():
+        # Invert velocity when colliding with canvas border:
+        if self.pos['x']-self.r <= 0 or self.pos['x']+self.r >= CANVAS_WIDTH:
             self.vel['x'] = -self.vel['x'];
-        if pos.y-self.r <= 0 or pos.y+self.r >= self.canvas.get_height():
+        if self.pos['y']-self.r <= 0 or self.pos['y']+self.r >= CANVAS_HEIGHT:
             self.vel['y'] = -self.vel['y'];
-        self.element.move(self.vel['x'], self.vel['y']);
+        self.big_step.outputEvent(Event("move_element", self.getOutPortName("ui"), [self.canvas_id, self.circle_id, self.vel['x'], self.vel['y']]))
+        self.pos['x'] += self.vel['x']
+        self.pos['y'] += self.vel['y']
     
     def _main_behaviour_bouncing_1_exec(self, parameters):
         x = parameters[0]
         y = parameters[1]
         button = parameters[2]
-        self.element.set_color("#ff0");
+        self.big_step.outputEvent(Event("set_element_color", self.getOutPortName("ui"), [self.canvas_id, self.circle_id, '#ff0']))
     
     def _main_behaviour_bouncing_1_guard(self, parameters):
         x = parameters[0]
@@ -1012,32 +1026,25 @@ class BallInstance(RuntimeClassBase):
         x = parameters[0]
         y = parameters[1]
         button = parameters[2]
-        dx = x - self.mouse_pos['x'];
-        dy = y - self.mouse_pos['y'];
-        
-        self.element.move(dx, dy);
-        
-        # keep ball within boundaries
-        pos = self.element.get_position();
-        if pos.x-self.r <= 0 :
-            pos.x = self.r + 1;
-        elif pos.x+self.r >= self.canvas.width :
-            pos.x = self.canvas.width-self.r-1;
-        if pos.y-self.r <= 0 :
-            pos.y = self.r + 1;
-        elif pos.y+self.r >= self.canvas.height :
-            pos.y = self.canvas.height-self.r-1;
-        self.element.set_position(pos.x, pos.y);
-        self.mouse_pos = {'x':x, 'y':y};
+        # Always keep ball within canvas:
+        x = min(max(0+self.r, x), CANVAS_WIDTH-self.r)
+        y = min(max(0+self.r, y), CANVAS_HEIGHT-self.r)
+        
+        dx = x - self.pos['x']
+        dy = y - self.pos['y']
+        
         self.vel = {
             'x': (1-self.smooth)*dx + self.smooth*self.vel['x'],
             'y': (1-self.smooth)*dy + self.smooth*self.vel['y']
-        };
+        }
+        
+        self.pos = {'x': x, 'y': y}
+        self.big_step.outputEvent(Event("set_element_pos", self.getOutPortName("ui"), [self.canvas_id, self.circle_id, x-self.r, y-self.r]))
     
     def _main_behaviour_dragging_1_exec(self, parameters):
         x = parameters[0]
         y = parameters[1]
-        self.element.set_color("#f00");
+        self.big_step.outputEvent(Event("set_element_color", self.getOutPortName("ui"), [self.canvas_id, self.circle_id, '#f00']))
     
     def _main_behaviour_selected_0_exec(self, parameters):
         x = parameters[0]
@@ -1053,6 +1060,7 @@ class BallInstance(RuntimeClassBase):
     
     def _main_behaviour_selected_1_exec(self, parameters):
         self.big_step.outputEventOM(Event("narrow_cast", None, [self, 'parent', Event("delete_ball", None, [self.association_name])]))
+        self.big_step.outputEvent(Event("destroy_element", self.getOutPortName("ui"), [self.canvas_id, self.element_id]))
     
     def initializeStatechart(self):
         # enter default state

Разница между файлами не показана из-за своего большого размера
+ 0 - 1114
examples/BouncingBalls/PyDEVS/tester.py


+ 63 - 54
examples/BouncingBalls/Python/target.py

@@ -12,6 +12,9 @@ from sccd.runtime.libs import ui_v2 as ui
 from sccd.runtime.libs.utils import utils
 import random
 
+CANVAS_WIDTH = 800
+CANVAS_HEIGHT = 550
+
 # package "Bouncing_Balls_Python_Version"
 
 class MainApp(RuntimeClassBase):
@@ -214,19 +217,9 @@ class Field(RuntimeClassBase):
         Field.user_defined_constructor(self)
     
     def user_defined_constructor(self):
-        # All of these TkInter calls have been converted to in/out-events:
-        #self.field_window = ui.new_window(800,600,"BouncingBalls");
-        #self.canvas = ui.append_canvas(self.field_window,800,550,{'background':'#eee'});
-        #ui.bind_event(self.field_window, ui.EVENTS.WINDOW_CLOSE, self.controller, 'window_close', self.inports['field_ui']);
-        #ui.bind_event(self.field_window, ui.EVENTS.KEY_PRESS, self.controller, 'key_press', self.inports['field_ui']);
-        #ui.bind_event(self.canvas.element, ui.EVENTS.MOUSE_RIGHT_CLICK,    self.controller, 'right_click', self.inports['field_ui']);
-        #ui.bind_event(self.canvas.element, ui.EVENTS.MOUSE_MOVE, self.controller, 'mouse_move', self.inports['field_ui']);
-        #ui.bind_event(self.canvas.element, ui.EVENTS.MOUSE_RELEASE, self.controller, 'mouse_release', self.inports['field_ui']);
-        print("created field")
         pass
     
     def user_defined_destructor(self):
-        # ui.close_window(self.field_window);
         pass
     
     
@@ -387,7 +380,7 @@ class Field(RuntimeClassBase):
         self.big_step.outputEvent(Event("create_window", self.getOutPortName("ui"), [800, 600, "BouncingBalls", self.inports['field_ui']]))
     
     def _root_creating_canvas_enter(self):
-        self.big_step.outputEvent(Event("create_canvas", self.getOutPortName("ui"), [self.window_id, 800, 550, {'background':'#eee'}, self.inports['field_ui']]))
+        self.big_step.outputEvent(Event("create_canvas", self.getOutPortName("ui"), [self.window_id, CANVAS_WIDTH, CANVAS_HEIGHT, {'background':'#eee'}, self.inports['field_ui']]))
     
     def _root_creating_button_enter(self):
         self.big_step.outputEventOM(Event("create_instance", None, [self, "buttons", "Button", self.window_id, 'create_new_field', 'Spawn New Window']))
@@ -421,7 +414,7 @@ class Field(RuntimeClassBase):
         x = parameters[0]
         y = parameters[1]
         button = parameters[2]
-        self.big_step.outputEventOM(Event("create_instance", None, [self, "balls", "Ball", self.canvas, x, y]))
+        self.big_step.outputEventOM(Event("create_instance", None, [self, "balls", "Ball", self.canvas_id, x, y]))
     
     def _root_running_main_behaviour_creating_ball_0_exec(self, parameters):
         association_name = parameters[0]
@@ -479,10 +472,6 @@ class Button(RuntimeClassBase):
     def user_defined_constructor(self, window_id, event_name, button_text):
         self.window_id = window_id;
         self.event_name = event_name;
-        
-        # Translated to events:
-        #button = ui.append_button(tkparent, event_name);
-        #ui.bind_event(button.element, ui.EVENTS.MOUSE_CLICK, self.controller, 'mouse_click', self.inports['button_ui']);
     
     def user_defined_destructor(self):
         pass
@@ -546,7 +535,7 @@ class Button(RuntimeClassBase):
         RuntimeClassBase.initializeStatechart(self)
 
 class Ball(RuntimeClassBase):
-    def __init__(self, controller, canvas, x, y):
+    def __init__(self, controller, canvas_id, x, y):
         RuntimeClassBase.__init__(self, controller)
         
         self.inports["ball_ui"] = controller.addInputPort("ball_ui", self)
@@ -561,17 +550,18 @@ class Ball(RuntimeClassBase):
         self.build_statechart_structure()
         
         # user defined attributes
-        self.canvas = None
+        self.canvas_id = None
+        self.pos = None
         
         # call user defined constructor
-        Ball.user_defined_constructor(self, canvas, x, y)
+        Ball.user_defined_constructor(self, canvas_id, x, y)
     
-    def user_defined_constructor(self, canvas, x, y):
-        self.canvas = canvas;
+    def user_defined_constructor(self, canvas_id, x, y):
+        self.canvas_id = canvas_id;
         self.r = 20.0;
         self.vel = {'x': random.uniform(-5.0, 5.0), 'y': random.uniform(-5.0, 5.0)};
-        self.mouse_pos = {};
-        self.smooth = 0.4; # value between 0 and 1
+        self.pos = {'x': x, 'y': y};
+        self.smooth = 0.6; # value between 0 and 1
         
         # TODO:
         #circle = self.canvas.add_circle(x, y, self.r, {'fill':'#000'});
@@ -581,7 +571,8 @@ class Ball(RuntimeClassBase):
         #self.element = circle;
     
     def user_defined_destructor(self):
-        self.canvas.remove_element(self.element);
+        #self.canvas.remove_element(self.element);
+        pass
     
     
     # builds Statechart structure
@@ -596,24 +587,29 @@ class Ball(RuntimeClassBase):
         # state /main_behaviour/initializing
         self.states["/main_behaviour/initializing"] = State(2, "/main_behaviour/initializing", self)
         
+        # state /main_behaviour/creating_circle
+        self.states["/main_behaviour/creating_circle"] = State(3, "/main_behaviour/creating_circle", self)
+        self.states["/main_behaviour/creating_circle"].setEnter(self._main_behaviour_creating_circle_enter)
+        
         # state /main_behaviour/bouncing
-        self.states["/main_behaviour/bouncing"] = State(3, "/main_behaviour/bouncing", self)
+        self.states["/main_behaviour/bouncing"] = State(4, "/main_behaviour/bouncing", self)
         self.states["/main_behaviour/bouncing"].setEnter(self._main_behaviour_bouncing_enter)
         self.states["/main_behaviour/bouncing"].setExit(self._main_behaviour_bouncing_exit)
         
         # state /main_behaviour/dragging
-        self.states["/main_behaviour/dragging"] = State(4, "/main_behaviour/dragging", self)
+        self.states["/main_behaviour/dragging"] = State(5, "/main_behaviour/dragging", self)
         
         # state /main_behaviour/selected
-        self.states["/main_behaviour/selected"] = State(5, "/main_behaviour/selected", self)
+        self.states["/main_behaviour/selected"] = State(6, "/main_behaviour/selected", self)
         
         # state /deleted
-        self.states["/deleted"] = State(6, "/deleted", self)
+        self.states["/deleted"] = State(7, "/deleted", self)
         
         # add children
         self.states[""].addChild(self.states["/main_behaviour"])
         self.states[""].addChild(self.states["/deleted"])
         self.states["/main_behaviour"].addChild(self.states["/main_behaviour/initializing"])
+        self.states["/main_behaviour"].addChild(self.states["/main_behaviour/creating_circle"])
         self.states["/main_behaviour"].addChild(self.states["/main_behaviour/bouncing"])
         self.states["/main_behaviour"].addChild(self.states["/main_behaviour/dragging"])
         self.states["/main_behaviour"].addChild(self.states["/main_behaviour/selected"])
@@ -622,11 +618,17 @@ class Ball(RuntimeClassBase):
         self.states["/main_behaviour"].default_state = self.states["/main_behaviour/initializing"]
         
         # transition /main_behaviour/initializing
-        _main_behaviour_initializing_0 = Transition(self, self.states["/main_behaviour/initializing"], [self.states["/main_behaviour/bouncing"]])
+        _main_behaviour_initializing_0 = Transition(self, self.states["/main_behaviour/initializing"], [self.states["/main_behaviour/creating_circle"]])
         _main_behaviour_initializing_0.setAction(self._main_behaviour_initializing_0_exec)
         _main_behaviour_initializing_0.setTrigger(Event("set_association_name", None))
         self.states["/main_behaviour/initializing"].addTransition(_main_behaviour_initializing_0)
         
+        # transition /main_behaviour/creating_circle
+        _main_behaviour_creating_circle_0 = Transition(self, self.states["/main_behaviour/creating_circle"], [self.states["/main_behaviour/bouncing"]])
+        _main_behaviour_creating_circle_0.setAction(self._main_behaviour_creating_circle_0_exec)
+        _main_behaviour_creating_circle_0.setTrigger(Event("circle_created", None))
+        self.states["/main_behaviour/creating_circle"].addTransition(_main_behaviour_creating_circle_0)
+        
         # transition /main_behaviour/bouncing
         _main_behaviour_bouncing_0 = Transition(self, self.states["/main_behaviour/bouncing"], [self.states["/main_behaviour/bouncing"]])
         _main_behaviour_bouncing_0.setAction(self._main_behaviour_bouncing_0_exec)
@@ -659,8 +661,11 @@ class Ball(RuntimeClassBase):
         _main_behaviour_selected_1.setTrigger(Event("delete_self", None))
         self.states["/main_behaviour/selected"].addTransition(_main_behaviour_selected_1)
     
+    def _main_behaviour_creating_circle_enter(self):
+        self.big_step.outputEvent(Event("create_circle", self.getOutPortName("ui"), [self.canvas_id, self.pos['x'], self.pos['y'], self.r, {'fill':'#000'}, self.inports['ball_ui']]))
+    
     def _main_behaviour_bouncing_enter(self):
-        self.addTimer(0, (20 - self.getSimulatedTime() % 20) / 1000.0)
+        self.addTimer(0, 0.02)
     
     def _main_behaviour_bouncing_exit(self):
         self.removeTimer(0)
@@ -669,19 +674,29 @@ class Ball(RuntimeClassBase):
         association_name = parameters[0]
         self.association_name = association_name
     
+    def _main_behaviour_creating_circle_0_exec(self, parameters):
+        canvas_id = parameters[0]
+        circle_id = parameters[1]
+        self.circle_id = circle_id
+        self.big_step.outputEvent(Event("bind_canvas_event", self.getOutPortName("ui"), [self.canvas_id, circle_id, ui.EVENTS.MOUSE_PRESS, 'mouse_press', self.inports['ball_ui']]))
+        self.big_step.outputEvent(Event("bind_canvas_event", self.getOutPortName("ui"), [self.canvas_id, circle_id, ui.EVENTS.MOUSE_MOVE, 'mouse_move', self.inports['ball_ui']]))
+        self.big_step.outputEvent(Event("bind_canvas_event", self.getOutPortName("ui"), [self.canvas_id, circle_id, ui.EVENTS.MOUSE_RELEASE, 'mouse_release', self.inports['ball_ui']]))
+    
     def _main_behaviour_bouncing_0_exec(self, parameters):
-        pos = self.element.get_position();    
-        if pos.x-self.r <= 0 or pos.x+self.r >= self.canvas.get_width():
+        # Invert velocity when colliding with canvas border:
+        if self.pos['x']-self.r <= 0 or self.pos['x']+self.r >= CANVAS_WIDTH:
             self.vel['x'] = -self.vel['x'];
-        if pos.y-self.r <= 0 or pos.y+self.r >= self.canvas.get_height():
+        if self.pos['y']-self.r <= 0 or self.pos['y']+self.r >= CANVAS_HEIGHT:
             self.vel['y'] = -self.vel['y'];
-        self.element.move(self.vel['x'], self.vel['y']);
+        self.big_step.outputEvent(Event("move_element", self.getOutPortName("ui"), [self.canvas_id, self.circle_id, self.vel['x'], self.vel['y']]))
+        self.pos['x'] += self.vel['x']
+        self.pos['y'] += self.vel['y']
     
     def _main_behaviour_bouncing_1_exec(self, parameters):
         x = parameters[0]
         y = parameters[1]
         button = parameters[2]
-        self.element.set_color("#ff0");
+        self.big_step.outputEvent(Event("set_element_color", self.getOutPortName("ui"), [self.canvas_id, self.circle_id, '#ff0']))
     
     def _main_behaviour_bouncing_1_guard(self, parameters):
         x = parameters[0]
@@ -693,32 +708,25 @@ class Ball(RuntimeClassBase):
         x = parameters[0]
         y = parameters[1]
         button = parameters[2]
-        dx = x - self.mouse_pos['x'];
-        dy = y - self.mouse_pos['y'];
-        
-        self.element.move(dx, dy);
-        
-        # keep ball within boundaries
-        pos = self.element.get_position();
-        if pos.x-self.r <= 0 :
-            pos.x = self.r + 1;
-        elif pos.x+self.r >= self.canvas.width :
-            pos.x = self.canvas.width-self.r-1;
-        if pos.y-self.r <= 0 :
-            pos.y = self.r + 1;
-        elif pos.y+self.r >= self.canvas.height :
-            pos.y = self.canvas.height-self.r-1;
-        self.element.set_position(pos.x, pos.y);
-        self.mouse_pos = {'x':x, 'y':y};
+        # Always keep ball within canvas:
+        x = min(max(0+self.r, x), CANVAS_WIDTH-self.r)
+        y = min(max(0+self.r, y), CANVAS_HEIGHT-self.r)
+        
+        dx = x - self.pos['x']
+        dy = y - self.pos['y']
+        
         self.vel = {
             'x': (1-self.smooth)*dx + self.smooth*self.vel['x'],
             'y': (1-self.smooth)*dy + self.smooth*self.vel['y']
-        };
+        }
+        
+        self.pos = {'x': x, 'y': y}
+        self.big_step.outputEvent(Event("set_element_pos", self.getOutPortName("ui"), [self.canvas_id, self.circle_id, x-self.r, y-self.r]))
     
     def _main_behaviour_dragging_1_exec(self, parameters):
         x = parameters[0]
         y = parameters[1]
-        self.element.set_color("#f00");
+        self.big_step.outputEvent(Event("set_element_color", self.getOutPortName("ui"), [self.canvas_id, self.circle_id, '#f00']))
     
     def _main_behaviour_selected_0_exec(self, parameters):
         x = parameters[0]
@@ -734,6 +742,7 @@ class Ball(RuntimeClassBase):
     
     def _main_behaviour_selected_1_exec(self, parameters):
         self.big_step.outputEventOM(Event("narrow_cast", None, [self, 'parent', Event("delete_ball", None, [self.association_name])]))
+        self.big_step.outputEvent(Event("destroy_element", self.getOutPortName("ui"), [self.canvas_id, self.element_id]))
     
     def initializeStatechart(self):
         # enter default state

+ 101 - 95
examples/BouncingBalls/sccd.xml

@@ -7,6 +7,9 @@
         from sccd.runtime.libs import ui_v2 as ui
         from sccd.runtime.libs.utils import utils
         import random
+
+        CANVAS_WIDTH = 800
+        CANVAS_HEIGHT = 550
     </top>
     <inport name="ui"/>
     <outport name="ui"/>
@@ -16,9 +19,7 @@
         </relationships>
         <constructor>
             <body>
-                <![CDATA[
                 self.nr_of_fields = 0
-                ]]>
             </body>
         </constructor>
         <scxml initial="running">
@@ -50,9 +51,7 @@
                                     <parameter expr='association_name' />
                                 </raise>
                                 <script>
-                                    <![CDATA[
                                     self.nr_of_fields -= 1
-                                    ]]>
                                 </script>
                             </transition>
                         </state>
@@ -66,9 +65,7 @@
                                     <parameter expr="association_name" />
                                 </raise>
                                 <script>
-                                    <![CDATA[
                                     self.nr_of_fields += 1
-                                    ]]>
                                 </script>
                             </transition>
                         </state>
@@ -98,30 +95,6 @@
             <association name="buttons" class="Button" />
             <association name="parent" class="MainApp" min="1" max="1" />
         </relationships>
-        <constructor>
-            <body>
-                <![CDATA[
-                # All of these TkInter calls have been converted to in/out-events:
-                #self.field_window = ui.new_window(800,600,"BouncingBalls");
-                #self.canvas = ui.append_canvas(self.field_window,800,550,{'background':'#eee'});
-                #ui.bind_event(self.field_window, ui.EVENTS.WINDOW_CLOSE, self.controller, 'window_close', self.inports['field_ui']);
-                #ui.bind_event(self.field_window, ui.EVENTS.KEY_PRESS, self.controller, 'key_press', self.inports['field_ui']);
-                #ui.bind_event(self.canvas.element, ui.EVENTS.MOUSE_RIGHT_CLICK,    self.controller, 'right_click', self.inports['field_ui']);
-                #ui.bind_event(self.canvas.element, ui.EVENTS.MOUSE_MOVE, self.controller, 'mouse_move', self.inports['field_ui']);
-                #ui.bind_event(self.canvas.element, ui.EVENTS.MOUSE_RELEASE, self.controller, 'mouse_release', self.inports['field_ui']);
-                print("created field")
-                pass
-                ]]>
-            </body>
-        </constructor>
-        <destructor>
-            <body>
-                <![CDATA[
-                # ui.close_window(self.field_window);
-                pass
-                ]]>
-            </body>
-        </destructor>
         <scxml initial="root">
             <state id="root" initial="waiting">
                 <state id="waiting">
@@ -168,8 +141,8 @@
                     <onentry>
                         <raise port="ui" event="create_canvas">
                             <parameter expr="self.window_id"/><!-- window_id -->
-                            <parameter expr="800"/><!-- width -->
-                            <parameter expr="550"/><!-- height -->
+                            <parameter expr="CANVAS_WIDTH"/><!-- width -->
+                            <parameter expr="CANVAS_HEIGHT"/><!-- height -->
                             <parameter expr="{'background':'#eee'}"/><!-- style -->
                             <parameter expr="self.inports['field_ui']"/><!-- inport for response -->
                         </raise>
@@ -218,11 +191,6 @@
                         </raise>
                     </transition>
                 </state>
-<!--                 <state id="packing">
-                    <transition event="button_created" target='../running'>
-                    </transition>
-                </state>
- -->
                  <parallel id="running">
                     <transition port="field_ui" event="window_close" target="../deleting">
                         <raise event="delete_instance" scope="cd">
@@ -241,7 +209,7 @@
                                 <raise scope="cd" event="create_instance">
                                     <parameter expr='"balls"' />
                                     <parameter expr='"Ball"' />
-                                    <parameter expr="self.canvas" />
+                                    <parameter expr="self.canvas_id" />
                                     <parameter expr="x" />
                                     <parameter expr="y" />
                                 </raise>
@@ -316,14 +284,8 @@
             <parameter name="event_name" type="str" />
             <parameter name="button_text" type="str" />
             <body>
-                <![CDATA[
                 self.window_id = window_id;
                 self.event_name = event_name;
-
-                # Translated to events:
-                #button = ui.append_button(tkparent, event_name);
-                #ui.bind_event(button.element, ui.EVENTS.MOUSE_CLICK, self.controller, 'mouse_click', self.inports['button_ui']);
-                ]]>
             </body>
         </constructor>
         <scxml initial="creating_button">
@@ -338,9 +300,7 @@
                 <transition event="button_created" target="../running">
                     <parameter name="button_id" type="int"/>
                     <script>
-                        <![CDATA[
                         self.button_id = button_id
-                        ]]>
                     </script>
                     <raise port="ui" event="bind_event">
                         <parameter expr="button_id"/><!-- widget_id -->
@@ -350,13 +310,6 @@
                     </raise>
                 </transition>
             </state>
-<!--             <state id="initializing">
-                <transition target="../running">
-                    <raise event="button_created" scope="narrow" target="'parent'">
-                    </raise>
-                </transition>
-            </state>
- -->
              <state id="running">
                 <transition port='button_ui' event="mouse_click" target='.' cond="button == ui.MOUSE_BUTTONS.LEFT">
                     <parameter name="x" />
@@ -371,23 +324,24 @@
     </class>
     
     <class name="Ball">
-        <atrribute name="element" />
-        <attribute name="canvas" />
+        <attribute name="canvas_id" />
+        <atrribute name="circle_id" />
+        <attribute name="pos" />
         <inport name="ball_ui" />
         <relationships>
             <association name="parent" class="Field" min="1" max="1" />
         </relationships>
         <constructor>
-            <parameter name="canvas" />
+            <parameter name="canvas_id" />
             <parameter name="x" />
             <parameter name="y" />
             <body>
                 <![CDATA[
-                self.canvas = canvas;
+                self.canvas_id = canvas_id;
                 self.r = 20.0;
                 self.vel = {'x': random.uniform(-5.0, 5.0), 'y': random.uniform(-5.0, 5.0)};
-                self.mouse_pos = {};
-                self.smooth = 0.4; # value between 0 and 1
+                self.pos = {'x': x, 'y': y};
+                self.smooth = 0.6; # value between 0 and 1
 
                 # TODO:
                 #circle = self.canvas.add_circle(x, y, self.r, {'fill':'#000'});
@@ -401,44 +355,94 @@
         <destructor>
             <body>
                 <![CDATA[
-                self.canvas.remove_element(self.element);
+                #self.canvas.remove_element(self.element);
+                pass
                 ]]>
             </body>
         </destructor>
         <scxml initial="main_behaviour">
             <state id="main_behaviour" initial="initializing">
                 <state id="initializing">
-                    <transition event="set_association_name" target="../bouncing">
+                    <transition event="set_association_name" target="../creating_circle">
                         <parameter name="association_name" type="str" />
                         <script>
-                            <![CDATA[
                             self.association_name = association_name
-                            ]]>                            
                         </script>
                     </transition>
                 </state>
+                <state id="creating_circle">
+                    <onentry>
+                        <raise port="ui" event="create_circle">
+                            canvas_id, x, y, r, style, res_port
+                            <parameter expr="self.canvas_id"/><!-- canvas_id -->
+                            <parameter expr="self.pos['x']"/><!-- x -->
+                            <parameter expr="self.pos['y']"/><!-- y -->
+                            <parameter expr="self.r"/><!-- r -->
+                            <parameter expr="{'fill':'#000'}"/><!-- style -->
+                            <parameter expr="self.inports['ball_ui']"/><!-- inport for response -->
+                        </raise>
+                    </onentry>
+                    <transition event="circle_created" target="../bouncing">
+                        <parameter name="canvas_id"/>
+                        <parameter name="circle_id"/>
+                        <script>
+                            self.circle_id = circle_id
+                        </script>
+                        <raise port="ui" event="bind_canvas_event">
+                            <parameter expr="self.canvas_id"/>
+                            <parameter expr="circle_id"/>
+                            <parameter expr="ui.EVENTS.MOUSE_PRESS"/>
+                            <parameter expr="'mouse_press'"/>
+                            <parameter expr="self.inports['ball_ui']"/>
+                        </raise>
+                        <raise port="ui" event="bind_canvas_event">
+                            <parameter expr="self.canvas_id"/>
+                            <parameter expr="circle_id"/>
+                            <parameter expr="ui.EVENTS.MOUSE_MOVE"/>
+                            <parameter expr="'mouse_move'"/>
+                            <parameter expr="self.inports['ball_ui']"/>
+                        </raise>
+                        <raise port="ui" event="bind_canvas_event">
+                            <parameter expr="self.canvas_id"/>
+                            <parameter expr="circle_id"/>
+                            <parameter expr="ui.EVENTS.MOUSE_RELEASE"/>
+                            <parameter expr="'mouse_release'"/>
+                            <parameter expr="self.inports['ball_ui']"/>
+                        </raise>
+                    </transition>
+                </state>
                 <state id="bouncing">
-                    <transition after="(20 - self.getSimulatedTime() % 20) / 1000.0" target=".">
+                    <!-- <transition after="(20 - self.getSimulatedTime() % 20) / 1000.0" target="."> -->
+                    <transition after="0.02" target=".">
                         <script>
                             <![CDATA[
-                            pos = self.element.get_position();    
-                            if pos.x-self.r <= 0 or pos.x+self.r >= self.canvas.get_width():
+                            # Invert velocity when colliding with canvas border:
+                            if self.pos['x']-self.r <= 0 or self.pos['x']+self.r >= CANVAS_WIDTH:
                                 self.vel['x'] = -self.vel['x'];
-                            if pos.y-self.r <= 0 or pos.y+self.r >= self.canvas.get_height():
+                            if self.pos['y']-self.r <= 0 or self.pos['y']+self.r >= CANVAS_HEIGHT:
                                 self.vel['y'] = -self.vel['y'];
-                            self.element.move(self.vel['x'], self.vel['y']);
-                            ]]>                            
+                            ]]>
+                        </script>
+                        <raise port="ui" event="move_element">
+                            <parameter expr="self.canvas_id"/>
+                            <parameter expr="self.circle_id"/>
+                            <parameter expr="self.vel['x']"/>
+                            <parameter expr="self.vel['y']"/>
+                        </raise>
+                        <script>
+                            self.pos['x'] += self.vel['x']
+                            self.pos['y'] += self.vel['y']
                         </script>
                     </transition>
                     <transition port="ball_ui" event="mouse_press" target="../selected" cond="button == ui.MOUSE_BUTTONS.LEFT">
                         <parameter name="x" />
                         <parameter name="y" />
                         <parameter name="button" />
-                        <script>
-                            <![CDATA[
-                            self.element.set_color("#ff0");
-                            ]]>                            
-                        </script>
+                        <raise port="ui" event="set_element_color">
+                            <parameter expr="self.canvas_id"/>
+                            <parameter expr="self.circle_id"/>
+                            <parameter expr="'#ff0'"/>
+                        </raise>
                     </transition>
                 </state>
                 <state id="dragging">
@@ -448,38 +452,36 @@
                         <parameter name="button" />
                         <script>
                             <![CDATA[
-                            dx = x - self.mouse_pos['x'];
-                            dy = y - self.mouse_pos['y'];
+                            # Always keep ball within canvas:
+                            x = min(max(0+self.r, x), CANVAS_WIDTH-self.r)
+                            y = min(max(0+self.r, y), CANVAS_HEIGHT-self.r)
 
-                            self.element.move(dx, dy);
+                            dx = x - self.pos['x']
+                            dy = y - self.pos['y']
 
-                            # keep ball within boundaries
-                            pos = self.element.get_position();
-                            if pos.x-self.r <= 0 :
-                                pos.x = self.r + 1;
-                            elif pos.x+self.r >= self.canvas.width :
-                                pos.x = self.canvas.width-self.r-1;
-                            if pos.y-self.r <= 0 :
-                                pos.y = self.r + 1;
-                            elif pos.y+self.r >= self.canvas.height :
-                                pos.y = self.canvas.height-self.r-1;
-                            self.element.set_position(pos.x, pos.y);
-                            self.mouse_pos = {'x':x, 'y':y};
                             self.vel = {
                                 'x': (1-self.smooth)*dx + self.smooth*self.vel['x'],
                                 'y': (1-self.smooth)*dy + self.smooth*self.vel['y']
-                            };
+                            }
+
+                            self.pos = {'x': x, 'y': y}
                             ]]>
                         </script>
+                        <raise port="ui" event="set_element_pos">
+                            <parameter expr="self.canvas_id"/>
+                            <parameter expr="self.circle_id"/>
+                            <parameter expr="x-self.r"/>
+                            <parameter expr="y-self.r"/>
+                        </raise>
                     </transition>
                     <transition port="ball_ui" event="mouse_release" target="../bouncing">
                         <parameter name="x" />
                         <parameter name="y" />
-                        <script>
-                            <![CDATA[
-                            self.element.set_color("#f00");
-                            ]]>                            
-                        </script>
+                        <raise port="ui" event="set_element_color">
+                            <parameter expr="self.canvas_id"/>
+                            <parameter expr="self.circle_id"/>
+                            <parameter expr="'#f00'"/>
+                        </raise>
                     </transition>
                 </state>
                 <state id='selected'>
@@ -497,6 +499,10 @@
                         <raise event="delete_ball" scope="narrow" target="'parent'">
                             <parameter expr='self.association_name' />
                         </raise>
+                        <raise port="ui" event="destroy_element">
+                            <parameter expr="self.canvas_id" />
+                            <parameter expr="self.element_id" />
+                        </raise>
                     </transition>
                 </state>
             </state>

+ 53 - 30
sccd/runtime/DEVS_statecharts_core.py

@@ -758,7 +758,13 @@ class ObjectManagerBase(object):
             input_port = e.getPort()
 
             #target_instance = input_port.instance
-
+            #TODO: get the first field, should be dynamically
+            #temp = None
+            #try:
+            #    temp = self.processAssociationReference(e.parameters[0])[0]
+            #    temp = temp[1]
+            #except:
+            #    temp = 0
             target_instance = list(self.instances)[0]
             #target_instance = self.State[0]
             if target_instance == None:
@@ -784,6 +790,24 @@ class ObjectManagerBase(object):
                 # TODO; changed this from self.accurate_time.get_wct() to self.simulated_time
                 self.input_queue.add((0 if self.simulated_time is None else 0) + time_offset, e)
 
+    def addInputPort(self, virtual_name, instance = None):
+        if instance == None:
+            port_name = virtual_name
+        else:
+            port_name = "private_" + str(self.private_port_counter) + "_" + virtual_name
+            self.private_port_counter += 1
+        self.input_ports[port_name] = InputPortEntry(virtual_name, instance)
+        return port_name
+        
+    def addOutputPort(self, virtual_name, instance = None):
+        if instance == None:
+            port_name = virtual_name
+        else:
+            port_name = "private_" + str(self.private_port_counter) + "_" + virtual_name
+            self.private_port_counter += 1
+        self.output_ports[port_name] = OutputPortEntry(port_name, virtual_name, instance)
+        return port_name
+
 
     def handleEvent(self, e):
         self.handlers[e.getName()](e.getParameters())
@@ -820,7 +844,7 @@ class ObjectManagerBase(object):
             for i in self.getInstances(source, traversal_list):
                 #i["instance"].start()
                 # TODO: start instance over a link from mainapp to field
-                self.to_send.append((i['assoc_name'], "Field", 0, Event("start_instance", None, None)))
+                self.to_send.append((i['assoc_name'], i['to_class'], 0, Event("start_instance", None, [i['instance']])))
 
 
 
@@ -848,20 +872,15 @@ class ObjectManagerBase(object):
             class_name = association.to_class if len(parameters) == 2 else parameters[2]
             #new_instance = self.createInstance(class_name, parameters[3:])
 
-            id = None
-            for index, i in enumerate(self.instances):
-                if i == source:
-                    id = index
-                    break
-
-            # Normally [3:] but instance cannot be past along, need to fix this
-            #self.to_send.append((self.name, class_name, id, Event("create_instance", None, parameters[4:])))
-                
+            #id = None
+            #for index, i in enumerate(self.instances):
+            #    if i == source:
+            #        id = index
+            #        break
 
             hulp = [association_name]
             hulp.extend(parameters[3:])
-            self.to_send.append((self.name, class_name, id, Event('create_instance', None, hulp)))
-            #self.to_send.append((None, class_name, id, Event('create_instance', None, hulp)))
+            self.to_send.append((self.name, class_name, 0, Event('create_instance', None, hulp)))
 
             #if not new_instance:
             #    raise ParameterException("Creating instance: no such class: " + class_name)
@@ -897,23 +916,24 @@ class ObjectManagerBase(object):
             association = source.associations[traversal_list[0][0]]
             
             for i in instances:
-                try:
-                    for assoc_name in i["instance"].associations:
-                        if assoc_name != 'parent':
-                            traversal_list = self.processAssociationReference(assoc_name)
-                            instances = self.getInstances(i["instance"], traversal_list)
-                            if len(instances) > 0:
-                                raise RuntimeException("Error removing instance from association %s, still %i children left connected with association %s" % (association_name, len(instances), assoc_name))
-                    del i["instance"].controller.input_ports[i["instance"].narrow_cast_port]
-                    association.removeInstance(i["instance"])
-                    self.instances.discard(i["instance"])
-                    self.eventless.discard(i["instance"])
-                except AssociationException as exception:
-                    raise RuntimeException("Error removing instance from association '" + association_name + "': " + str(exception))
-                i["instance"].user_defined_destructor()
-                i["instance"].stop()
+                self.to_send.append((i['assoc_name'], i['to_class'], 0, Event("delete_instance", None, None)))
+                #try:
+                    #for assoc_name in i["instance"].associations:
+                    #    if assoc_name != 'parent':
+                    #        traversal_list = self.processAssociationReference(assoc_name)
+                    #        instances = self.getInstances(i["instance"], traversal_list)
+                    #        if len(instances) > 0:
+                    #            raise RuntimeException("Error removing instance from association %s, still %i children left connected with association %s" % (association_name, len(instances), assoc_name))
+                    #del i["instance"].controller.input_ports[i["instance"].narrow_cast_port]
+                    #association.removeInstance(i["instance"])
+                    #self.instances.discard(i["instance"])
+                    #self.eventless.discard(i["instance"])
+                #except AssociationException as exception:
+                #    raise RuntimeException("Error removing instance from association '" + association_name + "': " + str(exception))
+                #i["instance"].user_defined_destructor()
+                #i["instance"].stop()
                 
-            source.addEvent(Event("instance_deleted", parameters = [parameters[1]]))
+            #source.addEvent(Event("instance_deleted", parameters = [parameters[1]]))
                 
     def handleAssociateEvent(self, parameters):
         if len(parameters) != 3:
@@ -979,13 +999,14 @@ class ObjectManagerBase(object):
             for i in self.getInstances(source, traversal_list):
                 # TODO: port cannot be none but don't know yet how to do port 
                 ev = Event(cast_event.name, None, cast_event.parameters)
-                self.to_send.append((i["assoc_name"], "Field", i["assoc_index"], ev))
+                self.to_send.append((i["assoc_name"], i['to_class'], i["assoc_index"], ev))
 
                 #to_send_event = Event(cast_event.name, i["instance"].narrow_cast_port, cast_event.parameters)
                 #i["instance"].controller.addInput(to_send_event, force_internal=True)
         
     def getInstances(self, source, traversal_list):
         currents = [{
+            "to_class": None,
             "instance": source,
             "ref": None,
             "assoc_name": None,
@@ -1001,6 +1022,7 @@ class ObjectManagerBase(object):
                     try:
                         # TODO: instance in nexts was the object but now a reference, can introduce bugs
                         nexts.append({
+                            "to_class": association.to_class,
                             "instance": association.instances[index],
                             "ref": current["instance"],
                             "assoc_name": name,
@@ -1013,6 +1035,7 @@ class ObjectManagerBase(object):
                 elif (index == -1):
                     for i in association.instances:
                         nexts.append({
+                            "to_class": association.to_class,
                             "instance": association.instances[i],
                             "ref": current["instance"],
                             "assoc_name": name,

+ 16 - 6
sccd/runtime/libs/DEVui_v2.py

@@ -63,7 +63,9 @@ class UI:
             button = tk.Button(window, text=text)
             button.pack(fill=tk.BOTH, expand=1)
             button_id = self._gen_id(button)
-            self.controller.addInput(Event("button_created", res_port, [button_id]))
+
+            #self.controller.addInput(Event("button_created", res_port, [button_id]))
+            self.controller.realtime_interrupt(f"{res_port} Event(\"button_created\",\"{res_port}\",[{button_id}])")
         # schedule in mainloop:
         self.tk.after(0, callback)
 
@@ -76,7 +78,7 @@ class UI:
             canvas_id = self._gen_id(canvas)
             
             #self.controller.addInput(Event("canvas_created", res_port, [canvas_id]))
-            self.controller.realtime_interrupt(f"{res_port} Event(\"canvas_created\",self.{res_port},[{canvas_id}])")
+            self.controller.realtime_interrupt(f"{res_port} Event(\"canvas_created\",\"{res_port}\",[{canvas_id}])")
         # schedule in mainloop:
         self.tk.after(0, callback)
 
@@ -89,7 +91,7 @@ class UI:
             #self.controller.addInput(Event("window_created", res_port, [window_id]))
 
             #source = f"{res_port} Event(\"window_created\", {res_port},[{window_id}])"
-            self.controller.realtime_interrupt(f"{res_port} Event(\"window_created\",self.{res_port},[{window_id}])")
+            self.controller.realtime_interrupt(f"{res_port} Event(\"window_created\",\"{res_port}\",[{window_id}])")
         # schedule in mainloop:
         self.tk.after(0, callback)
 
@@ -111,7 +113,8 @@ class UI:
         def callback():
             canvas = self.mapping[canvas_id]
             circle_id = canvas.create_oval(x-r, y-r, x+r, y+r, **style)
-            self.controller.addInput(Event("circle_created", res_port, [canvas_id, circle_id]))
+            #self.controller.addInput(Event("circle_created", res_port, [canvas_id, circle_id]))
+            self.controller.realtime_interrupt(f"{res_port} Event(\"circle_created\",\"{res_port}\",[{canvas_id},{circle_id}])")
         # schedule in mainloop:
         self.tk.after(0, callback)
 
@@ -163,10 +166,17 @@ class UI:
              event == EVENTS.MOUSE_RELEASE or \
              event == EVENTS.MOUSE_RIGHT_CLICK :
             #self.controller.addInput(Event(raise_name, port, [ev.x, ev.y, ev.num]))
-            self.controller.realtime_interrupt(f"{port} Event(\"{raise_name}\",self.{port},[{ev.x},{ev.y},\"{ev.num}\"])")
+
+            match ev.num:
+                case int():
+                    hel = f"{port} Event(\"{raise_name}\",\"{port}\",[{ev.x},{ev.y},{ev.num}])"
+                case _:
+                    hel = f"{port} Event(\"{raise_name}\",\"{port}\",[{ev.x},{ev.y},\"{ev.num}\"])"
+                    
+            self.controller.realtime_interrupt(hel)
         elif event == EVENTS.WINDOW_CLOSE :
             #self.controller.addInput(Event(raise_name, port, []))
-            self.controller.realtime_interrupt(f"{port} Event(\"{raise_name}\",self.{port},[])")
+            self.controller.realtime_interrupt(f"{port} Event(\"{raise_name}\",\"{port}\",[])")
             
         else:
             raise Exception('Unsupported event: ' + str(event))