Observer and crash

Hi all,

I’m experiencing some crashes on my SketchUp 2026 (and 2025) on both Mac and Windows. I only have my extension Addesign installed.

I suspect observers are the cause. I have InstanceObserver, EntityObserver, and EntitiesObserver implemented in my extension.

When I read the documentation, I found this note:

Note:

The methods of this observer fire in such a way that making changes to the model while inside of them is dangerous. If you experience sudden crashes, it could be because of this observer. A potential workaround is to use a ToolsObserver to watch what the user is doing instead.

My observers’ goals are:

  • Identify when a wall (component instance) is moved or deleted → so other walls can adjust their sizes, and windows can follow the wall (window components aren’t inside walls)

  • Identify when a window (component instance) is moved or deleted → to move or remove the opening

  • Identify when a window’s size changes (component definition) → to change the wall opening

Crashes happen suddenly but particularly when the user double-clicks on a group or a component (even if the component doesn’t have any link to any wall or window where observers are attached).

Is there a workaround for observers?

Any advice?

You trigger a movement that triggers another movement. This creates an avalanche of movements, which likely causes the buffer to overflow. Try disabling your observer when you trigger a movement, perform the required movement, and then re-enable your observer.

1 Like

thanks for your answer but that was not case. its was the Sketchup::EntitiesObserver, that create this crash. I find a way to avoid this EntitiesObserver and its works.

I have used observers that edit the model with the following approach:

  • Setup a BuildQueue handler class. When an observer sees that an element of the model is edited, instead of triggering model edits, it adds an instruction to edit the model to the BuildQueue. It should check that the instruction isn’t in the queue already.
  • The BuildQueue class processes its queue only on onTransactionCommit (so you need set up an observer for that). You would be advised to put int a timer before you process too (see sketchup-safe-observer-events/src/safer_observer_events.rb at main · SketchUp/sketchup-safe-observer-events · GitHub )
  • All of the edits in the BuildQueue should be processed as one transaction commit. Before you do anything, you need to have a way to turn off all of your own observers while you make your changes, otherwise you’ll get stuck in an infinite loop. The commit operation I tend to attach to the previous commit (so the start_operation method would use the flag “transparent: flag to true.)

Here is a class I made for my purposes - happy to share.

=begin
Name : BuildQueueClass
Author : Tom Kaneko (c) 2025
Version : 1.0
###
=end

module TK

###
# BuildQueue class handles the editing of the model.  This class aggregates many requests to make edits to the model into one call, which happens when the BuildQueue::process_queue() method is called.
class BuildQueue
  
  # The build queue is stored as a Hash of BuildEvent objects.
  # The key is the template object to build, and the value is the BuildEvent
  @@queue = {}
    
  ###
  # Sets up the call for editing stuff
  def self.add_queue(event)
    @@queue[event.name] = event
    puts "DEBUG: BuildQueue.add_queue " + event.name.to_s
  end

  ###
  # Processes the events queue, and does stuff.
  def self.process_queue()
    successes = {}
    new_stuffs = []
    @@queue.each{ |name, build_event|
      new_stuff = build_event.execute()
      if (!new_stuff.nil?)
        new_stuffs << new_stuff
      end
      successes[name] = new_stuff
    }
    # Delete the items off the queue
    successes.each{ |name, new_stuff|
      @@queue.delete(name)
    }
    return new_stuffs
  end
  
  # Method deletes an action from the queue, using name as the key.
  def self.remove_stuff(name)
    if (@@queue.has_key?(name))
      @@queue.delete(name)
    end
  end


  def self.length
    return @@queue.length
  end


  class << self
    def queue
      @@queue
    end
  end

end


###
# The BuildEvent class holds all the variables needed to build Stuff
class BuildEvent

###
# Accessors
attr_accessor :action_type
attr_accessor :waiting

###
# Constructor for BuildEvent
# @param stuff
# @param action_type (optional) - the kind of build event that occurs. Options:
#   - :build - simply builds stuff
#   - :update - removes previous stuff and builds new stuff

def initialize(stuff, action_type = :build)
  # initialise the event with stuff
  @stuff = stuff
  @action_type = action_type
  # etc
end

###
# Executes the buildEvent. Note that this operation may be carried out in the context of an observer, which can cause many issues. Therefore, it is important that all entities that we work with is checked for validity before doing anything with it.
# @return true on success, false on failure.
def execute
  puts "DEBUG: Executing BuildQueue"
  # Observers need to be turned off to avoid infinite loop
  temp_deactivate_observers = TK.deactivate_observers
  TK.deactivate_observers = true
  # DO STUFF HERE, including commit
  # Turn observers back on
  TK::SkinUp.deactivate_observers = temp_deactivate_observers
end
   
end # class BuildEvent


###
# Model observer is used to process the BuildQueue after each commit operation.
class CommitModelObserver < Sketchup::ModelObserver

# class variable prevents commits from this observer comitting in an infinite loop.
@@deactivate_commit = false

def onTransactionCommit(model)
  return if @@deactivate_commit || !CommitObserver.auto_update
  puts "DEBUG: onTransactionCommit start"
  # The following snippet from SaferObserverEvents: https://github.com/SketchUp/sketchup-safe-observer-events/blob/master/src/safer_observer_events.rb
  executed = false
  timer_id = UI.start_timer(0, false) {
    break if executed
    executed = true
    next if  BuildQueue.length == 0
    # Ensure that the operation is transparent so the Undo stack isn't
    # cluttered.
    if (!TK.deactivate_observers)
      TK.deactivate_observers = true
      @@deactivate_commit = true
      status = model.start_operation('Do Stuff (doesn\'t show anyway)', true, false, true)
      raise LogicError, "Start Operation Error" if status == false
      new_stuffs = []
      begin
        new_stuffs = BuildQueue.process_queue()
        if new_stuffs.length == 0
          puts caller
        end
      rescue
        # Note: Never abort a transparent operation, it will abort the operation
        # it chains to. Instead, try to clean up and simply commit in order to
        # make sure the operation is closed.
        # https://assets.sketchup.com/files/ewh/Observers2016.pdf
        # TODO: the commit operation triggers a crash in the observers in DCobservers.  Stack trace following commit:
        # /users/tomkaneko/library/application support/sketchup 2019/sketchup/plugins/su_dynamiccomponents/ruby/dcobservers.rbe:829:in `instances'
        #/users/tomkaneko/library/application support/sketchup 2019/sketchup/plugins/su_dynamiccomponents/ruby/dcobservers.rbe:829:in `onComponentAdded'
        model.commit_operation
        @@deactivate_commit = false
        TK.deactivate_observers = false
        puts "DEBUG: onTransactionCommit end"
        raise LogicError, 'SkinUp Build Queue processing failed'
      end
      puts "DEBUG: events processed: " + new_stuffs.length.to_s
      status = model.commit_operation
      puts "DEBUG: transaction commited"
      @@deactivate_commit = false
      TK.deactivate_observers = false
    end
  }
  puts "DEBUG: onTransactionCommit end"
end

end # class CommitModelObserver

end # module

3 Likes

Thank you so much for this detailed explanation — your solution is really brilliant!
I really like the idea of using a build queue and only applying the changes during onTransactionCommit.

I’ll take the time to test this approach as soon as possible.
Thanks again for sharing both the code and the method!

1 Like