wires 0.3.8 → 0.4.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: 486d20e1c2995dea50118010dd60c4b9c8a5bbd8
4
- data.tar.gz: d93b97c600da203bfcabd55781b80f2e6b0190ab
3
+ metadata.gz: a3c79a4bb96ed3a79e94be39171fda18a4846025
4
+ data.tar.gz: 7dd1087d9f74885e1bbbf4a25d70e07ae98919e9
5
5
  SHA512:
6
- metadata.gz: 204086554b9b668d2ca5d559d99e3abf0173c23de146fb7232d80c9cb728e2f2c0de2dd82cd8ac619e0a5bc010beaf6c6a83e1b0d09e5682b170f418cd8cf598
7
- data.tar.gz: f392ff623d48525508a535ea280d1924ea214f3a9276c1351a7d68d661ee78f70a164185be9585b7f560e0310f47f8ea3aa2dc23a4a59a338cf79d65381d04a8
6
+ metadata.gz: 61b8a2c4881a8fdec36257a7ea4f5bfb2d4831a0157e1a9fbe53897c3d0d83d7746ba655025fbd23903a39af623d9bbf672f798ed5d15344972fa707bedb47bb
7
+ data.tar.gz: 962a6bca1dfa83c565920750b46bc9699915603a1e8a5c7bf00a7984477dad2a6cea79ba5323c06ae1686362f28a45b0b5a45db969b9f44bf12369b2991840a6
data/lib/wires/channel.rb CHANGED
@@ -5,44 +5,29 @@ module Wires
5
5
 
6
6
  attr_reader :name
7
7
  attr_reader :target_list
8
- attr_reader :relevant_channels
8
+ attr_accessor :not_firable
9
9
 
10
- def inspect; "Channel(#{name.inspect})"; end
10
+ def inspect; "#{self.class}(#{name.inspect})"; end
11
11
 
12
- # Redefine this class method to use an alternate Hub
13
- def self.hub; Hub; end
14
- # Don't redefine this instance method!
15
- def hub; self.class.hub; end
12
+ @hub = Hub
13
+ @router = Router
14
+ @new_lock = Mutex.new
15
+ @@aim_lock = Mutex.new
16
16
 
17
- # Give out references to the star channel
18
- def self.channel_star; @@channel_star; end
19
- def channel_star; @@channel_star; end
20
-
21
- # Ensure that there is only one instance of Channel per name
22
- @@new_lock = Mutex.new
23
- def self.new(*args, &block)
24
- (args.include? :recursion_guard) ?
25
- (args.delete :recursion_guard) :
26
- (@@channel_star ||= self.new('*', :recursion_guard))
17
+ class << self
18
+ attr_accessor :hub
19
+ attr_accessor :router
27
20
 
28
- @@new_lock.synchronize do
29
- @@channel_hash ||= Hash.new
30
- @@channel_hash[args[0]] ||= super(*args, &block)
21
+ def new(*args)
22
+ channel = @new_lock.synchronize do
23
+ router.get_channel(self, *args) { |name| super(name) } end
31
24
  end
25
+ alias_method :[], :new
32
26
  end
33
27
 
34
28
  def initialize(name)
35
29
  @name = name
36
- @target_list = Set.new
37
- unless @@channel_hash.empty?
38
- _relevant_init
39
- @@channel_hash.values.each do |c|
40
- c.send(:_test_relevance, self)
41
- _test_relevance c
42
- end
43
- else
44
- @relevant_channels = []
45
- end
30
+ @target_list = []
46
31
  end
47
32
 
48
33
  # Register a proc to be triggered by an event on this channel
@@ -50,18 +35,27 @@ module Wires
50
35
  def register(*events, &proc)
51
36
  if not proc.is_a?(Proc) then raise SyntaxError, \
52
37
  "No Proc given to execute on event: #{events}" end
53
- _normalize_event_list(events)
54
- @target_list << [events, proc]
38
+ events = Event.new_from(*events)
39
+
40
+ @@aim_lock.synchronize do
41
+ @target_list << [events, proc] \
42
+ unless @target_list.include? [events, proc]
43
+ end
44
+
55
45
  proc
56
46
  end
57
47
 
58
48
  # Unregister a proc from the target list of this channel
59
49
  # Return true if at least one matching target was unregistered, else false
60
50
  def unregister(*events, &proc)
61
- _normalize_event_list(events)
62
- !!(@target_list.reject! do |e, p|
63
- (proc and proc==p) and (events.map{|event| e.include? event}.all?)
64
- end)
51
+ events = events.empty? ? [] : Event.new_from(*events)
52
+
53
+ @@aim_lock.synchronize do
54
+ !!(@target_list.reject! do |es,pr|
55
+ (proc and proc==pr) and \
56
+ (events.map{|event| es.map{|e| event=~e}.any?}.all?)
57
+ end)
58
+ end
65
59
  end
66
60
 
67
61
  # Add hook methods
@@ -78,27 +72,47 @@ module Wires
78
72
  end
79
73
 
80
74
  # Fire an event on this channel
81
- def fire(event, blocking:false)
75
+ def fire(input, blocking:false)
82
76
 
83
77
  raise *@not_firable if @not_firable
84
78
 
85
79
  backtrace = caller
86
80
 
87
- # Create an instance object from one of several acceptable input forms
88
- event = Event.new_from event
81
+ event = Event.new_from(*input)
82
+
83
+ case event.count
84
+ when 0
85
+ raise ArgumentError,"Can't create an event from input: #{input.inspect}"
86
+ when 1
87
+ event = event.first
88
+ else
89
+ raise ArgumentError,"Can't fire on multiple events: #{event.inspect}"
90
+ end
89
91
 
90
92
  self.class.run_hooks(:@before_fire, event, self)
91
93
 
