Observer: How is better?

There are several instances of class (items) that are crated and managed by a Tool class instance (manager). Every item must react to the view being changed.

Which is better?
A) Add an instance of the observer to each item?
B) Add an instance of the observer to the Tool instance and “inform” items about the change?

In the examples, for simplicity, I create 100 items during initialization, and the reaction of the items is a simple count.

Case A:

module Dezmo
  class MyItemA
    def initialize
      @my_variable = 0
      start_observing
    end
    
    def onViewChanged(view)
      @my_variable += 1
    end
    
    def start_observing
      model = Sketchup.active_model
      model.active_view.remove_observer(self)
      model.active_view.add_observer(self)
    end
    
    def stop_observing 
      model = Sketchup.active_model
      model.active_view.remove_observer(self)
    end
  end
  
  class MyItemManagerToolA
    def initialize
      @my_items = {}
      100.times{|i|
         @my_items[i] = Dezmo::MyItemA.new
      }
    end
    
    def deactivate(view)
      @my_items.each{ |item| item.stop_observing }
    end
    
    def manage_items_method_1
       # ...and so on
    end
  end
end

Case B

module Dezmo
  class MyItemB  
    def initialize
      @my_variable = 0
    end
    
    def view_changed(view)
       @my_variable += 1
    end
  end
  
  class MyItemManagerToolB
    def initialize
      @my_items = {}
      100.times{|i|
         @my_items[i] = Dezmo::MyItemB.new
      }
      start_observing
    end
    
    def deactivate(view)
      stop_observing
    end
    
    def onViewChanged(view)
      @my_items.each{ |item| item.view_changed(view) }
    end
    
    def start_observing
      model = Sketchup.active_model
      model.active_view.remove_observer(self)
      model.active_view.add_observer(self)
    end
    
    def stop_observing 
      model = Sketchup.active_model
      model.active_view.remove_observer(self)
    end
    
    def manage_items_method_1
       # ...and so on
    end
  end
end

Other quiestions:
If I replace e.g. @my_items[50] = Dezmo::MyItemA.new
What happens to the item that was previously assigned to @my_items[50]?
What happens to its observer if it was assigned to it and I did not remove it?
Does the garbage collection mechanism remove them?

1 Like

It becomes unreferenced and will be garbage collected the next time GC runs.

2 Likes

I think that the SketchUp observer queue disposes of observers attached to invalid objects. The observer queue will be holding a reference to MyItemA which might prevent immediate garbage collection when your code stops referencing it, so the stop_observing() method has benefit.


Of the two scenarios, I’d prefer B. But I also would likely key the hash with object IDs or PIDs if they are Drawingelement subclass objects. You can also key the hash with object references as well which are also unique.

If the things to be watched are model objects, you could just insert @vars into the entity instances using the instance_variable_set() method.

1 Like

I also like better B (less observers).

No. There are a lot more to do inside in one item. I just put an example that need a calculation if view changes.
These are not a Drawingelement, but It is a holding a calculated points of let say, shapes, however I also can call it as my own virtual Drawingelement, because it will drawn by a Tool (or Overlay).
Some have the property that they always seem to be the same size (like the protractor), so I have to watch when the view changes and calculate a new transformation.

1 Like

If you are inside a tool I would use draw(view) to track changes. That would get rid of the observer altogether.

1 Like

The thing is, there’s quite a lot of calculation (can be) involved, so I’d rather calculate it in advance and have # draw(…) just draw the *points (cached in instance variable).

I have the feeling (but I have no idea) that view.draw(...) is called more often than the view observer changes (?)

1 Like

I woul use B, that is, an observer attached to the tool, created at `activate` and removed at `deactivate`.

Another option is to store the view parameters (eye, target, etc…) in `draw` and compare them each time. If they are different, then you perform the view-dependent calculation. Otherwise, you used the cached value. draw is called anyway when the view changes.

2 Likes

I wrote a test code to compare the number of calls in draw vs. observer, Tool vs. Overlay.

  1. animation: I change the view with the mouse wheel or the middle mouse button (Orbit Tool).
    In Overlay, the draw method is called more times than the observer. If in Tool - obviously when using the Orbit Tool, MyTool is suspended - it does not call draw.

  2. animation: I change the view with 3Dconnection mouse.
    There is no really significant difference in the number of draw or observer calls.

It seems that in Tool it is better to calculate in the draw method than in the Observer.
With Overlay it is almost the same.

Is there something I did not take into account?

module Dezmo
  class MyOv < Sketchup::Overlay
    def initialize
      super('example_inc.my_ov', 'TestOv')
    end
    def onViewChanged(view)
      @viewobs_count += 1
    end
    def start
      @draw_count = 0
      @viewobs_count = 0
      model = Sketchup.active_model
      model.active_view.remove_observer(self)
      model.active_view.add_observer(self)
    end
    def stop
      model = Sketchup.active_model
      model.active_view.remove_observer(self)
    end
    def draw(view)
       @draw_count += 1
       view.draw_text([10,60], 
                      "Odraw_count:#{@draw_count}\nOviewobs_count:#{@viewobs_count}",
                      size:16,
                      bold:true,
                      color:"red")
    end
  end
  ov = MyOv.new
  Sketchup.active_model.overlays.add(ov)

  class MyTool
    def initialize
      @draw_count = 0
      @viewobs_count = 0
      start_observing
    end
    def deactivate(view)
      stop_observing
    end
    def resume(view)
      view.invalidate
    end
    def onViewChanged(view)
      @viewobs_count += 1
    end
    def start_observing
      model = Sketchup.active_model
      model.active_view.remove_observer(self)
      model.active_view.add_observer(self)
    end

    def stop_observing 
      model = Sketchup.active_model
      model.active_view.remove_observer(self)
    end

    def draw(view)
       @draw_count += 1
       view.draw_text([10,10], 
                      "draw_count:#{@draw_count}\nviewobs_count:#{@viewobs_count}",
                      size:16,
                      bold:true)
    end
  end
  Sketchup.active_model.select_tool(MyTool.new)
end

ovvsdraw

ovvsdraw3Dcon

1 Like