lib/motion/events.rb in rm-extensions-0.4.5 vs lib/motion/events.rb in rm-extensions-0.5.0

- old
+ new

@@ -2,60 +2,63 @@ module ObjectExtensions module Events - def rmext_events_proxy - @rmext_events_proxy ||= EventsProxy.new(self) + def rmext_events_from_proxy + @rmext_events_from_proxy ||= EventsFromProxy.new(self) end - def rmext_events_proxy? - !@rmext_events_proxy.nil? + def rmext_events_from_proxy? + !!@rmext_events_from_proxy end + def rmext_events_to_proxy + @rmext_events_to_proxy ||= EventsToProxy.new(self) + end + + def rmext_events_to_proxy? + !!@rmext_events_to_proxy + end + # register a callback when an event is triggered on this object. - def rmext_on(object, event, &block) - object.rmext_events_proxy.on(event, limit:-1, inContext:self, withBlock:block) + def rmext_on(event, opts={}, &block) + rmext_events_from_proxy.on(event, opts, &block) end - def rmext_now_and_on(object, event, &block) - object.rmext_events_proxy.now_and_on(event, inContext:self, withBlock:block) + def rmext_now_and_on(event, opts={}, &block) + rmext_events_from_proxy.now_and_on(event, opts, &block) end # register a callback when an event is triggered on this object and remove it after it fires once - def rmext_once(object, event, &block) - object.rmext_events_proxy.on(event, limit:1, inContext:self, withBlock:block) + def rmext_once(event, opts={}, &block) + opts[:limit] = 1 + rmext_events_from_proxy.on(event, opts, &block) end - # remove a specific callback for an event on object - def rmext_off(object, event, &block) - if object.rmext_events_proxy? - object.rmext_events_proxy.off(event, inContext:self, withBlock:block) + # @model.rmext_off(:fire, self) # remove :fire in context "self" + # @model.rmext_off(:fire, &block) # remove :fire for specific handler + # @model.rmext_off(:fire) # remove all :fire in all knowns contexts + # @model.rmext_off(self) # remove all events in context "self" + # @model.rmext_off # remove all events in all known contexts + def rmext_off(event=nil, context=nil, &block) + if rmext_events_from_proxy? + rmext_events_from_proxy.off(event, context, &block) end end - # remove all event callbacks on this object, # remove all event callbacks from other objects in this object's "self" def rmext_cleanup - if @rmext_events_proxy - @rmext_events_proxy.cleanup + if rmext_events_to_proxy? + rmext_events_to_proxy.cleanup end end - ### these get called on the object: ie. @model.rmext_off_all - - # remove all event callbacks on this object - def rmext_off_all - if @rmext_events_proxy - @rmext_events_proxy.off_all - end - end - # trigger an event with value on this object def rmext_trigger(event, value=nil) - if @rmext_events_proxy - @rmext_events_proxy.trigger(event, value) + if rmext_events_from_proxy? + rmext_events_from_proxy.trigger(event, value) end end end @@ -63,110 +66,153 @@ class EventResponse attr_accessor :context, :value, :target, :event end - # Proxy class used to hold the actual listeners and contexts where listening - # and watches for the real class intended to hold the observation to be - # deallocated, so the events can be cleaned up. - class EventsProxy + # Proxy object used to hold the firing objects that this real object's + # "self" owns handlers for. + # Can be used to cleanup all handlers across all firing objects that have + # the hanlder's owner (Proc#owner) == this real object. + # Does not need to perform deallocation logic as nothing is retained + # and the real object will fall out of the cooresponding EventsFromProxy + # automatically. + class EventsToProxy + rmext_zeroing_weak_attr_accessor :weak_object + def initialize(obj) - @weak_object = WeakRef.new(obj) + self.weak_object = obj + @has_handlers_for = NSHashTable.weakObjectsHashTable + end + + def has_handlers_for!(firing_object) + if ::RMExtensions.debug? + p "CONTEXT:", weak_object.rmext_object_desc, "LISTENING TO:", firing_object.rmext_object_desc + end + @has_handlers_for.addObject(firing_object) + end + + def cleanup(firing_object=nil) + # p "cleanup caller", caller + if firing_object + if @has_handlers_for.containsObject(firing_object) + if ::RMExtensions.debug? + p "CONTEXT:", weak_object.rmext_object_desc, "UNLISTENING TO:", firing_object.rmext_object_desc + end + @has_handlers_for.removeObject(firing_object) + firing_object.rmext_off(weak_object) + end + else + while firing_object = @has_handlers_for.anyObject + if ::RMExtensions.debug? + p "CONTEXT:", weak_object.rmext_object_desc, "UNLISTENING TO:", firing_object.rmext_object_desc + end + @has_handlers_for.removeObject(firing_object) + firing_object.rmext_off(weak_object) + end + end + true + end + + end + + # Proxy class used to hold the actual handlers and contexts of handlers. + # When the real class deallocates, all handlers are removed. + class EventsFromProxy + + rmext_zeroing_weak_attr_accessor :weak_object + + def initialize(obj) + self.weak_object = obj @events = NSMapTable.weakToStrongObjectsMapTable - @listenings = NSHashTable.weakObjectsHashTable if ::RMExtensions.debug? - p "CREATED EventsProxy: #{@weak_object.rmext_object_desc}" + p "CREATED #{className}: #{weak_object.rmext_object_desc}" end end def dealloc - @did_dealloc = true - cleanup + # @did_dealloc = true + off if ::RMExtensions.debug? - p "DEALLOC EventsProxy: #{@weak_object.rmext_object_desc}" + p "DEALLOC #{className}: #{weak_object.rmext_object_desc}" end super end - def cleanup - off_all - off_all_context - true - end - - def on(event, limit:limit, inContext:context, withBlock:block) + def on(event, opts={}, &block) return if event.nil? || block.nil? event = event.to_s - context ||= self.class + context = block.owner unless context_events = @events.objectForKey(context) context_events = {} @events.setObject(context_events, forKey:context) end unless context_event_blocks = context_events.objectForKey(event) context_event_blocks = {} context_events.setObject(context_event_blocks, forKey:event) end block.weak! - context_event_blocks[block] = limit - # i.e.: controller/view listening_to model - context.rmext_events_proxy.listening_to(@weak_object) + context_event_blocks[block] = opts[:limit] || -1 + # i.e.: controller/view has handlers for object + context.rmext_events_to_proxy.has_handlers_for!(weak_object) end - # this is called in the reverse direction than normal - def listening_to(object) - if ::RMExtensions.debug? - p "CONTEXT:", @weak_object.rmext_object_desc, "LISTENING TO:", object.rmext_object_desc - end - @listenings.addObject(object) - end - - def now_and_on(event, inContext:context, withBlock:block) + def now_and_on(event, opts={}, &block) rmext_inline_or_on_main_q do res = EventResponse.new - res.context = context + res.context = block.owner res.value = nil - res.target = @weak_object + res.target = weak_object res.event = event block.call(res) end - on(event, limit:-1, inContext:context, withBlock:block) + on(event, opts, &block) end - def off(event, inContext:context, withBlock:block) - return if event.nil? || block.nil? - event = event.to_s - context ||= self.class - return unless context_events = @events.objectForKey(context) - return unless context_event_blocks = context_events.objectForKey(event) - context_event_blocks.delete block - nil - end - - def off_all - @events.removeAllObjects - end - - def off_context(context) - @events.setObject(nil, forKey:context) - end - - def off_all_context - while object = @listenings.anyObject - if ::RMExtensions.debug? - p "CONTEXT:", @weak_object.rmext_object_desc, "UNLISTENING TO:", object.rmext_object_desc + def off(event=nil, context=nil, &block) + if event.is_a?(String) || event.is_a?(Symbol) + event = event.to_s + if context + # remove all handlers for the given event in the given context + if context_events = @events.objectForKey(context) + context_events.delete(event) + end + elsif block + # remove the one block for the event in the blocks #owner + context = block.owner + if context_events = @events.objectForKey(context) + if context_event_blocks = context_events.objectForKey(event) + context_event_blocks.delete block + end + end + else + # remove all handlers for the event in all contexts known + keyEnumerator = @events.keyEnumerator + contexts = [] + while context = keyEnumerator.nextObject + contexts.push context + end + while context = contexts.pop + if context_events = @events.objectForKey(context) + context_events.delete event + end + end end - @listenings.removeObject(object) - if object.rmext_events_proxy? - object.rmext_events_proxy.off_context(@weak_object) - end + elsif event + # event is really a context. remove all events and handlers for the context + context = event + @events.removeObjectForKey(context) + else + # remove everything + @events.removeAllObjects end + nil end def trigger(event, value) rmext_inline_or_on_main_q do - next if @did_dealloc + # next if @did_dealloc next if event.nil? event = event.to_s keyEnumerator = @events.keyEnumerator contexts = [] while context = keyEnumerator.nextObject @@ -175,27 +221,27 @@ while context = contexts.pop if context_events = @events.objectForKey(context) if event_blocks = context_events[event] blocks = event_blocks.keys if ::RMExtensions.debug? - p "TRIGGER:", event, "OBJECT:", @weak_object.rmext_object_desc, "CONTEXT:", context.rmext_object_desc, "BLOCKS SIZE:", blocks.size + p "TRIGGER:", event, "OBJECT:", weak_object.rmext_object_desc, "CONTEXT:", context.rmext_object_desc, "BLOCKS SIZE:", blocks.size end while block = blocks.pop limit = event_blocks[block] res = EventResponse.new res.context = context res.value = value - res.target = @weak_object + res.target = weak_object res.event = event block.call(res) if limit == 1 # off if ::RMExtensions.debug? - p "LIMIT REACHED:", event, "OBJECT:", @weak_object.rmext_object_desc, "CONTEXT:", context.rmext_object_desc + p "LIMIT REACHED:", event, "OBJECT:", weak_object.rmext_object_desc, "CONTEXT:", context.rmext_object_desc end - off(event, inContext:context, withBlock:block) + off(event, context, &block) elsif limit > 1 - context_events[block] -= 1 + event_blocks[block] -= 1 end end end end end