92
- # Fire to each relevant target on each channel
93
- for chan in relevant_channels()
94
- for target in chan.target_list
95
- for string in target[0] & event.class.codestrings
96
- self.class.hub.spawn(event, # fired event object event
97
- self.name, # name of channel fired from
98
- target[1], # proc to execute
99
- blocking, # boolean from blocking kwarg
100
- backtrace) # captured backtrace
101
- end end end
94
+ # Select appropriate targets
95
+ procs = []
96
+ @@aim_lock.synchronize do
97
+ self.class.router
98
+ .get_receivers(self).each do |chan|
99
+ chan.target_list.each do |elist, pr|
100
+ elist.each do |e|
101
+ procs << pr if e =~ event
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ # Fire to selected targets
108
+ procs.uniq.each do |pr|
109
+ self.class.hub.spawn \
110
+ event, # fired event object event
111
+ self.name, # name of channel fired from
112
+ pr, # proc to execute
113
+ blocking, # boolean from blocking kwarg
114
+ backtrace # captured backtrace
115
+ end
102
116
 
103
117
  self.class.run_hooks(:@after_fire, event, self)
104
118
 
@@ -109,61 +123,20 @@ module Wires
109
123
  self.fire(event, blocking:true)
110
124
  end
111
125
 
112
- # Convert events to array of unique codestrings
113
- def _normalize_event_list(events)
114
- events = [events] unless events.is_a? Array
115
- events.flatten!
116
- events.map! { |e| (e.is_a?(Class) ? e.codestring : e.to_s) }
117
- events.uniq!
118
- end
119
-
120
- def _relevant_init
121
- @relevant_channels = [@@channel_star]
122
- @my_names = (self.name.is_a? Array) ? self.name : [self.name]
123
- @my_names.map {|o| (o.respond_to? :channel_name) ? o.channel_name : o.to_s}
124
- .flatten(1)
125
- _test_relevance self
126
- end
127
-
128
- def _test_relevance(other_chan)
129
- if self==@@channel_star
130
- @relevant_channels << other_chan
131
- return
132
- end
133
-
134
- for my_name in @my_names
135
-
136
- if my_name.is_a?(Regexp) then
137
- @not_firable = [TypeError,
138
- "Cannot fire on Regexp channel: #{self.name}."\
139
- " Regexp channels can only used in event handlers."]
140
- return
141
- end
142
-
143
- other_name = other_chan.name
144
- other_name = (other_name.respond_to? :channel_name) ? \
145
- other_name.channel_name : other_name
146
-
147
- @relevant_channels << other_chan if \
148
- !@relevant_channels.include?(other_chan) and \
149
- if other_name.is_a?(Regexp)
150
- my_name =~ other_name
151
- else
152
- my_name.to_s == other_name.to_s
153
- end
154
- end
155
- end
156
-
157
- # Compare matching with another Channel
126
+ # Returns true if listening on 'self' would hear a firing on 'other'
127
+ # (not commutative)
158
128
  def =~(other)
159
- (other.is_a? Channel) ? (other.relevant_channels.include? self) : super
129
+ (other.is_a? Channel) ?
130
+ (self.class.router.get_receivers(other).include? self) :
131
+ super
160
132
  end
161
133
 
162
- hub.before_kill(true) do
163
- self.clear_hooks(:@before_fire)
164
- self.clear_hooks(:@after_fire)
134
+ def receivers
135
+ self.class.router.get_receivers self
165
136
  end
166
137
 
138
+ router.clear_channels
139
+
167
140
  end
168
141
 
169
142
  end
@@ -3,25 +3,25 @@ module Wires
3
3
 
4
4
  module Convenience
5
5
 
6
- def Channel(*args) Channel.new(*args) end
7
-
8
- def on(events, channels='*', &codeblock)
9
- channels = [channels] unless channels.is_a? Array
10
- for channel in channels
6
+ def on(events, channels=self, &codeblock)
7
+ [*channels].each do |channel|
11
8
  channel=Channel.new(channel) unless channel.is_a? Channel
9
+
12
10
  channel.register(*events, &codeblock)
13
11
  end
14
12
  codeblock
15
13
  end
16
14
 
17
- def fire(event, channel='*', **kwargs)
18
- channel = Channel.new(channel) unless channel.is_a? Channel
19
- unless kwargs[:time] or (kwargs[:count] and kwargs[:count]!=1)
20
- channel.fire(event, **kwargs)
21
- else
22
- time = kwargs[:time] or Time.now
23
- kwargs.reject!{|k,v| k==:time}
24
- TimeScheduler.add(time, event, channel, **kwargs)
15
+ def fire(event, channels=self, **kwargs)
16
+ [*channels].each do |channel|
17
+ channel = Channel.new(channel) unless channel.is_a? Channel
18
+
19
+ if kwargs[:time] or (kwargs[:count] and kwargs[:count]!=1)
20
+ time = kwargs.delete(:time) or Time.now
21
+ TimeScheduler.add(time, event, channel, **kwargs)
22
+ else
23
+ channel.fire(event, **kwargs)
24
+ end
25
25
  end
26
26
  nil end
27
27
 
@@ -30,28 +30,6 @@ module Wires
30
30
  fire(*args, **kwargs)
31
31
  end
32
32
 
33
-
34
-
35
- class << self
36
- def prefix_methods(prefix)
37
-
38
- return unless prefix
39
- prefix = prefix.to_s
40
-
41
- instance_methods.each do |thing|
42
- thing = thing.to_s
43
- f2 = (prefix+'_'+thing)
44
- f2 = (thing[0]=~/[[:lower:]]/) ? f2.underscore : f2.camelcase
45
- f2 = f2.to_sym; thing = thing.to_sym
46
- alias_method f2, thing
47
- remove_method thing
48
- end
49
-
50
- # remove_method :prefix_methods
51
-
52
- end
53
- end
54
-
55
33
  end
56
34
 
57
35
  end
@@ -1,49 +1,65 @@
1
1
 
