sketchucation logo sketchucation
    • Login
    ℹ️ Licensed Extensions | FredoBatch, ElevationProfile, FredoSketch, LayOps, MatSim and Pic2Shape will require license from Sept 1st More Info

    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

      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
      • Dan RathbunD Offline
        Dan Rathbun
        last edited by

        (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
        • Dan RathbunD Offline
          Dan Rathbun
          last edited by

          (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

            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
            • Dan RathbunD Offline
              Dan Rathbun
              last edited by

              @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
              • Dan RathbunD Offline
                Dan Rathbun
                last edited by

                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
                • Dan RathbunD Offline
                  Dan Rathbun
                  last edited by

                  @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
                  • First post
                    Last post
                  Buy SketchPlus
                  Buy SUbD
                  Buy WrapR
                  Buy eBook
                  Buy Modelur
                  Buy Vertex Tools
                  Buy SketchCuisine
                  Buy FormFonts

                  Advertisement