Observers firing sequence

Hi, I’m workin with observers and I noted that the observers’ fire sequence is not what I aspected.
When a change page I aspect that fires first the onContentsModified method inside Sketchup::PagesObserver to catch the stat moving from page to another and then fires the onActiveSectionPlaneChanged inside Sketchup::EntitiesObserver. This doesn’t
happens.

The sequence actually is:

1 - Sketchup::EntitiesObserver → onActiveSectionPlaneChanged (n times for each changing SectionPlane)
2 - Sketchup::PagesObserver → onContentsModified (for page I’m moving from)
3 - Sketchup::PagesObserver → onContentsModified (for page I’m going to)

For my extension I need to catch a kind of “onBefore - ContentsModified” event firing before the “Sketchup::EntitiesObserver → onActiveSectionPlaneChanged” event

Where I’m wrong? is there a way to do what I need?

NO, there still are some restrictions, … and YES there was an overhaul done about 5 years ago.
It was the 2016 release where the overhaul took place. I put links in another topic from a few days ago:

DimensionArea and add_dimension_area? - #15 by DanRathbun

Again, see the linked topic where Thomas talks about this subject.

With regard to general API behavior, you must only expect what the API documentation says to expect.

The API documentation does not publish the exact order in which the observer callbacks are fired. So the order should not be thought of as “an API contract”. This means that the order of firing could change in future releases.
So, try to write code in a way that does not rely upon any certain order of observer firing if possible.

You need to use a FrameChangeObserver object to immediately detect page changes and transistions.

NOTE: You may need to cache the current (“from scene”) page for use on a Windows platform. (See the example snippet in the documentation.)

Also take note of any logged bugs or issues that are open for FrameChangeObserver in the issue tracker …

Hi Dan, my problem is more complex. I created this code to show what i need.

    sp1 = Sketchup.active_model.entities.add_section_plane([0, 0, 10], [0, 0, 1.0])
    Sketchup.active_model.entities.active_section_plane = sp1
    page1 = Sketchup.active_model.pages.add "page1"
    Sketchup.active_model.rendering_options["DisplaySectionPlanes"] = true
    page1.update PAGE_USE_SECTION_PLANES || PAGE_USE_RENDERING_OPTIONS

    sp2 = Sketchup.active_model.entities.add_section_plane([0, 0, 50], [0, 0, 1.0])
    Sketchup.active_model.entities.active_section_plane = sp2
    page2 = Sketchup.active_model.pages.add "page2"
    Sketchup.active_model.rendering_options["DisplaySectionPlanes"] = true
    page2.update PAGE_USE_SECTION_PLANES || PAGE_USE_RENDERING_OPTIONS

    Sketchup.active_model.styles.update_selected_style

    class MyFrameChangeObserver
      def frameChange(from_scene, to_scene, percent_done)
    from_scene = Sketchup.active_model.pages.selected_page if !from_scene
    if percent_done == 0.0
      Sketchup.status_text= "% done:"
      puts ">>> frameChange START - From page '#{from_scene.name}' to '#{to_scene.name}'"
    elsif percent_done > 0.0 && percent_done < 1.0
      Sketchup.status_text= "% done: #{'|'*(percent_done*100).to_i}"
    else
      puts ">>> frameChange END" if percent_done >= 1.0
    end
      end
    end
    @id = Sketchup::Pages.add_frame_change_observer(MyFrameChangeObserver.new)

    class MyEntitiesObserver < Sketchup::EntitiesObserver
      def onActiveSectionPlaneChanged(entities)
    sp = entities.active_section_plane
      if sp.nil?
        puts "--- onActiveSectionPlaneChanged Section plane is deactivated for page [#{Sketchup.active_model.pages.selected_page.name}]"
      else
        puts "--- onActiveSectionPlaneChanged #{sp.name} is activated for page [#{Sketchup.active_model.pages.selected_page.name}]"
      end
      end
    end
    Sketchup.active_model.entities.add_observer(MyEntitiesObserver.new)

    nil

When you move from a page to another, you will see that “onActiveSectionPlaneChanged” fires before “frameChange” because fires for those sectionPlanes that are changing on “from_page” in reason of the new sectionPlanes’ configuration of “to_page”.

Is there a way to prevent or catch “onActiveSectionPlaneChanged” when I’m moving between pages and let it fires only when I really interact with sectionPlanes on a specific page?
Use “MyFrameChangeObserver” doesn’t looks like the right solution.

As I said the order isn’t guaranteed in the API. If you think that frameChange should fire before any other observer callbacks, please file an API issue in the tracker.