2
- # Reopen the Time class and add the fire method to enable nifty syntax like:
3
- # 32.minutes.from_now.fire :event
4
- class ::Time
5
- unless instance_methods.include? :fire
6
- def fire(event, channel='*', **kwargs)
7
- Wires::TimeScheduler << \
8
- Wires::TimeSchedulerItem.new(self, event, channel, **kwargs)
9
- end
10
- end
11
- end
12
-
13
-
14
- # Reopen ActiveSupport::Duration to enable nifty syntax like:
15
- # 32.minutes.from_now do some_stuff end
16
- class ::ActiveSupport::Duration
17
-
18
- unless instance_methods.include? :__original_since
19
- alias_method :__original_since, :since
20
- def since(*args, &block)
2
+ module Wires
3
+ module Convenience
4
+
5
+ @core_ext = true
6
+
7
+ class << self
8
+
9
+ # Set this attribute to false to disable core_ext on include
10
+ attr_accessor :core_ext
21
11
 
22
- if block
23
- on :time_scheduler_anon, block.object_id do |e| block.call(e) end
24
- __original_since(*args).fire(:time_scheduler_anon,
25
- block.object_id)
26
- nil
27
- else
28
- __original_since(*args)
12
+ # Call extend_core on include unless attribute is set to false
13
+ def included(*args)
14
+ super
15
+ self.extend_core if @core_ext
29
16
  end
30
- end
31
- alias :from_now :since
32
- end
33
-
34
- unless instance_methods.include? :__original_ago
35
- alias :__original_ago :ago
36
- def ago(*args, &block)
37
- if block
38
- on :time_scheduler_anon, block.object_id do |e| block.call(e) end
39
- __original_ago(*args).fire(:time_scheduler_anon,
40
- block.object_id)
41
- nil
42
- else
43
- __original_ago(*args)
17
+
18
+ # Add methods to ::Time and ::Numeric
19
+ def extend_core
20
+ # Add Time#fire for timed firing of events
21
+ ::Time.class_eval do
22
+ def fire(event, channel='*', **kwargs)
23
+ Wires::TimeScheduler.add(self, event, channel, **kwargs)
24
+ end
25
+ end
26
+
27
+ # Add Numeric => Numeric time-factor converters
28
+ {
29
+ [:second, :seconds] => '1',
30
+ [:minute, :minutes] => '60',
31
+ [:hour, :hours] => '3600',
32
+ [:day, :days] => '24.hours',
33
+ [:week, :weeks] => '7.days',
34
+ [:fortnight, :fortnights] => '2.weeks',
35
+ }.each_pair do |k,v|
36
+ ::Numeric.class_eval <<-CODE
37
+ def #{k.last}
38
+ self * #{v}
39
+ end
40
+ alias #{k.first.inspect} #{k.last.inspect}
41
+ CODE
42
+ end
43
+
44
+ # Add Numeric => Time converters with implicit anonymous fire
45
+ {
46
+ [:from_now, :since] => '+',
47
+ [:until, :ago] => '-',
48
+ }.each_pair do |k,v|
49
+ ::Numeric.class_eval <<-CODE
50
+ def #{k.last}(time = ::Time.now, &block)
51
+ if block
52
+ Channel[block.object_id].register :time_scheduler_anon, &block
53
+ self.#{k.last}(time).fire(:time_scheduler_anon, block.object_id)
54
+ end
55
+ time #{v} self
56
+ end
57
+ alias #{k.first.inspect} #{k.last.inspect}
58
+ CODE
59
+ end
44
60
  end
61
+
45
62
  end
46
- alias :until :ago
63
+
47
64
  end
48
-
49
- end
65
+ end
data/lib/wires/event.rb CHANGED
@@ -2,166 +2,91 @@
2
2
  module Wires
3
3
 
4
4
  class Event
5
- class << self
6
- def event_registry_create
7
- @@registry = []
8
- event_registry_register
9
- end
10
- def event_registry_register(cls=self)
11
- @@registry << cls
12
- @@registry.uniq!
13
- end
5
+ attr_accessor :event_type
6
+
7
+ # Return a friendly output upon inspection
8
+ def inspect
9
+ list = [*args, **kwargs].map(&:inspect).join ', '
10
+ type = event_type ? event_type.inspect : ''
11
+ "#{self.class}#{type}(#{list})"
14
12
  end
15
- event_registry_create
16
- end
17
-
18
- # All Event classes should inherit from this one
19
- class Event
20
13
 
21
- # Operate on the metaclass as a type of singleton pattern
22
- class << self
23
-
24
- def inherited(subcls)
25
- # Be sure codestring doesn't conflict
26
- existing = _from_codestring(subcls.codestring)
27
- if existing then raise NameError, \
28
- "New Event subclass '#{subcls}' conflicts with"\
29
- " existing Event subclass '#{existing}'."\
30
- " The generated codestring '#{subcls.codestring}'"\
31
- " must be unique for each Event subclass." end
32
-
33
- super
34
- event_registry_register(subcls)
35
- end
36
-
37
- # List of class inheritance lineage back to but excluding Object
38
- def ancestry(cls=self)
39
- _next = cls.superclass
40
- [cls==Object ? [] : [cls, ancestry(_next)]].flatten
41
- end
42
-
43
- # Convert class <ClassNameEvent> to string "class_name"
44
- def codestring(cls=self)
45
- @codestring ||= \
46
- File.basename cls.to_s
47
- .underscore
48
- .gsub(/_event$/, "")
49
- end
50
-
51
- # List of codestrings associated with this event and ancestors
52
- def codestrings
53
- x = ancestry
54
- .map {|cls| cls.codestring}
55
- end
56
-
57
- # Pull class from registry by codestring
58
- # (more reliable than crafting a reverse regexp)
59
- def _from_codestring(str)
60
- return @@registry.select{|e| e.codestring==str}[0]
61
- end; private :_from_codestring
62
-
63
- def from_codestring(str)
64
- cls = _from_codestring(str.to_s)
65
- if not cls then raise NameError,
66
- "No known Event subclass with codestring: '#{str}'" end
67
- cls
68
- end
69
-
70
- # Convert an event from 'array notation' to an Event subclass instance
71
- # TODO: List acceptable input forms here for documentation
72
- def new_from(input)
73
-
74
- # Standardize to array and pull out arguments if they exist
75
- input = [input] unless input.is_a? Array
76
- input, *args = input
77
-
78
- # Create event object from event as an object, class, or symbol/string
79
- event = case input
80
- when Event
81
- input
82
- when Class
83
- input.new(*args) if input <= Event
84
- else
85
- Event.from_codestring(input.to_s).new(*args)
86
- end
87
- end
14
+ # Internalize all *args and **kwargs and &block to be accessed later
15
+ def initialize(*args, **kwargs, &block)
16
+ @ignore = []
17
+ @kwargs = kwargs.dup
88
18
 
