Module: Hyperstack::Internal::State::Mapper
- Defined in:
- lib/hyperstack/internal/state/mapper.rb
Overview
Typically mutated! is called during some javascript event, and we will want to delay notification until the event handler has completed execution.
Class Method Summary collapse
-
.bulk_update ⇒ Object
Code can be wrapped in the bulk_update method, and notifications of any mutations that occur during the yield will be scheduled for after the current event finishes.
-
.current_objects ⇒ Object
and a list of objects indexed by observers.
-
.current_observers ⇒ Object
at the end of the rendering cycle the new_objects are processed into a list of observers indexed by objects…
-
.delay_updates?(object) ⇒ Boolean
determine if updates should be delayed.
- .ignore_mutations ⇒ Object
-
.mutated!(object) ⇒ Object
Called when an object has been mutated.
-
.new_objects ⇒ Object
new_objects are added as the @current_observer reads an objects state.
-
.observed!(object) ⇒ Object
called when an object has been observed (i.e. read) by somebody.
-
.observed?(object) ⇒ Boolean
Check to see if an object has been observed.
-
.observers_to_update(exclusions) ⇒ Object
observers_to_update returns a hash with observers as keys, and lists of objects as values.
-
.observing(observer, immediate_update, rendering, update_objects) ⇒ Object
Once the observer’s block completes execution, the context instance variables are restored.
-
.remove(observer = @current_observer) ⇒ Object
call remove before unmounting components to prevent stray events from being sent to unmounted components.
-
.remove_current_observers_and_objects(observer) ⇒ Object
remove_current_observers_and_objects clears the hashes between renders.
-
.run_delayed_updater ⇒ Object
run_delayed_updater will call the mutations method for each observer passing the entire list of objects that changed while waiting for the delay except those that the observer has already seen (the exclusion list).
-
.schedule_delayed_updater(object) ⇒ Object
If an object changes state again then the Set will be reinitialized, and all the observers that might have been on a previous exclusion list, will now be notified.
-
.update_exclusions ⇒ Object
We avoid keeping empty lists of observers on the exclusion lists by not adding an object hash key unless the object already has pending state changes.
-
.update_objects_to_observe(observer = @current_observer) ⇒ Object
TODO: see if we can get rid of all this and simply calling remove_current_observers_and_objects at the START of each components rendering cycle (i.e. before_mount and before_update).
Class Method Details
.bulk_update ⇒ Object
Code can be wrapped in the bulk_update method, and notifications of any mutations that occur during the yield will be scheduled for after the current event finishes.
97 98 99 100 101 102 103 |
# File 'lib/hyperstack/internal/state/mapper.rb', line 97 def bulk_update saved_bulk_update_flag = @bulk_update_flag @bulk_update_flag = true yield ensure @bulk_update_flag = saved_bulk_update_flag end |
.current_objects ⇒ Object
and a list of objects indexed by observers
166 167 168 |
# File 'lib/hyperstack/internal/state/mapper.rb', line 166 def current_objects @current_objects ||= Hash.new { |h, k| h[k] = [] } end |
.current_observers ⇒ Object
at the end of the rendering cycle the new_objects are processed into a list of observers indexed by objects…
161 162 163 |
# File 'lib/hyperstack/internal/state/mapper.rb', line 161 def current_observers @current_observers ||= Hash.new { |h, k| h[k] = [] } end |
.delay_updates?(object) ⇒ Boolean
determine if updates should be delayed. always delay updates if the bulk_update_flag is set otherwise delayed updates only occurs if Hyperstack.on_client? is true WITH ONE EXCEPTION: observers can indicate that they need immediate updates in case that the object being updated is themselves.
209 210 211 212 213 |
# File 'lib/hyperstack/internal/state/mapper.rb', line 209 def delay_updates?(object) @bulk_update_flag || (Hyperstack.on_client? && (@immediate_update != @current_observer || @current_observer != object)) end |
.ignore_mutations ⇒ Object
105 106 107 108 109 110 111 |
# File 'lib/hyperstack/internal/state/mapper.rb', line 105 def ignore_mutations saved_ignore_mutations_flag = @ignore_mutations @ignore_mutations = true yield ensure @ignore_mutations = saved_ignore_mutations_flag end |
.mutated!(object) ⇒ Object
Called when an object has been mutated. Depending on the state of StateContext we will either schedule the update notification for later, immediately notify any observers, or do nothing.
75 76 77 78 79 80 81 82 83 84 |
# File 'lib/hyperstack/internal/state/mapper.rb', line 75 def mutated!(object) return if @ignore_mutations if delay_updates?(object) schedule_delayed_updater(object) elsif @rendering_level.zero? current_observers[object].each do |observer| observer.mutations([object]) end if current_observers.key? object end end |
.new_objects ⇒ Object
new_objects are added as the @current_observer reads an objects state
155 156 157 |
# File 'lib/hyperstack/internal/state/mapper.rb', line 155 def new_objects @new_objects ||= Hash.new { |h, k| h[k] = Set.new } end |
.observed!(object) ⇒ Object
called when an object has been observed (i.e. read) by somebody
64 65 66 67 68 69 |
# File 'lib/hyperstack/internal/state/mapper.rb', line 64 def observed!(object) return unless @current_observer new_objects[@current_observer] << object return unless update_exclusions[object] update_exclusions[object] << @current_observer end |
.observed?(object) ⇒ Boolean
Check to see if an object has been observed.
87 88 89 90 91 |
# File 'lib/hyperstack/internal/state/mapper.rb', line 87 def observed?(object) # we don't want to unnecessarily create a reference to ourselves # in the current_observers hash so we just look for the key. current_observers.key?(object)# && current_observers[object].any? end |
.observers_to_update(exclusions) ⇒ Object
observers_to_update returns a hash with observers as keys, and lists of objects as values. The hash is built by filtering the current_observers list including only observers that have mutated objects, that are not on the exclusion list.
260 261 262 263 264 265 266 267 268 269 |
# File 'lib/hyperstack/internal/state/mapper.rb', line 260 def observers_to_update(exclusions) Hash.new { |hash, key| hash[key] = Array.new }.tap do |updates| exclusions.each do |object, excluded_observers| current_observers[object].each do |observer| next if excluded_observers.include?(observer) updates[observer] << object end if current_observers.key? object end end end |
.observing(observer, immediate_update, rendering, update_objects) ⇒ Object
Once the observer’s block completes execution, the context instance variables are restored.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/hyperstack/internal/state/mapper.rb', line 45 def observing(observer, immediate_update, rendering, update_objects) saved_context = [@current_observer, @immediate_update] @current_observer = observer @immediate_update = immediate_update && observer if rendering @rendering_level += 1 observed!(observer) observed!(observer.class) end return_value = yield update_objects_to_observe(observer) if update_objects return_value ensure @current_observer, @immediate_update = saved_context @rendering_level -= 1 if rendering return_value end |
.remove(observer = @current_observer) ⇒ Object
call remove before unmounting components to prevent stray events from being sent to unmounted components.
141 142 143 144 145 146 |
# File 'lib/hyperstack/internal/state/mapper.rb', line 141 def remove(observer = @current_observer) remove_current_observers_and_objects(observer) new_objects.delete observer # see run_delayed_updater for the purpose of @removed_observers @removed_observers << observer if @removed_observers end |
.remove_current_observers_and_objects(observer) ⇒ Object
remove_current_observers_and_objects clears the hashes between renders
189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/hyperstack/internal/state/mapper.rb', line 189 def remove_current_observers_and_objects(observer) raise 'state management called outside of watch block' unless observer deleted_objects = current_objects.delete(observer) return unless deleted_objects deleted_objects.each do |object| # to allow for GC we never want objects hanging around as keys in # the current_observers hash, so we tread carefully here. next unless current_observers.key? object current_observers[object].delete(observer) current_observers.delete object if current_observers[object].empty? end end |
.run_delayed_updater ⇒ Object
run_delayed_updater will call the mutations method for each observer passing the entire list of objects that changed while waiting for the delay except those that the observer has already seen (the exclusion list). The observers mutation method may cause some other observer already on the observers_to_update list to be removed. To prevent these observers from receiving mutations we keep a temporary set of removed_observers. This is initialized before the mutations, and then cleared as soon as we are done.
244 245 246 247 248 249 250 251 252 253 |
# File 'lib/hyperstack/internal/state/mapper.rb', line 244 def run_delayed_updater current_update_exclusions = @update_exclusions @update_exclusions = @delayed_updater = nil @removed_observers = Set.new observers_to_update(current_update_exclusions).each do |observer, objects| observer.mutations objects unless @removed_observers.include? observer end ensure @removed_observers = nil end |
.schedule_delayed_updater(object) ⇒ Object
If an object changes state again then the Set will be reinitialized, and all the observers that might have been on a previous exclusion list, will now be notified.
231 232 233 234 |
# File 'lib/hyperstack/internal/state/mapper.rb', line 231 def schedule_delayed_updater(object) update_exclusions[object] = Set.new @delayed_updater ||= after(0) { run_delayed_updater } end |
.update_exclusions ⇒ Object
We avoid keeping empty lists of observers on the exclusion lists by not adding an object hash key unless the object already has pending state changes. (See the schedule_delayed_updater method below)
183 184 185 |
# File 'lib/hyperstack/internal/state/mapper.rb', line 183 def update_exclusions @update_exclusions ||= Hash.new end |
.update_objects_to_observe(observer = @current_observer) ⇒ Object
TODO: see if we can get rid of all this and simply calling remove_current_observers_and_objects at the START of each components rendering cycle (i.e. before_mount and before_update)
132 133 134 135 136 137 |
# File 'lib/hyperstack/internal/state/mapper.rb', line 132 def update_objects_to_observe(observer = @current_observer) remove_current_observers_and_objects(observer) objects = new_objects.delete(observer) objects.each { |object| current_observers[object] << observer } if objects current_objects[observer] = objects end |