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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5b26a6c6020e7909d89cd9a3f87da0e721ffe9ea
4
- data.tar.gz: 021904136183e2874fb7cc9e054642cf1a8dd04b
3
+ metadata.gz: ed1477b941ed3c64b10f1d6d7f0087fc17f07aad
4
+ data.tar.gz: db91d0d10df1770328f942a8f4febe4be4c90502
5
5
  SHA512:
6
- metadata.gz: 46a6c48fe72d56da690f27811eac4087560c1b7eb24e53c1e066543eb3036add3965706092a19ba6dfd6a7275f7a6a7a74c8cda5318426b3fff3b747ac00f081
7
- data.tar.gz: f2d03f3ff04f99d04158b6170f02b44f661b1bc171c1f275a0898e95bbeda923a5f080a011ee6b1b87545b03321f2d81fd2e298b9d7d20a96731f0ce202e7b12
6
+ metadata.gz: d179bf2b1d6d0642ef84483c9f8521f49f8628bd676dd11a87da1d9108da152827ed657887e43dd6458ddaa10eb7b027941644578243844c8370249584dfb20c
7
+ data.tar.gz: f88af8249cceebf4774de1ba33e60bb9f203d5d0da6f13fdadb128ea0fc244cbb35bb717e50c0f573b3feea95dcf73418897bc8122236e612fa85ce0d20ccda5
data/LICENSE CHANGED
@@ -1,2 +1,2 @@
1
- Copyright (c) 2013 : Joe McIlvain
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.
@@ -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/build_alt'
48
+ loader.call 'base/util/router_table'
48
49
 
49
50
  loader.call 'base/event'
50
- loader.call 'base/hub'
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 &reg
110
+ coded_channels.values.each &reg
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
@@ -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 {Hub.spawn} (see source code).
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 {Hub.spawn} (see source code).
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
- attr_reader :name
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(self, name) { |name| super(name) }
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
- # Register the events under the callable in the @handlers hash
139
+ ref = weak ? Ref::WeakReference.new(callable) :
140
+ Ref::StrongReference.new(callable)
130
141
  @@aim_lock.synchronize do
131
- ary = (@handlers.has_key?(callable) ?
132
- @handlers[callable] :
133
- @handlers[callable] = [])
134
- events.each { |e| ary << e unless ary.include? e }
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
- # Insert the @registered_channels variable into the callable
138
- channels = callable.instance_variable_get(:@registered_channels)
139
- if channels
140
- channels << self
141
- else
142
- callable.instance_variable_set(:@registered_channels, [self])
148
+ callable
149
+ end
150
+
151
+ module RegisteredHandler
152
+ def register_on_channel(channel)
153
+ (@registered_channels ||= []) << channel
143
154
  end
144
155
 
145
- # Insert the #unregister method into the callable
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 unless callable.respond_to? :unregister
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
- !!(@handlers.delete callable)
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.map { |callable, events| [events, callable] }
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
- self.class.hub.spawn \
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
@@ -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 Hub. Event/proc associations come in, and the procs
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 Hub
5
+ class Launcher
6
6
  class << self
7
7
 
8
8
  # Refuse to instantiate; it's a singleton!
@@ -1,67 +1,113 @@
1
1
 
2
2
  module Wires.current_network::Namespace
3
- module Router
3
+ class Channel; end
4
+
5
+ class Router
4
6
 
5
- class Default
6
- class << self
7
- # Refuse to instantiate; it's a singleton!
8
- private :new
9
-
10
- def clear_channels()
11
- @table = {}
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
- clear_channels
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
- class Simple
45
- class << self
46
- # Refuse to instantiate; it's a singleton!
47
- private :new
48
-
49
- def clear_channels()
50
- @table = {}
51
- end
52
-
53
- def get_channel(chan_cls, name)
54
- @table[name] ||= yield name
55
- end
56
-
57
- def get_receivers(chan)
58
- [chan]
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
- clear_channels
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.5.8
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-05 00:00:00.000000000 Z
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/hub.rb
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