wires 0.5.8 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/jemc/wires.png)](https://travis-ci.org/jemc/wires)
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/wires.png)](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
|