wires 0.5.8 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +3 -0
- data/lib/wires/base.rb +3 -2
- data/lib/wires/base/actor.rb +204 -0
- data/lib/wires/base/channel.rb +51 -37
- data/lib/wires/base/event.rb +10 -0
- data/lib/wires/base/{hub.rb → launcher.rb} +2 -2
- data/lib/wires/base/router.rb +98 -52
- data/lib/wires/base/util/router_table.rb +143 -0
- metadata +21 -7
- data/lib/wires/base/util/build_alt.rb +0 -18
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ed1477b941ed3c64b10f1d6d7f0087fc17f07aad
|
|
4
|
+
data.tar.gz: db91d0d10df1770328f942a8f4febe4be4c90502
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d179bf2b1d6d0642ef84483c9f8521f49f8628bd676dd11a87da1d9108da152827ed657887e43dd6458ddaa10eb7b027941644578243844c8370249584dfb20c
|
|
7
|
+
data.tar.gz: f88af8249cceebf4774de1ba33e60bb9f203d5d0da6f13fdadb128ea0fc244cbb35bb717e50c0f573b3feea95dcf73418897bc8122236e612fa85ce0d20ccda5
|
data/LICENSE
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Copyright
|
|
1
|
+
Copyright 2013-2014 Joe McIlvain
|
|
2
2
|
All rights reserved.
|
data/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
Wires
|
|
2
2
|
=====
|
|
3
3
|
|
|
4
|
+
[](https://travis-ci.org/jemc/wires)
|
|
5
|
+
[](http://badge.fury.io/rb/wires)
|
|
6
|
+
|
|
4
7
|
A lightweight, extensible asynchronous event routing framework in Ruby.
|
|
5
8
|
|
|
6
9
|
Patch your objects together with wires.
|
data/lib/wires/base.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
require 'thread'
|
|
3
3
|
require 'threadlock'
|
|
4
|
+
require 'ref'
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
module Wires
|
|
@@ -44,10 +45,10 @@ loader = Proc.new do |path|
|
|
|
44
45
|
end
|
|
45
46
|
|
|
46
47
|
loader.call 'base/util/hooks'
|
|
47
|
-
loader.call 'base/util/
|
|
48
|
+
loader.call 'base/util/router_table'
|
|
48
49
|
|
|
49
50
|
loader.call 'base/event'
|
|
50
|
-
loader.call 'base/
|
|
51
|
+
loader.call 'base/launcher'
|
|
51
52
|
loader.call 'base/router'
|
|
52
53
|
loader.call 'base/channel'
|
|
53
54
|
loader.call 'base/time_scheduler_item'
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
|
|
2
|
+
module Wires.current_network::Namespace
|
|
3
|
+
|
|
4
|
+
# A module to aid in making objects that interact with {Wires}.
|
|
5
|
+
#
|
|
6
|
+
# Because event handlers created with {Channel#register} or {Convenience#on}
|
|
7
|
+
# are global and permanent (until unregistered), other {Wires} patterns
|
|
8
|
+
# are more conducive to global or singleton structures, but an {Actor}
|
|
9
|
+
# can easily be more transient and dynamic.
|
|
10
|
+
#
|
|
11
|
+
# An {Actor} marks one or more of its methods as event handlers with
|
|
12
|
+
# {ClassMethods#handler} or {#handler}, and indicates the channels to
|
|
13
|
+
# listen on with {#listen_on}. Events fired on those channels will generate
|
|
14
|
+
# invocations of the corresponding marked event handler methods.
|
|
15
|
+
#
|
|
16
|
+
# A basic {Actor} might look something like this:
|
|
17
|
+
#
|
|
18
|
+
# class Node
|
|
19
|
+
# include Wires::Actor
|
|
20
|
+
#
|
|
21
|
+
# def initialize(instream, outstream)
|
|
22
|
+
# @instream = instream
|
|
23
|
+
# @outstream = outstream
|
|
24
|
+
# @pending_ids = []
|
|
25
|
+
# listen_on self, :in=>@instream, :out=>@outstream
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# def request(id, command, payload)
|
|
29
|
+
# if command_valid? command, payload
|
|
30
|
+
# fire :pending[id], @outstream
|
|
31
|
+
# fire :process[id, command, payload], self
|
|
32
|
+
# else
|
|
33
|
+
# fire :invalid[id], @outstream
|
|
34
|
+
# end
|
|
35
|
+
# end
|
|
36
|
+
# handler :request, :channel=>:in
|
|
37
|
+
#
|
|
38
|
+
# private
|
|
39
|
+
#
|
|
40
|
+
# def command_valid?(command, payload)
|
|
41
|
+
# # ...
|
|
42
|
+
# # Return true if the command is determined to be valid, else false
|
|
43
|
+
# # ...
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# def process(id, command, payload)
|
|
47
|
+
# # ...
|
|
48
|
+
# # Do some time consuming operation to produce a result
|
|
49
|
+
# # ...
|
|
50
|
+
# @pending_ids -= [id]
|
|
51
|
+
# fire :done[id, result], @outstream
|
|
52
|
+
# end
|
|
53
|
+
# handler :process
|
|
54
|
+
#
|
|
55
|
+
# def track_pending(id)
|
|
56
|
+
# @pending_ids << id
|
|
57
|
+
# end
|
|
58
|
+
# handler :track_pending, :event=>:pending, :channel=>:out
|
|
59
|
+
# end
|
|
60
|
+
#
|
|
61
|
+
module Actor
|
|
62
|
+
include Wires::Convenience
|
|
63
|
+
|
|
64
|
+
# When included into a class, extend {ClassMethods} in.
|
|
65
|
+
#
|
|
66
|
+
def self.included(obj)
|
|
67
|
+
obj.extend ClassMethods
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Define the list of channels that the Actor's handlers listen on.
|
|
71
|
+
# Each subsequent call to {#listen_on} will overwrite the last call,
|
|
72
|
+
# so the entire list of tagged and untagged channels should be specified
|
|
73
|
+
# all in one invocation.
|
|
74
|
+
#
|
|
75
|
+
# @param channels [Array] The untagged channels to listen_on.
|
|
76
|
+
# An event fired on any one of these channels will be routed to any
|
|
77
|
+
# {#handler} that did not specify a :channel when {#handler} was
|
|
78
|
+
# invoked. Each object in the array is treated as the {Channel#name},
|
|
79
|
+
# unless it is itself a {Channel} object.
|
|
80
|
+
#
|
|
81
|
+
# @param coded_channels [Hash] The tagged channels to listen_on,
|
|
82
|
+
# with each key symbol as the channel code, and each value treated as
|
|
83
|
+
# the {Channel#name} (or {Channel} object).
|
|
84
|
+
# An event fired on the given channel will be routed to any
|
|
85
|
+
# {#handler} that specified the same :channel when {#handler} was
|
|
86
|
+
# invoked.
|
|
87
|
+
#
|
|
88
|
+
# @TODO handle Channel objects instead of names
|
|
89
|
+
# @TODO handle Channel matching correctly (respecting Router)
|
|
90
|
+
#
|
|
91
|
+
def listen_on(*channels, **coded_channels)
|
|
92
|
+
@_wires_actor_listen_proc ||= Proc.new do |e,c|
|
|
93
|
+
e = e.dup
|
|
94
|
+
e.kwargs[:_wires_actor_original_channel] = c
|
|
95
|
+
@_wires_actor_channel.fire! e
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
unreg = Proc.new { |c| Channel[c].unregister &@_wires_actor_listen_proc }
|
|
99
|
+
reg = Proc.new { |c| Channel[c].register :*, &@_wires_actor_listen_proc }
|
|
100
|
+
|
|
101
|
+
@_wires_actor_coded_channels ||= {nil=>[]}
|
|
102
|
+
|
|
103
|
+
old_channels = @_wires_actor_coded_channels.delete nil
|
|
104
|
+
old_coded_channels = @_wires_actor_coded_channels
|
|
105
|
+
|
|
106
|
+
old_channels.each &unreg
|
|
107
|
+
old_coded_channels.values.each &unreg
|
|
108
|
+
|
|
109
|
+
channels.each ®
|
|
110
|
+
coded_channels.values.each ®
|
|
111
|
+
|
|
112
|
+
@_wires_actor_coded_channels = coded_channels.dup
|
|
113
|
+
@_wires_actor_coded_channels[nil] = channels
|
|
114
|
+
|
|
115
|
+
nil
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Mark a method by name to receive events of the same type name on the
|
|
119
|
+
# channels specified by the most recent call to {#listen_on}. By default,
|
|
120
|
+
# the method is called with the arguments used in {Event} creation.
|
|
121
|
+
#
|
|
122
|
+
# @param method_name [Symbol] The name of the instance method to mark
|
|
123
|
+
# as an event handler.
|
|
124
|
+
# @param event [Symbol] The type of events to listen for.
|
|
125
|
+
# @param channel [Symbol] The channel code corresponding to one of
|
|
126
|
+
# the keywords to be used in a call to {#listen_on}. This channel code
|
|
127
|
+
# is used rather than a literal {Channel#name} because the name may or
|
|
128
|
+
# may not be known at the time of {#handler} call; the code will be
|
|
129
|
+
# resolved to an actual {Channel#name} at time of event reception
|
|
130
|
+
# based on the last call to {#listen_on}. If no channel is
|
|
131
|
+
# specified, the handler will listen for the event on the untagged
|
|
132
|
+
# channels specified in {#listen_on}.
|
|
133
|
+
#
|
|
134
|
+
# @param expand [Boolean] Specify the form of the arguments to be
|
|
135
|
+
# passed to the method when called. This argument is +true+ by default,
|
|
136
|
+
# indicating the default behavior of passing the arguments used in
|
|
137
|
+
# {Event} creation. If +false+ is specified, the method will receive
|
|
138
|
+
# the same arguments a block registered as an event handler with
|
|
139
|
+
# {Channel#register} or {Convenience#on} would receive - namely,
|
|
140
|
+
# the {Event} object and the {Channel#name} that it was fired on.
|
|
141
|
+
#
|
|
142
|
+
def handler(method_name,
|
|
143
|
+
event: method_name,
|
|
144
|
+
channel: nil,
|
|
145
|
+
expand: true)
|
|
146
|
+
@_wires_actor_handler_procs << (
|
|
147
|
+
@_wires_actor_channel.register event, weak:true do |e|
|
|
148
|
+
orig_channel = e.kwargs.delete :_wires_actor_original_channel
|
|
149
|
+
|
|
150
|
+
channel_list = @_wires_actor_coded_channels[channel]
|
|
151
|
+
channel_list = [channel_list] unless channel_list.is_a? Array
|
|
152
|
+
|
|
153
|
+
if channel_list.include? orig_channel
|
|
154
|
+
if expand
|
|
155
|
+
send method_name, *e.args, **e.kwargs, &e.codeblock
|
|
156
|
+
else
|
|
157
|
+
send method_name, e, orig_channel
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
nil
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# A collection of methods to aid in class definitions that include {Actor}.
|
|
167
|
+
# This module gets extended into the class object by {Actor.included}.
|
|
168
|
+
#
|
|
169
|
+
module ClassMethods
|
|
170
|
+
|
|
171
|
+
# This method is used in class definitions to mark a method as an event
|
|
172
|
+
# handler. This creates a delayed call to {Actor#handler} upon instance
|
|
173
|
+
# object creation inside {#new}.
|
|
174
|
+
#
|
|
175
|
+
# Refer to the argument specification in {Actor#handler}, because
|
|
176
|
+
# arguments are passed on verbatim.
|
|
177
|
+
#
|
|
178
|
+
def handler(*args)
|
|
179
|
+
@_wires_actor_handlers ||= []
|
|
180
|
+
@_wires_actor_handlers << args
|
|
181
|
+
|
|
182
|
+
nil
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# On object creation, transfer any handlers specified with {#handler}
|
|
186
|
+
# in the class definition into the instance object created.
|
|
187
|
+
#
|
|
188
|
+
def new(*args, &block)
|
|
189
|
+
super(*args, &block).tap do |obj|
|
|
190
|
+
obj.instance_eval do
|
|
191
|
+
@_wires_actor_channel = Channel.new Object.new
|
|
192
|
+
@_wires_actor_handler_procs = []
|
|
193
|
+
@_wires_actor_handlers =
|
|
194
|
+
self.class.instance_variable_get(:@_wires_actor_handlers) || []
|
|
195
|
+
|
|
196
|
+
@_wires_actor_handlers.each { |a| handler(*a) }
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
end
|
data/lib/wires/base/channel.rb
CHANGED
|
@@ -10,9 +10,9 @@ module Wires.current_network::Namespace
|
|
|
10
10
|
# level, so registering a hook will affect all instances of {Channel}.
|
|
11
11
|
# There are two hooks types that {Channel} will invoke:
|
|
12
12
|
# * +:@before_fire+ - yields the event object and channel {#name} to the
|
|
13
|
-
# user block before {#fire} invokes {
|
|
13
|
+
# user block before {#fire} invokes {Launcher.spawn} (see source code).
|
|
14
14
|
# * +:@after_fire+ - yields the event object and channel {#name} to the
|
|
15
|
-
# user block after {#fire} invokes {
|
|
15
|
+
# user block after {#fire} invokes {Launcher.spawn} (see source code).
|
|
16
16
|
class Channel
|
|
17
17
|
|
|
18
18
|
# The unique name of the channel, which can be any kind of hashable object.
|
|
@@ -20,7 +20,7 @@ module Wires.current_network::Namespace
|
|
|
20
20
|
# Because it is unique, a reference to the channel may be obtained from
|
|
21
21
|
# the class-level method {Channel.[] Channel[]} using only the {#name}.
|
|
22
22
|
#
|
|
23
|
-
|
|
23
|
+
def name; @name.object end
|
|
24
24
|
|
|
25
25
|
# An array specifying the exception type and string to raise if {#fire} is
|
|
26
26
|
# called, or +nil+ if it's okay to {#fire} from this channel.
|
|
@@ -33,8 +33,6 @@ module Wires.current_network::Namespace
|
|
|
33
33
|
# @return [String] friendly output showing the class and channel {#name}
|
|
34
34
|
def inspect; "#{self.class}[#{name.inspect}]"; end
|
|
35
35
|
|
|
36
|
-
@hub = Hub
|
|
37
|
-
@router = Router::Default
|
|
38
36
|
@new_lock = Monitor.new
|
|
39
37
|
@@aim_lock = Mutex.new # @api private
|
|
40
38
|
|
|
@@ -42,13 +40,6 @@ module Wires.current_network::Namespace
|
|
|
42
40
|
|
|
43
41
|
class << self
|
|
44
42
|
|
|
45
|
-
# The currently selected {Hub} for all channels ({Hub} by default).
|
|
46
|
-
#
|
|
47
|
-
# It is the {Hub}'s responsibility to execute event handlers.
|
|
48
|
-
# @api private
|
|
49
|
-
#
|
|
50
|
-
attr_accessor :hub
|
|
51
|
-
|
|
52
43
|
# The currently selected {Router} for all channels
|
|
53
44
|
# ({Router::Default} by default).
|
|
54
45
|
#
|
|
@@ -78,10 +69,29 @@ module Wires.current_network::Namespace
|
|
|
78
69
|
#
|
|
79
70
|
def new(name)
|
|
80
71
|
channel = @new_lock.synchronize do
|
|
81
|
-
router.get_channel(
|
|
72
|
+
router.get_channel(name) { |name| super(name) }
|
|
82
73
|
end
|
|
83
74
|
end
|
|
75
|
+
|
|
84
76
|
alias_method :[], :new
|
|
77
|
+
|
|
78
|
+
# Forget a channel by {#name}. All registered handlers are forgotten,
|
|
79
|
+
# and a future attempt to access the channel object by name will create
|
|
80
|
+
# a new object.
|
|
81
|
+
#
|
|
82
|
+
# The work of forgetting the object with the given {#name} is delegated
|
|
83
|
+
# to the currently selected {.router}.
|
|
84
|
+
#
|
|
85
|
+
# @param name [#hash] a hashable object of any type as the channel {#name}
|
|
86
|
+
#
|
|
87
|
+
# @return [nil]
|
|
88
|
+
#
|
|
89
|
+
def forget(name)
|
|
90
|
+
@new_lock.synchronize do
|
|
91
|
+
router.forget_channel(name)
|
|
92
|
+
end
|
|
93
|
+
nil
|
|
94
|
+
end
|
|
85
95
|
end
|
|
86
96
|
|
|
87
97
|
# Assigns the given +name+ as the {#name} of this channel object
|
|
@@ -89,8 +99,8 @@ module Wires.current_network::Namespace
|
|
|
89
99
|
# @param name [#hash] a hashable object of any type, unique to this channel
|
|
90
100
|
#
|
|
91
101
|
def initialize(name)
|
|
92
|
-
@name = name
|
|
93
|
-
@handlers =
|
|
102
|
+
@name = RouterTable::Reference.new name
|
|
103
|
+
@handlers = []
|
|
94
104
|
end
|
|
95
105
|
|
|
96
106
|
# Register an event handler to be executed when a matching event occurs.
|
|
@@ -121,36 +131,33 @@ module Wires.current_network::Namespace
|
|
|
121
131
|
# @raise [ArgumentError] if no ampersand-argument or inline block is
|
|
122
132
|
# given as the +&callable+.
|
|
123
133
|
#
|
|
124
|
-
def register(*events, &callable)
|
|
134
|
+
def register(*events, weak:false, &callable)
|
|
125
135
|
raise ArgumentError, "No callable given to execute on event: #{events}" \
|
|
126
136
|
unless callable.respond_to? :call
|
|
127
137
|
events = Event.list_from *events
|
|
128
138
|
|
|
129
|
-
|
|
139
|
+
ref = weak ? Ref::WeakReference.new(callable) :
|
|
140
|
+
Ref::StrongReference.new(callable)
|
|
130
141
|
@@aim_lock.synchronize do
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
142
|
+
self.class.router.hold_channel name if @handlers.empty?
|
|
143
|
+
@handlers << [events, ref]
|
|
144
|
+
callable.extend RegisteredHandler
|
|
145
|
+
callable.register_on_channel self
|
|
135
146
|
end
|
|
136
147
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
148
|
+
callable
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
module RegisteredHandler
|
|
152
|
+
def register_on_channel(channel)
|
|
153
|
+
(@registered_channels ||= []) << channel
|
|
143
154
|
end
|
|
144
155
|
|
|
145
|
-
|
|
146
|
-
callable.singleton_class.send :define_method, :unregister do
|
|
147
|
-
singleton_class.send :remove_method, :unregister
|
|
156
|
+
def unregister
|
|
148
157
|
@registered_channels.each do |c|
|
|
149
158
|
c.unregister &self
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
callable
|
|
159
|
+
end.tap { @registered_channels = [] }
|
|
160
|
+
end
|
|
154
161
|
end
|
|
155
162
|
|
|
156
163
|
# Unregister an event handler that was defined with #register
|
|
@@ -181,7 +188,10 @@ module Wires.current_network::Namespace
|
|
|
181
188
|
#
|
|
182
189
|
def unregister(&callable)
|
|
183
190
|
@@aim_lock.synchronize do
|
|
184
|
-
|
|
191
|
+
!!@handlers.reject! do |stored|
|
|
192
|
+
stored.last.object == callable
|
|
193
|
+
end
|
|
194
|
+
.tap { self.class.router.release_channel name if @handlers.empty? }
|
|
185
195
|
end
|
|
186
196
|
end
|
|
187
197
|
|
|
@@ -191,7 +201,8 @@ module Wires.current_network::Namespace
|
|
|
191
201
|
# containing an array of event patterns followed by the associated Proc.
|
|
192
202
|
#
|
|
193
203
|
def handlers
|
|
194
|
-
@handlers.
|
|
204
|
+
@handlers.reject! { |_, ref| ref.object.nil? }
|
|
205
|
+
@handlers.map { |events, ref| [events, ref.object] }
|
|
195
206
|
end
|
|
196
207
|
|
|
197
208
|
# Fire an event on this channel.
|
|
@@ -254,7 +265,7 @@ module Wires.current_network::Namespace
|
|
|
254
265
|
|
|
255
266
|
# Fire to selected targets
|
|
256
267
|
threads = procs.uniq.map do |pr|
|
|
257
|
-
|
|
268
|
+
Launcher.spawn \
|
|
258
269
|
event, # fired event object event
|
|
259
270
|
self.name, # name of channel fired from
|
|
260
271
|
pr, # proc to execute
|
|
@@ -453,5 +464,8 @@ module Wires.current_network::Namespace
|
|
|
453
464
|
self.class.router.get_receivers self
|
|
454
465
|
end
|
|
455
466
|
|
|
467
|
+
Channel.router = Router::Default
|
|
468
|
+
Router::Default.clear_channels
|
|
469
|
+
Router::Simple.clear_channels
|
|
456
470
|
end
|
|
457
471
|
end
|
data/lib/wires/base/event.rb
CHANGED
|
@@ -40,6 +40,16 @@ module Wires.current_network::Namespace
|
|
|
40
40
|
# Directly access contents of @kwargs by key
|
|
41
41
|
def [](key); @kwargs[key]; end
|
|
42
42
|
|
|
43
|
+
# Duplicate this event, ensuring that the args Array and kwargs Hash are
|
|
44
|
+
# not the same object, and receivers of an event can't modify that
|
|
45
|
+
# Array or Hash in ways that might affect other receivers of the event
|
|
46
|
+
def dup
|
|
47
|
+
super.tap do |e|
|
|
48
|
+
e.args = e.args.dup
|
|
49
|
+
e.kwargs = e.kwargs.dup
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
43
53
|
# Returns true if all meaningful components of two events are equal
|
|
44
54
|
# Use #equal? instead if you want object identity comparison
|
|
45
55
|
def ==(other)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
module Wires.current_network::Namespace
|
|
3
|
-
# An Event
|
|
3
|
+
# An Event Launcher. Event/proc associations come in, and the procs
|
|
4
4
|
# get called in new threads in the order received
|
|
5
|
-
class
|
|
5
|
+
class Launcher
|
|
6
6
|
class << self
|
|
7
7
|
|
|
8
8
|
# Refuse to instantiate; it's a singleton!
|
data/lib/wires/base/router.rb
CHANGED
|
@@ -1,67 +1,113 @@
|
|
|
1
1
|
|
|
2
2
|
module Wires.current_network::Namespace
|
|
3
|
-
|
|
3
|
+
class Channel; end
|
|
4
|
+
|
|
5
|
+
class Router
|
|
4
6
|
|
|
5
|
-
class
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@fuzzy_table = {}
|
|
13
|
-
@star = nil
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def get_channel(chan_cls, name)
|
|
17
|
-
@chan_cls ||= chan_cls
|
|
18
|
-
channel = @table[name] ||= (new_one=true; yield name)
|
|
19
|
-
|
|
20
|
-
if new_one and name.is_a? Regexp then
|
|
21
|
-
@fuzzy_table[name] = channel
|
|
22
|
-
channel.not_firable = [TypeError,
|
|
23
|
-
"Cannot fire on Regexp channel: #{name.inspect}."\
|
|
24
|
-
" Regexp channels can only used in event handlers."]
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
channel
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def get_receivers(chan)
|
|
31
|
-
name = chan.name
|
|
32
|
-
return @table.values if name == '*'
|
|
33
|
-
|
|
34
|
-
@fuzzy_table.each_pair.select do |k,v|
|
|
35
|
-
(begin; name =~ k; rescue TypeError; end)
|
|
36
|
-
end.map { |k,v| v } + [chan, (@star||=@chan_cls['*'])]
|
|
37
|
-
end
|
|
7
|
+
class Category
|
|
8
|
+
def initialize(name)
|
|
9
|
+
@name = name
|
|
10
|
+
@table = RouterTable.new
|
|
11
|
+
@exclusive = true
|
|
12
|
+
@qualifiers = []
|
|
13
|
+
@executions = []
|
|
38
14
|
|
|
15
|
+
yield self if block_given?
|
|
39
16
|
end
|
|
40
|
-
|
|
17
|
+
|
|
18
|
+
attr_reader :name
|
|
19
|
+
attr_reader :table
|
|
20
|
+
attr_accessor :exclusive
|
|
21
|
+
attr_accessor :qualifiers
|
|
22
|
+
attr_accessor :executions
|
|
23
|
+
|
|
24
|
+
def qualify(&block) @qualifiers << block unless block.nil? end
|
|
25
|
+
def execute(&block) @executions << block unless block.nil? end
|
|
41
26
|
end
|
|
42
27
|
|
|
28
|
+
attr_accessor :refreshes
|
|
29
|
+
def refresh(&block) @refreshes << block unless block.nil? end
|
|
43
30
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
31
|
+
def initialize(&block)
|
|
32
|
+
@refreshes = []
|
|
33
|
+
|
|
34
|
+
singleton_class.class_exec self, &block if block
|
|
35
|
+
category :all unless @categories
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def category sym, &block
|
|
39
|
+
@categories ||= []
|
|
40
|
+
@categories << [sym, Category.new(sym, &block)]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def table(key)
|
|
44
|
+
Hash[@categories][key].table
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def clear_channels
|
|
48
|
+
@categories.map(&:last).each { |c| c.table.clear }
|
|
49
|
+
@refreshes.each { |r| instance_eval &r }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def get_channel(name, &block)
|
|
53
|
+
channel = nil
|
|
54
|
+
@categories.map(&:last).each do |c|
|
|
55
|
+
if c.qualifiers.map { |q| instance_exec name, &q }.all?
|
|
56
|
+
channel = (c.table[name] ||= (channel or yield name))
|
|
57
|
+
c.executions.each { |e| instance_exec channel, &e }
|
|
58
|
+
return channel if c.exclusive
|
|
59
59
|
end
|
|
60
|
-
|
|
61
60
|
end
|
|
62
|
-
|
|
61
|
+
channel
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def get_receivers(chan)
|
|
65
|
+
[chan]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def forget_channel(name)
|
|
69
|
+
@categories.map(&:last).each { |c| c.table.delete name }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def hold_channel(name)
|
|
73
|
+
@categories.map(&:last).each { |c| c.table.make_strong name }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def release_channel(name)
|
|
77
|
+
@categories.map(&:last).each { |c| c.table.make_weak name }
|
|
63
78
|
end
|
|
64
79
|
|
|
65
80
|
|
|
81
|
+
Simple = Router.new
|
|
82
|
+
|
|
83
|
+
Default = Router.new do |r|
|
|
84
|
+
r.category :star do |c|
|
|
85
|
+
c.qualify { |name| @star and name == '*' }
|
|
86
|
+
c.execute { |chan| @star = chan }
|
|
87
|
+
end
|
|
88
|
+
r.category :fuzzy do |c|
|
|
89
|
+
c.exclusive = false
|
|
90
|
+
c.qualify { |name| name.is_a? Regexp }
|
|
91
|
+
c.execute { |chan| chan.not_firable = [TypeError,
|
|
92
|
+
"Cannot fire on Regexp channel: #{chan.name.inspect}."\
|
|
93
|
+
" Regexp channels can only used in event handlers."] }
|
|
94
|
+
end
|
|
95
|
+
r.category :main
|
|
96
|
+
|
|
97
|
+
r.refresh { @star = Channel['*'.freeze] }
|
|
98
|
+
|
|
99
|
+
def get_receivers(chan)
|
|
100
|
+
name = chan.name
|
|
101
|
+
if name == '*'
|
|
102
|
+
table(:main).values
|
|
103
|
+
else
|
|
104
|
+
table(:fuzzy).each_pair.select do |k,v|
|
|
105
|
+
(begin; name =~ k; rescue TypeError; end)
|
|
106
|
+
end.map { |k,v| v } + [chan, @star]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
66
111
|
end
|
|
112
|
+
|
|
67
113
|
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
|
|
2
|
+
module Wires.current_network::Namespace
|
|
3
|
+
|
|
4
|
+
class RouterTable
|
|
5
|
+
|
|
6
|
+
class Reference
|
|
7
|
+
attr_reader :ref
|
|
8
|
+
def object; @ref.object; end
|
|
9
|
+
def weak?; @weak end
|
|
10
|
+
|
|
11
|
+
def initialize(obj)
|
|
12
|
+
raise ValueError "#{self.class} referent cannot be nil" if obj.nil?
|
|
13
|
+
|
|
14
|
+
# Make initial weak reference (if possible)
|
|
15
|
+
begin
|
|
16
|
+
@ref = Ref::WeakReference.new obj
|
|
17
|
+
@weak = true
|
|
18
|
+
rescue RuntimeError;
|
|
19
|
+
@ref = Ref::StrongReference.new obj
|
|
20
|
+
@weak = false
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def make_weak
|
|
25
|
+
unless @weak
|
|
26
|
+
@ref = Ref::WeakReference.new @ref.object
|
|
27
|
+
@weak = true
|
|
28
|
+
end
|
|
29
|
+
rescue RuntimeError
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def make_strong
|
|
33
|
+
if @weak
|
|
34
|
+
@ref = Ref::StrongReference.new @ref.object
|
|
35
|
+
@weak = false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def inspect
|
|
40
|
+
"<##{self.class} #{object.nil? ? "#Object not available" : object.inspect}>"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class RouterTable
|
|
48
|
+
include Enumerable
|
|
49
|
+
|
|
50
|
+
def initialize
|
|
51
|
+
@keys = {}
|
|
52
|
+
@key_ids = {}
|
|
53
|
+
@values = {}
|
|
54
|
+
@lock = Mutex.new
|
|
55
|
+
@finalizer = lambda { |id| remove_reference_to_key id }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def [](key)
|
|
59
|
+
@lock.synchronize do
|
|
60
|
+
ref = @values[@key_ids[key.hash]]
|
|
61
|
+
ref.object if ref
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def []=(key, value)
|
|
66
|
+
begin; ObjectSpace.define_finalizer key, @finalizer
|
|
67
|
+
rescue RuntimeError; end
|
|
68
|
+
|
|
69
|
+
@lock.synchronize do
|
|
70
|
+
id = key.object_id
|
|
71
|
+
key = RouterTable::Reference.new key
|
|
72
|
+
value = RouterTable::Reference.new value
|
|
73
|
+
|
|
74
|
+
@keys[id] = key
|
|
75
|
+
@values[id] = value
|
|
76
|
+
@key_ids[key.object.hash] = id
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def delete(key)
|
|
81
|
+
@lock.synchronize do
|
|
82
|
+
id = @key_ids[key.hash]
|
|
83
|
+
@key_ids.delete key.hash
|
|
84
|
+
@keys.delete id
|
|
85
|
+
@values.delete id
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def clear
|
|
90
|
+
@lock.synchronize do
|
|
91
|
+
@keys.clear
|
|
92
|
+
@values.clear
|
|
93
|
+
@key_ids.clear
|
|
94
|
+
end
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def keys; @keys.values.map(&:object) end
|
|
99
|
+
def values; @values.values.map(&:object) end
|
|
100
|
+
|
|
101
|
+
def each
|
|
102
|
+
Enumerator.new do |y|
|
|
103
|
+
@key_ids.values.each do |id|
|
|
104
|
+
key = @keys[id]
|
|
105
|
+
val = @values[id]
|
|
106
|
+
y << [key.object,val.object] unless key.nil? or val.nil?
|
|
107
|
+
end
|
|
108
|
+
end.each
|
|
109
|
+
end
|
|
110
|
+
alias_method :each_pair, :each
|
|
111
|
+
|
|
112
|
+
def make_weak(key)
|
|
113
|
+
@lock.synchronize do
|
|
114
|
+
id = @key_ids[key.hash]
|
|
115
|
+
kref = @keys[id]
|
|
116
|
+
vref = @values[id]
|
|
117
|
+
kref.make_weak if kref
|
|
118
|
+
vref.make_weak if vref
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def make_strong(key)
|
|
123
|
+
@lock.synchronize do
|
|
124
|
+
id = @key_ids[key.hash]
|
|
125
|
+
kref = @keys[id]
|
|
126
|
+
vref = @values[id]
|
|
127
|
+
kref.make_strong if kref
|
|
128
|
+
vref.make_strong if vref
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
def remove_reference_to_key(object_id)
|
|
135
|
+
@lock.synchronize do
|
|
136
|
+
@key_ids.delete_if { |k,v| v==object_id }
|
|
137
|
+
@keys.delete object_id
|
|
138
|
+
@values.delete object_id
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
end
|
metadata
CHANGED
|
@@ -1,29 +1,43 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wires
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joe McIlvain
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2014-02
|
|
11
|
+
date: 2014-04-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: threadlock
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- -
|
|
17
|
+
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: '2.0'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- -
|
|
24
|
+
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: ref
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.0'
|
|
27
41
|
- !ruby/object:Gem::Dependency
|
|
28
42
|
name: bundler
|
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -137,7 +151,6 @@ dependencies:
|
|
|
137
151
|
- !ruby/object:Gem::Version
|
|
138
152
|
version: '0'
|
|
139
153
|
description: A lightweight, extensible asynchronous event routing framework in Ruby.
|
|
140
|
-
Patch your objects together with wires. Inspired by the python 'circuits' framework.
|
|
141
154
|
email: joe.eli.mac@gmail.com
|
|
142
155
|
executables: []
|
|
143
156
|
extensions: []
|
|
@@ -147,15 +160,16 @@ files:
|
|
|
147
160
|
- README.md
|
|
148
161
|
- lib/wires.rb
|
|
149
162
|
- lib/wires/base.rb
|
|
163
|
+
- lib/wires/base/actor.rb
|
|
150
164
|
- lib/wires/base/channel.rb
|
|
151
165
|
- lib/wires/base/convenience.rb
|
|
152
166
|
- lib/wires/base/event.rb
|
|
153
|
-
- lib/wires/base/
|
|
167
|
+
- lib/wires/base/launcher.rb
|
|
154
168
|
- lib/wires/base/router.rb
|
|
155
169
|
- lib/wires/base/time_scheduler.rb
|
|
156
170
|
- lib/wires/base/time_scheduler_item.rb
|
|
157
|
-
- lib/wires/base/util/build_alt.rb
|
|
158
171
|
- lib/wires/base/util/hooks.rb
|
|
172
|
+
- lib/wires/base/util/router_table.rb
|
|
159
173
|
- lib/wires/core_ext.rb
|
|
160
174
|
- lib/wires/core_ext/numeric.rb
|
|
161
175
|
- lib/wires/core_ext/symbol.rb
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
module Wires.current_network::Namespace
|
|
3
|
-
module Util
|
|
4
|
-
|
|
5
|
-
# Build an alternate version of the Wires module that doesn't
|
|
6
|
-
# know about or interfere with the original Wires module.
|
|
7
|
-
# Specify the module_path as a string, starting with global locator '::':
|
|
8
|
-
# >> module MyModule; end
|
|
9
|
-
# >> Wires::Util.build_alt "::MyModule::MyWires"
|
|
10
|
-
def self.build_alt(module_path)
|
|
11
|
-
warn "DEPRECATED: Wires::Util.build_alt('::MyModule::MyWires')"\
|
|
12
|
-
" is deprecated, and will be removed in version 0.6."\
|
|
13
|
-
" Please use ::MyModule::MyWires=Wires.replicate"
|
|
14
|
-
eval "#{module_path} = Wires.replicate"
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
end
|
|
18
|
-
end
|