89
- # Create attributes and accessors for all arguments to the constructor.
90
- # This is done here rather than in initialize so that the functionality
91
- # will remain if the user developer overrides initialize in the subclass.
92
- def new(*args, &block)
93
- obj = super
94
-
95
- kwargs = args[-1].is_a?(Hash) ? args.pop.dup : Hash.new
96
- kwargs[:kwargs] = kwargs.dup.freeze
97
- kwargs[:args] = args.dup.freeze
98
- kwargs[:codeblock] = block if block
99
- for key in kwargs.keys
100
- att = key.to_s
101
- obj.instance_variable_set("@#{att}", kwargs[key])
102
- class_eval { attr_reader att }
103
- # class_eval { attr_writer att }
104
- end
105
-
106
- obj
107
- end
108
-
19
+ (@kwargs[:args] = args.freeze; @ignore<<:args) \
20
+ unless @kwargs.has_key? :args
21
+ (@kwargs[:codeblock] = block; @ignore<<:codeblock) \
22
+ unless @kwargs.has_key? :codeblock
23
+ @kwargs.freeze
109
24
  end
110
25
 
111
- # Calling super in new with *args will complain if this isn't here
112
- def initialize(*args, &block) end
26
+ # Directly access contents of @kwargs by key
27
+ def [](key); @kwargs[key]; end
113
28
 
114
- end
115
-
116
- #
117
- # Comparison support for Events and Symbols/Strings
118
- #
119
-
120
- # Reopen Event and add comparison functions
121
- class Event
29
+ # Used to fake a sort of read-only openstruct from contents of @kwargs
30
+ def method_missing(sym, *args, &block)
31
+ args.empty? and @kwargs.has_key?(sym) ?
32
+ @kwargs[sym] :
33
+ (sym==:kwargs ? @kwargs.reject{|k| @ignore.include? k} : super)
34
+ end
122
35
 
36
+ # Returns true if listening for 'self' would hear a firing of 'other'
37
+ # (not commutative)
123
38
  def =~(other)
124
39
  (other.is_a? Event) ?
125
40
  ((self.class >= other.class) \
41
+ and (self.event_type.nil? or self.event_type==other.event_type \
42
+ or (self.event_type.is_a? Class and other.event_type.is_a? Class \
43
+ and self.event_type >= other.event_type)) \
126
44
  and (not self.kwargs.each_pair.detect{|k,v| other.kwargs[k]!=v}) \
127
45
  and (not self.args.each_with_index.detect{|a,i| other.args[i]!=a})) :
128
46
  super
129
47
  end
130
48
 
131
- class << self
132
- def ==(other)
133
- other.is_a?(Class) ?
134
- super : codestring==other.to_s
135
- end
136
- def <=(other)
137
- other.is_a?(Class) ?
138
- super : codestrings.include?(other.to_s)
139
- end
140
- def <(other)
141
- other.is_a?(Class) ?
142
- super : (self<=other and not self==other)
143
- end
144
- def >=(other)
145
- other.is_a?(Class) ?
146
- super : Event.from_codestring(other.to_s)<=self
49
+ # Return an array of Event instance objects generated from
50
+ # specially formatted input (see spec/event_spec.rb).
51
+ def self.new_from(*args)
52
+ args.flatten!
53
+ list = []
54
+
55
+ args.each do |x|
56
+ (x.is_a? Hash) ?
57
+ (x.each_pair { |x,y| list << [x,y] }) :
58
+ (list << [x,[]])
147
59
  end
148
- def >(other)
149
- other.is_a?(Class) ?
150
- super : Event.from_codestring(other.to_s)<self
60
+
61
+ list.map! do |type, args|
62
+ case type
63
+ when Event; obj = type
64
+ when Class;
65
+ if type<=Event
66
+ obj = type.new(*args)
67
+ obj.event_type = type unless type==Wires::Event
68
+ end
69
+ when Symbol
70
+ obj = self.new(*args)
71
+ obj.event_type = type
72
+ obj if self==Wires::Event
73
+ end
74
+ obj
75
+ end.tap do |x|
76
+ raise ArgumentError,
77
+ "Invalid event creation input: #{args} \noutput: #{x}" \
78
+ if x.empty? or !x.all?
151
79
  end
152
80
  end
153
- end
154
-
155
- # Autogenerate the inverse comparison functions for Symbol/String
156
- for cls in [Symbol, String]
157
- %w(== < > <= >=).zip(%w(== > < >= <=))
158
- .each do |ops|
159
- op, opinv = ops # unzip operator and inverse operator
160
- cls.class_eval(
161
- "def #{op}(other)\n"\
162
- " (other.is_a?(Class) and other<=Event) ? \n"\
163
- " (other#{opinv}self) : super\n"\
164
- "end\n")
81
+
82
+ # Ensure that self.new_from is not inherited
83
+ def self.inherited(subcls)
84
+ super
85
+ class << subcls
86
+ undef_method :new_from
87
+ end if self == Wires::Event
165
88
  end
89
+
166
90
  end