Perhaps do not attach the MyEntitiesObserver until the FrameChangeObserver indicates in your frameChange callback that percent_done > 0.0 .
@watching” would be a boolean state variable to indicate whether your MyEntitiesObserver is attached or removed. You will need to set it initially false. The elsif condition needs to skip attaching if it’s already attached, so it does not happen multiple times. Ie …

    elsif percent_done > 0.0 && percent_done < 1.0
      if !@watching
        @spy = MyEntitiesObserver.new
        Sketchup.active_model.entities.add_observer(@spy)
        @watching = true
      end
      Sketchup.status_text= "% done: #{'|'*(percent_done*100).to_i}"
    else # done transitioning
      Sketchup.active_model.entities.remove_observer(@spy)
      @watching = false
      puts ">>> frameChange END" if percent_done >= 1.0
    end

You would also need to delete the unconditional attachment of the MyEntitiesObserver beneath it’s class definition. (Code should never unconditionally attach any observer except the AppObserver to the application. All other observers are attached to model objects, so they need to be conditionally attached in the AppObserver callbacks.)

I do not have time myself to test your code or this suggestion.

thanks Dan

Whoops. I forgot to put in the assignment to set @watching to true.
(I’ve now added it to the above snippet.)

:blush:

Hi Tom, I attach EntitiesObserver, PagesObserver and Pages.add_frame_change_observer on loading extension and then I use AppObserver to attach EntitiesObserver when I open or create a new model.

The initial code on loading my extension is something like this:

        @app_Obs = MyExtension::App_Obs.new
        @pages_Obs = MyExtension::Pages_Obs.new
        @ents_Obs = MyExtension::Ents_Obs.new
        @skemaFrameChangeObserverId = Sketchup::Pages.add_frame_change_observer(Frames_Obs.new)
        
        Sketchup.add_observer @app_Obs
        Sketchup.active_model.pages.add_observer @pages_Obs
        Sketchup.active_model.entities.add_observer @ents_Obs

This is the order I use to add observers.

Later, when you open or create a new model, AppObserver does something like this (only after checking if the observers are not just been added):

       if !@observers_just_been_added_for_this_model
            Sketchup.active_model.pages.add_observer @pages_Obs
            Sketchup.active_model.entities.add_observer @ents_Obs
       end

But this second part is not fundamental for my problem that lives when I have only one model opened too.

My problem is about the onActiveSectionPlaneChanged call, this is when I move from a page to another page of the same model. In fact I would aspect that frame and page observers will fire before and onActiveSectionPlaneChanged will fire later. Instead I find that onActiveSectionPlaneChanged fires first of all. Note that onActiveSectionPlaneChanged fires for all sectionPlanes that change (active/unactive) between actual page and the page I’m going to.

Now I made some changes to the deep logic of my extension to bypass this problem at all but I think that, where is possible, Sketchup should fires with this sequence:

When I click on new page or programmatically change Sketchup.active_model.pages.selected_page:

  1. ----> Fires frameChange(from_scene, to_scene, percent_done) with percent_done = 0.0%
  2. ----> Fires onContentsModified in Page Observers
  3. ----> Fires frameChange(from_scene, to_scene, percent_done) with percent_done = 1.0%
  4. ----> Fires onActiveSectionPlaneChanged for the new SectionPlanes status on the destination page compared to the SectionPlanes status on actual page.

Or maybe like this:

  1. ----> Fires frameChange(from_scene, to_scene, percent_done) with percent_done = 0.0%
  2. ----> Fires onContentsModified in Page Observers
  3. ----> Fires onActiveSectionPlaneChanged for the new SectionPlanes status on the destination page compared to the SectionPlanes status on actual page.
  4. ----> Fires frameChange(from_scene, to_scene, percent_done) with percent_done = 1.0%

Now instead the sequence is:

  1. ----> Fires onActiveSectionPlaneChanged for the new SectionPlanes status on the destination page compared to the SectionPlanes status on actual page.
  2. ----> Fires frameChange(from_scene, to_scene, percent_done) with percent_done = 0.0%
  3. ----> Fires onContentsModified in Page Observers
  4. ----> Fires frameChange(from_scene, to_scene, percent_done) with percent_done = 1.0%

My opinion is that Frame and Page observers work in an aspected way, onActiveSectionPlaneChanged not.
By the way I understood that the problem is more complex for the “background on the topic of observers in SketchUp” you exposed in your answer.

Thanks for this explaination, I’ve logged it in our issue tracker: onActiveSectionPlaneChanged triggering sequence · Issue #621 · SketchUp/api-issue-tracker (github.com)

2 Likes