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

Class Method Details

.bulk_updateObject

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_objectsObject

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_observersObject

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.

Returns:

  • (Boolean)


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_mutationsObject



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_objectsObject

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.

Returns:

  • (Boolean)


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_updaterObject

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_exclusionsObject

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