167
- end
91
+
92
+ end
data/lib/wires/hub.rb CHANGED
@@ -3,11 +3,10 @@ module Wires
3
3
  # An Event Hub. Event/proc associations come in, and the procs
4
4
  # get called in new threads in the order received
5
5
  class self::Hub
6
- # Operate on the metaclass as a type of singleton pattern
7
6
  class << self
8
7
 
9
8
  # Allow user to get/set limit to number of child threads
10
- attr_accessor :max_child_threads
9
+ attr_accessor :max_children
11
10
 
12
11
  private
13
12
 
@@ -17,85 +16,21 @@ module Wires
17
16
  # Moved to a dedicated method for subclass' sake
18
17
  def class_init
19
18
  # @queue = Queue.new
20
- @max_child_threads = nil
21
- @child_threads = Array.new
22
- @child_threads_lock = Monitor.new
23
- @neglected = Array.new
24
- @neglected_lock = Monitor.new
25
- @spawning_count = 0
26
- @spawning_count_lock = Monitor.new
19
+ @max_children = nil
20
+ @children = Array.new
21
+ @children .extend MonitorMixin
22
+ @neglected = Array.new
23
+ @neglected.extend MonitorMixin
27
24
 
28
- @before_runs = Queue.new
29
- @after_runs = Queue.new
30
- @before_kills = Queue.new
31
- @after_kills = Queue.new
32
- @before_fires = []
33
- @after_fires = []
34
-
35
- @please_finish_all = false
25
+ @hold_lock = Monitor.new
36
26
 
37
27
  reset_neglect_procs
38
28
  reset_handler_exception_proc
39
29
 
40
- # at_exit { (sleep 0.05 until dead?) unless $! }
41
-
42
- state_machine_init
43
-
44
30
  nil end
45
31
 
46
32
  public
47
33
 
48
- def dead?; state==:dead end
49
- def alive?; state==:alive end
50
-
51
- ##
52
- # Start the Hub to allow task spawning.
53
- #
54
- def run(*flags)
55
- sleep 0 until @spawning_count <= 0
56
- @spawning_count_lock.synchronize do
57
- sleep 0 until request_state :alive
58
- end
59
- spawn_neglected_task_threads
60
- join_children
61
- nil end
62
-
63
- ##
64
- # Kill the Hub event loop (optional flags change thread behavior)
65
- #
66
- # valid flags:
67
- # [+:nonblocking+]
68
- # Without this flag, calling thread will be blocked
69
- # until Hub thread is done
70
- # [+:purge_tasks+]
71
- # Without this flag, Hub thread won't be done
72
- # until all child threads are done
73
- def kill(*flags)
74
- sleep 0 until @spawning_count <= 0
75
- @please_finish_all = (not flags.include? :purge_tasks)
76
- sleep 0 until request_state :dead unless (flags.include? :nonblocking)
77
- nil end
78
-
79
- # Add hook methods
80
- include Hooks
81
-
82
- def before_run(*args, &proc)
83
- add_hook(:@before_run, *args, &proc)
84
- end
85
-
86
- def after_run(*args, &proc)
87
- add_hook(:@after_run, *args, &proc)
88
- end
89
-
90
- def before_kill(*args, &proc)
91
- add_hook(:@before_kill, *args, &proc)
92
- end
93
-
94
- def after_kill(*args, &proc)
95
- add_hook(:@after_kill, *args, &proc)
96
- end
97
-
98
-
99
34
  def on_neglect(&block)
100
35
  @on_neglect=block
101
36
  nil end
@@ -104,7 +39,7 @@ module Wires
104
39
  nil end
105
40
  def on_handler_exception(&block)
106
41
  @on_handler_exception=block
107
- end
42
+ nil end
108
43
 
109
44
  def reset_neglect_procs
110
45
  @on_neglect = Proc.new do |args|
@@ -117,18 +52,23 @@ module Wires
117
52
 
118
53
  def reset_handler_exception_proc
119
54
  @on_handler_exception = Proc.new { raise }
55
+ nil end
56
+
57
+ # Execute a block while neglecting all child threads
58
+ def hold
59
+ @hold_lock.synchronize { yield }
60
+ spawn_neglected_task_threads
120
61
  end
121
62
 
122
63
  # Spawn a task
123
- def spawn(*args) # :args: event, ch_string, proc, blocking, fire_bt
124
-
125
- @spawning_count_lock.synchronize { @spawning_count += 1 }
64
+ def spawn(*args) # :args: event, chan, proc, blocking, fire_bt
126
65
 
127
- return neglect(*args) if dead?
66
+ return neglect(*args) \
67
+ if @hold_lock.instance_variable_get(:@mon_mutex).locked?
128
68
 
129
- event, ch_string, proc, blocking, fire_bt = *args
130
- *proc_args = event, ch_string
131
- *exc_args = event, ch_string, fire_bt
69
+ event, chan, proc, blocking, fire_bt = *args
70
+ *proc_args = event, chan
71
+ *exc_args = event, chan, fire_bt
132
72
 
133
73
  # If blocking, run the proc in this thread
134
74
  if blocking
@@ -142,78 +82,59 @@ module Wires
142
82
  end
143
83
 
144
84
  # If not blocking, clear old threads and spawn a new thread
145
- new_thread = nil
146
-
147
- @child_threads_lock.synchronize do
148
-
149
- # Clear out dead threads
150
- @child_threads.select!{|t| t.status}
151
-
85
+ Thread.exclusive do
152
86
  begin
153
87
  # Raise ThreadError for user-set thread limit to mimic OS limit
154
- raise ThreadError if (@max_child_threads) and \
155
- (@max_child_threads <= @child_threads.size)
88
+ raise ThreadError if (@max_children) and \
89
+ (@max_children <= @children.count)
156
90
  # Start the new child thread; follow with chain of neglected tasks
157
- new_thread = Thread.new do
91
+ @children << Thread.new do
158
92
  begin
