• Login
sketchucation logo sketchucation
  • Login
⚠️ Attention | Having issues with Sketchucation Tools 5? Report Here

Removing entity observers

Scheduled Pinned Locked Moved Developers' Forum
7 Posts 2 Posters 1.2k Views 2 Watching
Loading More Posts
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • D Offline
    dmac
    last edited by 24 Aug 2010, 16:04

    I add entity observers to each face, and later remove them, but this causes a slowdown each time I do it. An example demonstrates this (run on a file with 1000 cubes):

    require 'sketchup'
    
    def timeOperation
      startTime = Time.now
    
      yield
    
      time = Time.now - startTime
      puts "Completed in; #{time.to_s}"
    end
    
    def addObservers
      Sketchup.active_model.entities.each { |e|
        if not e.instance_of? Sketchup;;Face
          o = Sketchup;;EntityObserver
    
          e.add_observer(o)
          e.remove_observer(o) # Returns true
        end
      }
    end
    
    def removeMaterials
      timeOperation {
        Sketchup.active_model.entities.each { |e|
          if e.instance_of? Sketchup;;Face
            e.material = nil
            e.back_material = nil
          end
        }
      }
    end
    

    Running the methods in the Ruby console:

    
    > removeMaterials
    Completed in; 0.172
    
    > addObservers
    > removeMaterials
    Completed in; 0.78
    
    > addObservers
    > addObservers
    > addObservers
    > addObservers
    
    > removeMaterials
    Completed in; 3.23
    
    

    Each time addObservers is called, the time that removeMaterials takes increases. Shouldn't addObservers be a no-op? An observer is added and then removed, resulting in no observers. Also, this persists after a File->Open.

    1 Reply Last reply Reply Quote 0
    • D Offline
      Dan Rathbun
      last edited by 24 Aug 2010, 19:24

      (1) You are supposed to subclass the Sketchup "Observer" superclasses, so that there is some ruby code within the callback methods, that executes when they get called.
      Ie, you do not create direct instances of Sketchup::EntityObserver or any other Sketchup Observer superclass.

      (2) In an iterative block such as: Sketchup.active_model.entities.each you are actually iterating the C++ collection entities. Do not make changes when you do this, only get information. If you need to make changes, you must use an Array copy of the entities, like so:
      Sketchup.active_model.entities**.to_a**.each
      Otherwise the C++ side of Sketchup will shuffle the elements of the collection around on you while you are iterating it, and you either iterate the same elements multiple times, or miss elements entirely.

      (3) Your code: if not e.instance_of? Sketchup::Face would add observers to ALL objects except faces. You'd need to remove the negation keyword "not."

      (4) Your code is incorrect.. it does not actually add observers. quote:
      o = Sketchup::EntityObserver only creates an alias that points at the Sketchup::EntityObserver class definition. It does not point at an observer instance. In Ruby, you use instances of class objects.

      (5) Some developers use a single custom observer instance, to watch multiple objects. It may not be efficient memory wise, to create an individual observer instance, for each and every Sketchup::Drawingelement subclass object. (Not to say that there may be situations where it must be done.)

      (6) In your method addObservers, (provided that you correct the code to actually attach an real observer instance,) it would do no good to attach an observer, and then immediately, remove it in the next statement.

      (7) Your statement at the top: require 'sketchup' is not needed unless you are calling a method in that file, or accessing a data structure defined in that file, and you wish to be sure that these things pe-exist before you use them.
      It does not hurt, however, as Ruby will just check the $LOADED_FEATURES array and if it's been loaded, Ruby will do nothing, and go to the next statement.

      (8) Most important... Your code is not wrappped in a namespace (a module.) When run, it is evaluated within Object, the ancestor of ALL objects. Your methods will get inherited by EVERY object, as a private method. This wastes memory. Use a module name you can remember, like: Dmactest for playing around.

      
      module Dmactest
        class << self
          # paste your methods here
        end # self
      end
      
      

      (9) For large operations, you should have the UI stop, by using the model instance methods:
      model.start_operation
      and
      model.commit_operation
      See also:
      model.abort_operation

      I'm not here much anymore.

      1 Reply Last reply Reply Quote 0
      • D Offline
        Dan Rathbun
        last edited by 24 Aug 2010, 19:34

        (10) And... with your method, if you use a local reference o, to refer to your observer instances, they (the references, not the observer instances,) will get disposed of when the method ends. Then you will not be able to reference them later (when you wish to remove them.)

        You need to have the reference stored outside the method, as a @@ module reference, which can be a Hash, or an Array, if you need to hold references to multiple objects (either many observer instances, or many object references that one observer is attached to.) Ex:
        @@watched_faces=[]

        I'm not here much anymore.

        1 Reply Last reply Reply Quote 0
        • D Offline
          dmac
          last edited by 25 Aug 2010, 15:06

          Thanks for the reply. I've updated the code but seeing the same results. I want to be able to remove observers, especially when a new model is opened. It does look like they're removed because the observer methods aren't called, but there is a slowdown each time they are added/removed which I don't get.

          1. I did use a subclass of EntityObserver but removed it when creating the test. I've readded a trivial observer.

          2. Ok. What causes this? Is it just calling methods on entities, or adding/removing entities? or any mutable operation on the model?

          3. I wanted to show that it doesn't matter what entities the observers are added to, the effect is the same.

          4. Yes, I meant to create an instance.

          5. That sounds better but I can't seem to get removeObservers to work with it. When I tried using a single instance with add_observer/remove_observer (@@observer in the below code), the puts output looked correct, but only a single observer was removed. remove_observer returns false, and I see the observer output when I paint materials.

          6, 10) I did this to simplify the example. It's the same when they are added and removed at separate times.

          1. Ok.

          2. Ok, I already use modules for larger scripts. What memory is used though? I thought each method used a constant amount of memory, no matter how many instances or subclasses there are.

          3. Ok.

          The results now are:

          removeMaterials
          (Completes in 0.05)

          addObservers
          (Observers are added. Painting a surface prints observer output.)

          removeObservers
          (Observers are removed. remove_observer returns true. Painting a surface gives no output.)

          removeMaterials
          (Completes in 0.4)

          removeMaterials takes more time after calling addObservers/removeObservers again. What is taking the time if the observers aren't active?

          
          module Dmactest
            class MyEntityObserver < Sketchup;;EntityObserver
              def onEraseEntity(e)
                puts "Erase Entity; #{e}"
              end
          
              def onChangeEntity(e)
                puts "Change Entity; #{e}"
              end
            end
          
            class << self
              @@watchedFaces = []
              @@observer = MyEntityObserver.new
          
              def timeOperation
                startTime = Time.now
          
                yield
          
                time = Time.now - startTime
                puts "Completed in; #{time.to_s}"
              end
          
              def addObservers
                model = Sketchup.active_model
          
                removeObservers
          
                @@watchedFaces = model.entities.select { |e| e.instance_of? Sketchup;;Face }
                @@watchedFaces.map! { |f| [f, MyEntityObserver.new] }
          
                @@watchedFaces.each { |f, o|
          #        puts "Adding Observer; #{f} #{@@observer}"
          #        f.add_observer @@observer
          
                  puts "Adding Observer; #{f} #{o}"
                  f.add_observer o
                }
          
                return nil
              end
          
              def removeObservers
                model = Sketchup.active_model
          
                @@watchedFaces.each { |f, o|
          #        puts "Removing Observer; #{f} #{@@observer}"
          #        puts "Result; #{f.remove_observer @@observer}"
          
                  puts "Removing Observer; #{f} #{o}"
                  f.remove_observer o
                }
                @@watchedFaces = []
          
                return nil
              end
          
              def removeMaterials
                model = Sketchup.active_model
          
                timeOperation {
                  model.start_operation 'removeMaterials', true
          
                  Sketchup.active_model.entities.to_a.each { |e|
                    if e.instance_of? Sketchup;;Face
                      e.material = nil
                      e.back_material = nil
                    end
                  }
          
                  model.commit_operation
                }
              end
            end
          end
          
          
          1 Reply Last reply Reply Quote 0
          • D Offline
            Dan Rathbun
            last edited by 25 Aug 2010, 21:03

            @dmac said:

            1. Ok. What causes this? Is it just calling methods on entities, or adding/removing entities? or any mutable operation on the model?

            Only changes to objects (entities,) in the model. You can call query methods directly that don't change the entities, such as getting a material, or it's visible state.

            @dmac said:

            1. Ok, I already use modules for larger scripts. What memory is used though? I thought each method used a constant amount of memory, no matter how many instances or subclasses there are.

            The method itself, yes... but each instance has to hold a pointer to the method in the superclass. Using a module prevents ALL objects from inheriting pointers to methods that they will never use.

            When you define global methods that should not be global, you invade other developer's namespaces (their modules and classes,) as well as all of Ruby's standard modules and base classes.

            I'm not here much anymore.

            1 Reply Last reply Reply Quote 0
            • D Offline
              Dan Rathbun
              last edited by 25 Aug 2010, 21:07

              Some of the Observers are bugged:

              Review this topic: State of Observers

              I'm not here much anymore.

              1 Reply Last reply Reply Quote 0
              • D Offline
                Dan Rathbun
                last edited by 25 Aug 2010, 21:25

                @dmac said:

                removeMaterials takes more time after calling addObservers/removeObservers again. What is taking the time if the observers aren't active?

                Some of us are suspicous about the remove_observer()
                (I recall some other topics, here at SCF where other people reported slow downs as the number of model entities increased. Do a search on this forum.)

                When you are done with an observer instance, set it to nil and run Garbage Collection.
                GC.start

                Also:
                @@watchedFaces.each { |f, o|
                needs to be:
                @@watchedFaces.each_with_index { |f, o|
                if you are using an Array...

                ... but I think you want to use a Hash instead, where the keys are the object_id of the face objects, and the Hash values are references to the observer instance that is attached to it.

                @@watchedFaces = Hash.new model.entities.to_a.each { |e| (@@watchedFaces[e.object_id]=MyEntityObserver.new) if e.instance_of?(Sketchup::Face) }
                (Since the entity is getting a 'change' by attaching an observer, it is smart, to iterate an array, rather than the C++ entities collection directly.)

                If there are Groups and Components in the model, then the above will only find "free" face objects, not faces nested down in Group or Component hierarchies.

                I'm not here much anymore.

                1 Reply Last reply Reply Quote 0
                • 1 / 1
                1 / 1
                • First post
                  1/7
                  Last post
                Buy SketchPlus
                Buy SUbD
                Buy WrapR
                Buy eBook
                Buy Modelur
                Buy Vertex Tools
                Buy SketchCuisine
                Buy FormFonts

                Advertisement