-
Notifications
You must be signed in to change notification settings - Fork 0
/
async_events.rb
127 lines (113 loc) · 3.57 KB
/
async_events.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# This event notification library is inspired by and based on the code found
# in the "Signals & Slots in Ruby" article by Axel Plinge
# http://axel.plinge.de/dev/060116rbsigslot.html (c) Axel Plinge 2006
require 'monitor'
#TODO(0.5) Logging!
# you may or you may not want to freeze the @targets
# you may want to override Thread.start (to the point of not actually starting
# a thread, or for thread pooling, for instance, of course).
module EventsModule
def fires(*events)
for event in events
module_eval <<-"end;"
def __#{event.to_s}_event_targets
@__#{event.object_id}__ ||= []
end
protected :__#{event.to_s}_event_targets
def #{event.to_s}!(*args, &block)
fire(:#{event.to_s}, *args, &block)
end
end;
end
end
protected :fires
end
module Events
def fires
methods.inject([]) { |res, m|
res << $1 if /^__(.*)_event_targets$/ =~ m
res
}
end
def fires?(event)
fires.find { |e| e == event.to_s }
end
# subscribe some target to some event notification (which will effectively be
# an [asynchronous] method call with the optional default arguments
#TODO Think of introducing the notification limit?
def notify!(event, object, method, *args, &block)
__event_sync {
if object && object.method(method).arity <= -2 # (src, *args)
puts "#{to_s} will notify #{object.to_s}::#{method.to_s}(#{args.join(',')}) on #{event.to_s}"
__event_targets(event) << [object, method, [self] + args, block]
self
end
}
end
# subscribe some target to some event notification (which will effectively be
# an [asynchronous] method call with the optional default arguments
def notify_not!(event, object, method)
__event_sync {
puts "#{to_s} will NOT notify #{object.to_s}::#{method.to_s}(#{args.join(',')}) on #{event.to_s}"
__event_targets(event).delete_if { |t| t[0..1] == [object, method] } if
fires?(event)
}
end
def notifies?(event, object, method)
__event_targets(event).find { |t| t[0..1] == [object, method] }
end
def party!(*objects)
for o in objects
for m in o.methods #(true) + o.singleton_methods(true)
notify!(m, o, m) if
fires?(m) && !notifies?(m, o, m)
end
for m in self.methods #(true) + self.singleton_methods(true)
o.notify!(m, self, m) if
o.fires?(m) && !o.notifies?(m, self, m)
end
end
self
end
def party_not!(*objects)
for o in objects
for m in o.methods #(true) + o.singleton_methods(true)
notify_not!(m, o, m)
end
for m in self.methods #(true) + self.singleton_methods(true)
o.notify_not!(m, self, m)
end
end
self
end
#TODO disconnect & disconnect(event
protected
# call associated targets' methods with the
# with the default args plus the given ones
def fire(event, *args/*, &block*/)
__event_sync {
for t in __event_targets(event)
begin
puts "#{to_s} fires #{event.to_s}(#{args.join(',')}) at #{t[0].to_s}::#{t[1].to_s}(#{(t[2] + args).join(',')})"
if t[0].respond_to? :event_send
t[0].event_send(t[1], *(t[2] + args), &t[3])
else
t[0].send(t[1], *(t[2] + args), &t[3])
end
rescue ThreadPooling::QueueFullError
if block_given?
yield
else
raise
end
end
end
}
end
def __event_targets(e)
method("__#{e.to_s}_event_targets").call
end
def __event_sync(&block)
(@__event_mon ||= Monitor.new).synchronize(&block)
end
end