159
93
  proc.call(*proc_args)
160
94
  rescue Exception => exc
161
95
  unhandled_exception(exc, *exc_args)
162
96
  ensure
163
97
  spawn_neglected_task_chain
98
+ @children.synchronize { @children.delete Thread.current }
164
99
  end
165
100
  end
166
101
 
167
102
  # Capture ThreadError from either OS or user-set limitation
168
103
  rescue ThreadError; return neglect(*args) end
169
104
 
170
- @child_threads << new_thread
171
- return new_thread
105
+ return @children.last
172
106
  end
173
107
 
174
- ensure
175
- @spawning_count_lock.synchronize { @spawning_count -= 1 }
176
108
  end
177
109
 
178
- def purge_neglected
179
- @neglected_lock.synchronize do
180
- @neglected.clear
181
- end
182
- nil end
183
-
184
- def number_neglected
185
- @neglected_lock.synchronize do
186
- @neglected.size
187
- end
188
- end
110
+ def clear_neglected; @neglected.synchronize { @neglected.clear; nil } end
111
+ def count_neglected; @neglected.synchronize { @neglected.count } end
189
112
 
190
113
  # Join child threads, one by one, allowing more children to appear
191
114
  def join_children
192
115
  a_thread = Thread.new{nil}
193
116
  while a_thread
194
- @child_threads_lock.synchronize do
195
- a_thread = @child_threads.shift
117
+ @children.synchronize do
118
+ a_thread = @children.shift
196
119
  end
197
120
  a_thread.join if ((a_thread) and (a_thread!=Thread.current))
198
- sleep 0 # Yield to other threads
121
+ Thread.pass
199
122
  end
200
123
  nil end
201
124
 
202
125
  private
203
126
 
204
127
  # Send relevant data to a custom exception handler
205
- def unhandled_exception(exception, event, ch_string, fire_bt)
206
-
128
+ def unhandled_exception(exception, event, chan, fire_bt)
207
129
  class << exception; attr_reader :fire_backtrace; end
208
130
  exception.instance_variable_set(:@fire_backtrace, fire_bt.dup)
209
131
 
210
- @on_handler_exception.call(exception, event, ch_string)
211
-
132
+ @on_handler_exception.call(exception, event, chan)
212
133
  end
213
134
 
214
135
  # Temporarily neglect a task until resources are available to run it
215
136
  def neglect(*args)
216
- @neglected_lock.synchronize do
137
+ @neglected.synchronize do
217
138
  @on_neglect.call(*args)
218
139
  @neglected << args
219
140
  end
@@ -221,7 +142,7 @@ module Wires
221
142
 
222
143
  # Run a chain of @neglected tasks in place until no more are waiting
223
144
  def spawn_neglected_task_chain
224
- args = @neglected_lock.synchronize do
145
+ args = @neglected.synchronize do
225
146
  return nil if @neglected.empty?
226
147
  ((@neglected.shift)[0...-1]<<true) # Call with blocking
227
148
  end
@@ -233,7 +154,7 @@ module Wires
233
154
  # Flush @neglected task queue, each in a new thread
234
155
  def spawn_neglected_task_threads
235
156
  until (cease||=false)
236
- args = @neglected_lock.synchronize do
157
+ args = @neglected.synchronize do
237
158
  break if (cease = @neglected.empty?)
238
159
  ((@neglected.shift)[0...-1]<<false) # Call without blocking
239
160
  end
@@ -245,49 +166,6 @@ module Wires
245
166
 
246
167
  end
247
168
 
248
-
249
- #***
250
- # Initialize state machine properties
251
- #***
252
- class << self
253
- include Hegemon
254
-
255
- # Protect Hub users methods that could cause deadlock
256
- # if called from inside an event
257
- private :state_obj,
258
- :state_objs,
259
- :request_state,
260
- :update_state,
261
- :do_state_tasks,
262
- :iter_hegemon_auto_loop,
263
- :start_hegemon_auto_thread,
264
- :join_hegemon_auto_thread,
265
- :end_hegemon_auto_thread
266
-
267
- def state_machine_init
268
-
269
- impose_state :dead
270
-
271
- declare_state :dead do
272
- transition_to :alive do
273
- before { flush_hooks :@before_run }
274
- after { flush_hooks :@after_run }
275
- end
276
- end
277
-
278
- declare_state :alive do
279
- transition_to :dead do
280
- before { flush_hooks :@before_kill }
281
- before { purge_neglected }
282
- before { join_children if @please_finish_all }
283
- after { @please_finish_all = false }
284
- after { flush_hooks :@after_kill }
285
- end
286
- end
287
-
288
- end
289
- end
290
-
291
169
  class_init
292
170
  end
293
171
  end
@@ -0,0 +1,42 @@
1
+
2
+ module Wires
3
+
4
+ class Router
5
+
6
+ @table = Hash.new
7
+
8
+ class << self
9
+
10
+ attr_accessor :table
11
+
12
+ def clear_channels()
13
+ @initialized = true
14
+ @table = {}
15
+ @fuzzy_table = {}
16
+ Channel['*']
17
+ end
18
+
19
+ def get_channel(chan_cls, name)
20
+ channel = @table[name] ||= (new_one=true; yield name)
21
+
22
+ if new_one and name.is_a? Regexp then
23
+ @fuzzy_table[name] = channel
24
+ channel.not_firable = [TypeError,
25
+ "Cannot fire on Regexp channel: #{name.inspect}."\
26
+ " Regexp channels can only used in event handlers."]
27
+ end
28
+
29
+ channel
30
+ end
31
+
32
+ def get_receivers(chan)
33
+ name = chan.name
34
+ @fuzzy_table.keys.select do |k|
35
+ (begin; name =~ k; rescue TypeError; end)
36
+ end.map { |k| @fuzzy_table[k] } + [chan, @table['*']]
37
+ end
38
+
39
+ end
40
+ end
41
+
42
+ end
data/lib/wires/time.rb CHANGED
@@ -1,11 +1,12 @@
1
1
 
