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 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