Class: Teek::CallbackRegistry

Inherits:
Object
  • Object
show all
Defined in:
lib/teek/callback_registry.rb

Overview

Tracks Ruby callback ids scoped to something narrower than a whole widget - keyed by (container, key) pairs - so they can be released again without waiting for a that may never come for the thing the callback is actually attached to (an event binding, a menu entry, ...). A single instance is shared across every feature that needs this; callers namespace their own container keys (by convention, [feature_tag, path] tuples) so two features tracking the same underlying path never collide.

There is exactly one way to keep the registry in sync: #reconcile. Its block is handed the => id hash tracked last time and must return the => id hash that should be tracked now; whatever id drops out between the two gets released. What the block does with the hash it's handed is entirely up to the caller - reuse its values as a starting point for a cheap in-memory update (nothing external can silently change an event binding), or ignore it and recompute the truth from scratch by asking Tk (Tk silently renumbers menu entries, so nothing short of asking can be trusted there). The registry itself never knows or cares which one a caller chose.

The one hard rule either way: the returned hash must be a DIFFERENT object from the one the block was handed - e.g. before.merge(...), never before.merge!(...) returned as-is. Released ids are computed as before.values - after.values; if the block mutates before in place and returns that same object, before and after are identical by the time that subtraction runs, so any id that was dropped or replaced is silently never released - a leak, the exact thing this class exists to prevent.

#forget_all_for_path is the only thing a handler needs to call: it releases every container ever registered under a path, regardless of which feature created it, via a reverse index built automatically as a side effect of #reconcile.

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ CallbackRegistry

Returns a new instance of CallbackRegistry.



40
41
42
43
44
# File 'lib/teek/callback_registry.rb', line 40

def initialize(app)
  @app = app
  @entries = Hash.new { |h, k| h[k] = {} }
  @containers_by_path = Hash.new { |h, k| h[k] = Set.new }
end

Instance Method Details

#forget_all_for_path(path) ⇒ void

This method returns an undefined value.

Release every callback tracked under any container registered for path, regardless of which feature created it, and forget them.



64
65
66
67
68
69
70
71
# File 'lib/teek/callback_registry.rb', line 64

def forget_all_for_path(path)
  containers = @containers_by_path.delete(path)
  return unless containers
  containers.each do |container|
    ids = @entries.delete(container)
    ids&.each_value { |id| @app.unregister_callback(id) }
  end
end

#reconcile(container) {|before| ... } ⇒ void

This method returns an undefined value.

Yield Parameters:

  • before (Hash)

    the => id hash tracked for container as of the last call (empty on the first call)

Yield Returns:

  • (Hash)

    the => id hash that should be tracked now - must be a different object from before (see class docs above); do not mutate before in place and return it, or dropped/replaced ids silently never get released



53
54
55
56
57
58
59
# File 'lib/teek/callback_registry.rb', line 53

def reconcile(container)
  track(container)
  before = @entries[container]
  after = yield(before)
  (before.values - after.values).each { |id| @app.unregister_callback(id) }
  @entries[container] = after
end