@@ -130,6 +130,8 @@ def update_objects_to_observe(observer = @current_observer)
130130 def remove ( observer = @current_observer )
131131 remove_current_observers_and_objects ( observer )
132132 new_objects . delete observer
133+ # see run_delayed_updater for the purpose of @removed_observers
134+ @removed_observers << observer if @removed_observers
133135 end
134136
135137 # Internal (Private) Methods
@@ -166,11 +168,13 @@ def current_objects
166168 # lists by not adding an object hash key unless the object
167169 # already has pending state changes. (See the
168170 # schedule_delayed_updater method below)
171+
169172 def update_exclusions
170173 @update_exclusions ||= Hash . new
171174 end
172175
173176 # remove_current_observers_and_objects clears the hashes between renders
177+
174178 def remove_current_observers_and_objects ( observer )
175179 raise 'state management called outside of watch block' unless observer
176180 deleted_objects = current_objects . delete ( observer )
@@ -190,6 +194,7 @@ def remove_current_observers_and_objects(observer)
190194 # Hyperstack.on_client? is true WITH ONE EXCEPTION:
191195 # observers can indicate that they need immediate updates in
192196 # case that the object being updated is themselves.
197+
193198 def delay_updates? ( object )
194199 @bulk_update_flag ||
195200 ( Hyperstack . on_client? &&
@@ -211,21 +216,36 @@ def delay_updates?(object)
211216 # If an object changes state again then the Set will be reinitialized, and all
212217 # the observers that might have been on a previous exclusion list, will now be
213218 # notified.
219+
214220 def schedule_delayed_updater ( object )
215221 update_exclusions [ object ] = Set . new
216- @delayed_updater ||= after ( 0 ) do
217- current_update_exclusions = @update_exclusions
218- @update_exclusions = @delayed_updater = nil
219- observers_to_update ( current_update_exclusions ) . each do |observer , objects |
220- observer . mutations objects
221- end
222+ @delayed_updater ||= after ( 0 ) { run_delayed_updater }
223+ end
224+
225+ # run_delayed_updater will call the mutations method for each observer passing
226+ # the entire list of objects that changed while waiting for the delay except
227+ # those that the observer has already seen (the exclusion list). The observers
228+ # mutation method may cause some other observer already on the observers_to_update
229+ # list to be removed. To prevent these observers from receiving mutations we keep a
230+ # temporary set of removed_observers. This is initialized before the mutations,
231+ # and then cleared as soon as we are done.
232+
233+ def run_delayed_updater
234+ current_update_exclusions = @update_exclusions
235+ @update_exclusions = @delayed_updater = nil
236+ @removed_observers = Set . new
237+ observers_to_update ( current_update_exclusions ) . each do |observer , objects |
238+ observer . mutations objects unless @removed_observers . include? observer
222239 end
240+ ensure
241+ @removed_observers = nil
223242 end
224243
225244 # observers_to_update returns a hash with observers as keys, and lists of objects
226245 # as values. The hash is built by filtering the current_observers list
227246 # including only observers that have mutated objects, that are not on the exclusion
228247 # list.
248+
229249 def observers_to_update ( exclusions )
230250 Hash . new { |hash , key | hash [ key ] = Array . new } . tap do |updates |
231251 exclusions . each do |object , excluded_observers |
0 commit comments