wires 0.3.8 → 0.4.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: 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