2
2
  module Wires
3
3
 
4
- class TimeSchedulerAnonEvent < Event; end
4
+ class TimeSchedulerAnonEvent < Event; end
5
5
 
6
6
  class TimeSchedulerItem
7
7
 
8
8
  attr_reader :time, :event, :channel, :interval
9
+ attr_accessor :schedulers
9
10
 
10
11
  def initialize(time, event, channel='*',
11
12
  interval:0.seconds, count:1,
@@ -33,18 +34,20 @@ module Wires
33
34
  @interval = interval
34
35
 
35
36
  @event = Event.new_from(event)
36
- @channel = Channel.new(channel) unless channel.is_a? Channel
37
+ @channel = channel.is_a?(Channel) ? channel : Channel.new(channel)
37
38
  @kwargs = kwargs
39
+
40
+ @schedulers = []
38
41
  end
39
42
 
40
43
  def active?; @active end
41
- def inactive?; not @active end
44
+ def inactive?; !@active end
42
45
 
43
- def ready?(at_time=Time.now); @active and at_time and (at_time>=@time) end
46
+ def ready?(at_time=Time.now); @active and (at_time>=@time) end
44
47
 
45
- def time_until; (@active ? [(Time.now - @time), 0].max : nil) end
48
+ def time_until; (@active ? [(@time - Time.now), 0].max : nil) end
46
49
 
47
- def cancel; @active = false ;nil end
50
+ def cancel; self.count=0 ;nil end
48
51
 
49
52
  # Get/set @count (and apply constraints on set)
50
53
  def count; @count end
@@ -60,24 +63,22 @@ module Wires
60
63
  @channel.fire(@event, **(@kwargs.merge(kwargs)))
61
64
  count_dec
62
65
  @time += @interval if @active
66
+ notify_schedulers
63
67
  nil end
64
68
 
65
69
  # Fire the event only if it is ready
66
70
  def fire_if_ready(**args); self.fire(**kwargs) if ready? end
67
71
 
68
- # Block until event is ready
69
- def wait_until_ready; sleep 0 until ready? end
72
+ private
70
73
 
71
- # Block until event is ready, then fire and block until it is done
72
- def fire_when_ready(**kwargs);
73
- wait_until_ready
74
- self.fire(**kwargs)
75
- end
74
+ def notify_schedulers; @schedulers.each &:refresh end
76
75
 
77
- # Lock (almost) all instance methods with common re-entrant lock
78
- threadlock instance_methods(false)-[\
79
- :wait_until_ready,
80
- :fire_when_ready]
76
+ # Lock some of the methods to try to make them atomic
77
+ # Must exclude methods that get called from within the TimeScheduler lock
78
+ threadlock :fire,
79
+ :count=,
80
+ :count_inc,
81
+ :count_dec
81
82
  end
82
83
 
83
84
  # A singleton class to schedule future firing of events
@@ -85,142 +86,58 @@ module Wires
85
86
  @schedule = Array.new
86
87
  @thread = Thread.new {nil}
87
88
  @schedule_lock = Monitor.new
88
- @dont_sleep = false
89
-
90
- @grain = 1.seconds
89
+ @cond = @schedule_lock.new_cond
91
90
 
92
- # Operate on the metaclass as a type of singleton pattern
93
91
  class << self
94
92
 
95
- attr_accessor :grain
96
-
97
93
  # Add an event to the schedule
98
94
  def add(*args)
99
- new_item = (args.first.is_a? TimeSchedulerItem) ?
100
- (args.first) :
101
- (TimeSchedulerItem.new(*args))
102
- schedule_add(new_item)
103
- nil end
95
+ new_item = args.first
96
+ new_item = (TimeSchedulerItem.new *args) \
97
+ unless new_item.is_a? TimeSchedulerItem
98
+
99
+ new_item.schedulers << self
100
+ schedule_update new_item
101
+ new_item
102
+ end
104
103
 
105
104
  # Add an event to the schedule using << operator
106
105
  def <<(arg); add(*arg); end
107
106
 
108
107
  # Get a copy of the event schedule from outside the class
109
- def list; @schedule.clone end
108
+ def list; @schedule_lock.synchronize { @schedule.dup } end
110
109
  # Clear the event schedule from outside the class
111
- def clear; schedule_clear end
110
+ def clear; @schedule_lock.synchronize { @schedule.clear } end
111
+ # Make the scheduler wake up and re-evaluate
112
+ def refresh; schedule_update end
112
113
 
113
114
  private
114
-
115
- def schedule_clear
116
- @schedule.clear
117
- end
118
-
119
- def schedule_reshuffle
120
- @schedule.select! {|x| x.active?}
121
- @schedule.sort! {|a,b| a.time <=> b.time}
122
- nil end
123
115
 
124
- def schedule_add(new_item)
125
-
126
- if @keepgoing
127
- if new_item.ready?
128
- loop do
129
- new_item.fire
130
- break unless new_item.ready?
131
- end
132
- end
133
-
134
- if new_item.ready?(@next_pass)
135
- Thread.new do
136
- loop do
137
- new_item.fire_when_ready(blocking:true)
138
- break unless new_item.ready?(@next_pass)
139
- end
140
- end
141
- end
116
+ def schedule_update(item_to_add=nil)
117
+ @schedule_lock.synchronize do
118
+ @schedule << item_to_add if item_to_add
119
+ @schedule.select! {|x| x.active?}
120
+ @schedule.sort! {|a,b| a.time <=> b.time}
121
+ @cond.broadcast
142
122
  end
143
-
144
- @schedule << new_item
145
- schedule_reshuffle
146
-
147
- nil end
148
-
149
- def schedule_concat(other_list)
150
- @schedule.concat other_list
151
- schedule_reshuffle
152
123
  nil end
153
124
 
154
- def schedule_pull
155
- pending_now = Array.new
156
- pending_soon = Array.new
157
- while ((not @schedule.empty?) and @schedule[0].ready?)
158
- pending_now << @schedule.shift
159
- end
160
- while ((not @schedule.empty?) and @schedule[0].ready?(@next_pass))
161
- pending_soon << @schedule.shift
162
- end
163
- return [pending_now, pending_soon]
164
- end
165
-
166
- def schedule_next_pass
167
- @next_pass = Time.now+@grain
168
- end
169
-
170
- # Put all functions dealing with @schedule under @schedule_lock
171
- threadlock :list,
172
- :schedule_clear,
173
- :schedule_reshuffle,
174
- :schedule_add,
175
- :schedule_concat,
176
- :schedule_pull,
177
- :schedule_next_pass,
178
- lock: :@schedule_lock
179
-
180
125
  def main_loop
181
-
182
- # @keepgoing = true
183
- pending = Array.new
184
- on_deck = nil
185
-
186
- while @keepgoing
187
-
188
- schedule_next_pass
189
-
190
- # Pull, fire, and requeue relevant events
191
- pending_now, pending_soon = schedule_pull
192
- pending_now.each { |x| x.fire }
193
- pending_soon.each{ |x| Thread.new{ x.fire_when_ready(blocking:true) }}
194
- # schedule_concat pending_now
195
-
196
- sleep [@next_pass-Time.now, 0].max
126
+ pending = []
127
+ loop do
128
+ @schedule_lock.synchronize do
129
+ timeout = (@schedule.first.time_until unless @schedule.empty?)
130
+ @cond.wait timeout
131
+ pending = @schedule.take_while &:ready?
132
+ end
133
+ pending.each &:fire
197
134
  end
198
-
199
135
  nil end
200
136
 
201
137
  end
202
138
 
203
- # Start the main loop upon run of Hub
204
- Hub.after_run(true) do
205
- @keepgoing = true
206
- @thread = Thread.new { main_loop }
207
- end
208
-
209
- # Stop the main loop upon death of Hub
210
- Hub.before_kill(true) do
211
- Thread.exclusive do
212
- @keepgoing=false
213
- @next_pass=Time.now
214
- @thread.wakeup
215
- end
216
- @thread.join
217
- schedule_clear
218
- end
139
+ @thread = Thread.new { main_loop }
219
140
 
220
141
  end
221
142
 
222
143
  end
223
-
224
-
225
- # TODO: Repeatable event sugar?
226
- # TODO: Tests for all new functionality
data/lib/wires.rb CHANGED
@@ -1,18 +1,14 @@
1
- require 'set'
1
+
2
2
  require 'thread'
3
- require 'active_support/core_ext' # Convenience functions from Rails
4
3
  require 'threadlock' # Easily add re-entrant lock to instance methods
5
- require 'hegemon' # State machine management
6
-
7
- require 'wires/util/expect_type'
8
- require 'wires/util/hooks'
9
-
10
- require 'wires/event'
11
- require 'wires/hub'
12
- require 'wires/channel'
13
- require 'wires/time'
14
4
 
15
- require 'wires/core_ext'
16
- require 'wires/convenience'
17
- include Wires::Convenience # require 'wires/clean' to uninclude Convenience
5
+ require_relative 'wires/util/expect_type'
6
+ require_relative 'wires/util/hooks'
18
7
 
8
+ require_relative 'wires/event'
9
+ require_relative 'wires/hub'
10
+ require_relative 'wires/router'
11
+ require_relative 'wires/channel'
12
+ require_relative 'wires/time'
13
+ require_relative 'wires/core_ext'
14
+ require_relative 'wires/convenience'
metadata CHANGED
@@ -1,43 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wires
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.8
4
+ version: 0.4.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: 2013-09-03 00:00:00.000000000 Z
11
+ date: 2013-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: activesupport-core-ext
14
+ name: threadlock
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ~>
18
18
  - !ruby/object:Gem::Version
19
- version: '4.0'
19
+ version: '1.2'
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
- version: '4.0'
27
- - !ruby/object:Gem::Dependency
28
- name: hegemon
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ~>
32
- - !ruby/object:Gem::Version
33
- version: 0.0.8
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ~>
39
- - !ruby/object:Gem::Version
40
- version: 0.0.8
26
+ version: '1.2'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: rake
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -80,20 +66,6 @@ dependencies:
80
66
  - - '>='
81
67
  - !ruby/object:Gem::Version
82
68
  version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: starkfish
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - '>='
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - '>='
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
69
  description: An asynchronous (threaded) event routing framework in Ruby. Patch your
98
70
  objects together with wires. Inspired by the python 'circuits' framework.
99
71
  email: joe.eli.mac@gmail.com
@@ -104,8 +76,8 @@ files:
104
76
  - lib/wires.rb
105
77
  - lib/wires/time.rb
106
78
  - lib/wires/hub.rb
79
+ - lib/wires/router.rb
107
80
  - lib/wires/core_ext.rb
108
- - lib/wires/clean.rb
109
81
  - lib/wires/util/expect_type.rb
110
82
  - lib/wires/util/hooks.rb
111
83
  - lib/wires/convenience.rb
@@ -115,7 +87,7 @@ files:
115
87
  - README.md
116
88
  homepage: https://github.com/jemc/wires/
117
89
  licenses:
118
- - 'Copyright (c) Joe McIlvain. All rights reserved '
90
+ - Copyright 2013 Joe McIlvain. All rights reserved.
119
91
  metadata: {}
120
92
  post_install_message:
121
93
  rdoc_options: []
data/lib/wires/clean.rb DELETED
@@ -1,9 +0,0 @@
1
- require 'wires'
2
-
3
- class << self
4
- target_owner = Wires::Convenience
5
- target_owner.instance_methods(false)
6
- .select { |sym| respond_to?(sym) }
7
- .select { |sym| method(sym).owner==target_owner }
8
- .each { |sym| undef_method sym }
9
- end