wires 0.1.12 → 0.2.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: 00826eb63c92ee41f33d4b9afca45cf79f6074ef
4
- data.tar.gz: 7547996adc41b8f58905b22b50f317f403ef56b3
3
+ metadata.gz: c13e2e944a09925c428bbcfd549162c0dd64a782
4
+ data.tar.gz: a7addc2fbf45a815e96ead41faf7bc9bd9f9e006
5
5
  SHA512:
6
- metadata.gz: 64f55f73930e572d8fdb589a8c84f302396a87ff56ec851e0a6fe24b7666dd8f572ee6638169a0571728e6364ea9b85d38d6d843b133656690cd22072cd00e8c
7
- data.tar.gz: efac20e8fc999a1e219ff778ee92cbeb0c5ebfd2e520708b382bd86411a6133c6ceac8ab7084620439e6c8ae4a4ff98bdece2136aa3f4bcc9a73dc3bb968bf71
6
+ metadata.gz: d175ae2bc54184972ca61169961256df0ff323a1fa0bbd77cdb328932d9f6f6a224902d181a23e8824bf7d57a7e715a8ffd5ace9ebab1978ea065a01d62afb23
7
+ data.tar.gz: f65e946b8aa4ac6ec4ceb3510d3ecb1ee65669e570f9989b6f276471386b7f3f9fb849c51b1c64d2db756ad2d73397817f4f81e332f5857aa07dba7d8f7f564b
data/lib/wires.rb CHANGED
@@ -4,7 +4,7 @@ require 'active_support/core_ext' # Convenience functions from Rails
4
4
  require 'threadlock' # Easily add re-entrant lock to instance methods
5
5
 
6
6
  require 'wires/expect_type'
7
- require 'wires/events'
7
+ require 'wires/event'
8
8
  require 'wires/hub'
9
- require 'wires/channels'
9
+ require 'wires/channel'
10
10
  require 'wires/time'
@@ -0,0 +1,109 @@
1
+
2
+ def on(events, channels='*', &codeblock)
3
+ channels = [channels] unless channels.is_a? Array
4
+ for channel in channels
5
+ Wires::Channel.new(channel).register(events, codeblock)
6
+ end
7
+ nil end
8
+
9
+
10
+ def fire(event, channel='*')
11
+ Wires::Channel.new(channel).fire(event, blocking:false)
12
+ nil end
13
+
14
+ def fire_and_wait(event, channel='*')
15
+ Wires::Channel.new(channel).fire(event, blocking:true)
16
+ nil end
17
+
18
+
19
+ def Channel(*args) Wires::Channel.new(*args) end
20
+
21
+ module Wires
22
+ class Channel
23
+
24
+ attr_reader :name
25
+ attr_reader :target_list
26
+
27
+ def initialize(name)
28
+ @name = name
29
+ @target_list = Set.new
30
+ nil end
31
+
32
+
33
+ # Redefine this class method to use an alternate Hub
34
+ def self.hub; Hub; end
35
+ # Don't redefine this instance method!
36
+ def hub; self.class.hub; end
37
+
38
+ # Channel registry hash and star channel reference are values
39
+ # In this Hash with the key being the reference to the Hub
40
+ @@channel_hash = Hash.new
41
+ @@channel_star = Hash.new
42
+
43
+ # Give out references to the star channel
44
+ def self.channel_star; @@channel_star[self.hub]; end
45
+ def channel_star; @@channel_star[self.hub]; end
46
+
47
+ # Ensure that there is only one instance of Channel per name
48
+ @@new_lock = Mutex.new
49
+ def self.new(*args, &block)
50
+ @@channel_star[self.hub] ||= Channel.new('*') unless (args[0]=='*')
51
+ @@new_lock.synchronize do
52
+ @@channel_hash[self.hub] ||= Hash.new
53
+ @@channel_hash[self.hub][args[0]] ||= super(*args, &block)
54
+ end
55
+ end
56
+
57
+ # Register a proc to be triggered by an event on this channel
58
+ def register(events, proc)
59
+
60
+ if not proc.is_a?(Proc) then raise SyntaxError, \
61
+ "No Proc given to execute on event: #{events}" end
62
+
63
+ # Convert all events to strings
64
+ events = [events] unless events.is_a? Array
65
+ events.flatten!
66
+ events.map! { |e| (e.is_a?(Class) ? e.codestring : e.to_s) }
67
+ events.uniq!
68
+
69
+ @target_list << [events, proc]
70
+ nil end
71
+
72
+ # Fire an event on this channel
73
+ def fire(event, blocking:false)
74
+
75
+ # Create an instance object from one of several acceptable input forms
76
+ event = Event.new_from event
77
+
78
+ # Fire to each relevant target on each channel
79
+ for chan in relevant_channels()
80
+ for target in chan.target_list
81
+ for string in target[0] & event.class.codestrings
82
+ self.class.hub << [string, event, blocking, *target[1..-1]]
83
+ end end end
84
+
85
+ nil end
86
+
87
+ def relevant_channels
88
+ return @@channel_hash[hub].values if self==channel_star
89
+
90
+ if self.name.is_a?(Regexp) then raise TypeError,
91
+ "Cannot fire on Regexp channel: #{self.name}."\
92
+ " Regexp channels can only used in event handlers." end
93
+
94
+ relevant = [channel_star]
95
+ for c in @@channel_hash[hub].values
96
+ relevant << c if \
97
+ if c.name.is_a?(Regexp)
98
+ self.name =~ c.name
99
+ elsif (defined?(c.name.channel_name) and
100
+ defined?(self.name.channel_name))
101
+ self.name.channel_name == c.name.channel_name
102
+ else
103
+ self.name.to_s == c.name.to_s
104
+ end
105
+ end
106
+ return relevant.uniq
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,161 @@
1
+
2
+ module Wires
3
+ # Store a list of all Event classes that get loaded.
4
+ class EventRegistry
5
+ @@registry = []
6
+
7
+ def self.<<(cls)
8
+ @@registry << cls
9
+ @@registry.uniq!
10
+ end
11
+
12
+ def self.list
13
+ @@registry
14
+ end
15
+ end
16
+
17
+ # All Event classes should inherit from this one
18
+ class Event < Object # explicit for the sake of Event.ancestry
19
+
20
+ # Register with the EventRegistry and make subclasses do the same
21
+ EventRegistry << self
22
+
23
+ # Operate on the metaclass as a type of singleton pattern
24
+ class << self
25
+
26
+ def inherited(subcls)
27
+
28
+ # Be sure codestring doesn't conflict
29
+ existing = _from_codestring(subcls.codestring)
30
+ if existing then raise NameError, \
31
+ "New Event subclass '#{subcls}' conflicts with"\
32
+ " existing Event subclass '#{existing}'."\
33
+ " The generated codestring '#{subcls.codestring}'"\
34
+ " must be unique for each Event subclass." end
35
+
36
+ # Register, then call super
37
+ EventRegistry << subcls
38
+ super
39
+ end
40
+
41
+ # List of class inheritance lineage back to but excluding Object
42
+ def ancestry(cls=self)
43
+ _next = cls.superclass
44
+ [cls==Object ? [] : [cls, ancestry(_next)]].flatten
45
+ end
46
+
47
+ # Convert class <ClassNameEvent> to string "class_name"
48
+ def codestring(cls=self)
49
+ File.basename cls.to_s
50
+ .underscore
51
+ .gsub(/_event$/, "")
52
+ end
53
+
54
+ # List of codestrings associated with this event and ancestors
55
+ def codestrings
56
+ x = ancestry
57
+ .map {|cls| cls.codestring}
58
+ end
59
+
60
+ # Pull class from registry by codestring
61
+ # (more reliable than crafting a reverse regexp)
62
+ def _from_codestring(str)
63
+ return EventRegistry.list
64
+ .select{|e| e.codestring==str}[0]
65
+ end; private :_from_codestring
66
+
67
+ def from_codestring(str)
68
+ cls = _from_codestring(str.to_s)
69
+ if not cls then raise NameError,
70
+ "No known Event subclass with codestring: '#{str}'" end
71
+ cls
72
+ end
73
+
74
+ # Convert an event from 'array notation' to an Event subclass instance
75
+ # TODO: List acceptable input forms here for documentation
76
+ def new_from(input)
77
+
78
+ # Standardize to array and pull out arguments if they exist
79
+ input = [input] unless input.is_a? Array
80
+ input, *args = input
81
+
82
+ # Create event object from event as an object, class, or symbol/string
83
+ event = case input
84
+ when Event
85
+ input
86
+ when Class
87
+ input.new(*args) if input < Event
88
+ else
89
+ Event.from_codestring(input.to_s).new(*args)
90
+ end
91
+ end
92
+
93
+ # Create attributes and accessors for all arguments to the constructor.
94
+ # This is done here rather than in initialize so that the functionality
95
+ # will remain if the user developer overrides initialize in the subclass.
96
+ def new(*args, &block)
97
+ obj = super
98
+
99
+ kwargs = args[-1].is_a?(Hash) ? args.pop : Hash.new
100
+ kwargs[:args] = args
101
+ kwargs[:codeblock] = block if block
102
+ for key in kwargs.keys
103
+ att = key.to_s
104
+ obj.instance_variable_set("@#{att}", kwargs[key])
105
+ class_eval("def #{att}; @#{att}; end")
106
+ class_eval("def #{att}=(val); @#{att}=val; end")
107
+ end
108
+
109
+ obj
110
+ end
111
+
112
+ end
113
+
114
+ # Calling super in new with *args will complain if this isn't here
115
+ def initialize(*args, &block) end
116
+ end
117
+
118
+
119
+ #
120
+ # Comparison support for Events and Symbols/Strings
121
+ #
122
+
123
+ # Reopen Event and add comparison functions
124
+ class Event
125
+ class << self
126
+ def ==(other)
127
+ other.is_a?(Class) ?
128
+ super : codestring==other.to_s
129
+ end
130
+ def <=(other)
131
+ other.is_a?(Class) ?
132
+ super : codestrings.include?(other.to_s)
133
+ end
134
+ def <(other)
135
+ other.is_a?(Class) ?
136
+ super : (self<=other and not self==other)
137
+ end
138
+ def >=(other)
139
+ other.is_a?(Class) ?
140
+ super : Event.from_codestring(other.to_s)<=self
141
+ end
142
+ def >(other)
143
+ other.is_a?(Class) ?
144
+ super : Event.from_codestring(other.to_s)<self
145
+ end
146
+ end
147
+ end
148
+
149
+ # Autogenerate the inverse comparison functions for Symbol/String
150
+ for cls in [Symbol, String]
151
+ %w(== < > <= >=).zip(%w(== > < >= <=))
152
+ .each do |ops|
153
+ op, opinv = ops # unzip operator and inverse operator
154
+ cls.class_eval(
155
+ "def #{op}(other)\n"\
156
+ " (other.is_a?(Class) and other<=Event) ? \n"\
157
+ " (other#{opinv}self) : super\n"\
158
+ "end\n")
159
+ end
160
+ end
161
+ end
data/lib/wires/hub.rb CHANGED
@@ -2,203 +2,204 @@
2
2
  # Make sure puts goes to $stdout for all threads!
3
3
  def puts(x) $stdout.puts(x) end
4
4
 
5
-
6
- # An Event Hub. Event/proc associations come in, and the procs
7
- # get called in new threads in the order received
8
- class Hub
9
- @queue = Queue.new
10
- @state = [:dead, :alive, :dying][0]
11
-
12
- @child_threads = Array.new
13
- @child_threads_lock = Mutex.new
14
-
15
- @before_kills = Queue.new
16
- @after_kills = Queue.new
17
-
18
- # Operate on the metaclass as a type of singleton pattern
19
- class << self
5
+ module Wires
6
+ # An Event Hub. Event/proc associations come in, and the procs
7
+ # get called in new threads in the order received
8
+ class Hub
9
+ @queue = Queue.new
10
+ @state = [:dead, :alive, :dying][0]
20
11
 
21
- def dead?; @state==:dead end
22
- def alive?; @state==:alive end
23
- def dying?; @state==:dying end
24
- def state; @state end
12
+ @child_threads = Array.new
13
+ @child_threads_lock = Mutex.new
25
14
 
26
- # Clear the Hub queue, but do not kill working threads
27
- def clear; @queue.clear end
15
+ @before_kills = Queue.new
16
+ @after_kills = Queue.new
28
17
 
29
- ##
30
- # Start the Hub event loop (optional flags change thread behavior)
31
- #
32
- # valid flags:
33
- # [+:blocking+] Hub event loop will be run in calling thread,
34
- # blocking until Hub is killed. If this flag is not
35
- # specified, the Hub event loop is run in a new thread,
36
- # which the main thread joins in at_exit.
37
- def run(*flags)
38
- if dead? # Only run if not already alive or dying
39
-
40
- # If :blocking is not set, run in a new thread and join at_exit
41
- if not (flags.include? :blocking)
42
- @thread = Thread.new() do
43
- self.send(:run_loop)
44
- end
45
- # Only join if main thread wasn't killed by an exception
46
- at_exit { @thread.join if not $! }
18
+ # Operate on the metaclass as a type of singleton pattern
19
+ class << self
20
+
21
+ def dead?; @state==:dead end
22
+ def alive?; @state==:alive end
23
+ def dying?; @state==:dying end
24
+ def state; @state end
25
+
26
+ # Clear the Hub queue, but do not kill working threads
27
+ def clear; @queue.clear end
28
+
29
+ ##
30
+ # Start the Hub event loop (optional flags change thread behavior)
31
+ #
32
+ # valid flags:
33
+ # [+:blocking+] Hub event loop will be run in calling thread,
34
+ # blocking until Hub is killed. If this flag is not
35
+ # specified, the Hub event loop is run in a new thread,
36
+ # which the main thread joins in at_exit.
37
+ def run(*flags)
38
+ if dead? # Only run if not already alive or dying
39
+
40
+ # If :blocking is not set, run in a new thread and join at_exit
41
+ if not (flags.include? :blocking)
42
+ @thread = Thread.new() do
43
+ self.send(:run_loop)
44
+ end
45
+ # Only join if main thread wasn't killed by an exception
46
+ at_exit { @thread.join if not $! }
47
+
48
+ # If :blocking is set, run in main thread and block until Hub death
49
+ else self.send(:run_loop) end
50
+
51
+ end
47
52
 
48
- # If :blocking is set, run in main thread and block until Hub death
49
- else self.send(:run_loop) end
53
+ sleep 0 # Yield to other threads
50
54
 
51
- end
55
+ nil end
52
56
 
53
- sleep 0 # Yield to other threads
57
+ ##
58
+ # Kill the Hub event loop (optional flags change thread behavior)
59
+ #
60
+ # valid flags:
61
+ # [+:finish_all+] Hub thread won't be done until all child threads done
62
+ # [+:blocking+] calling thread won't be done until Hub thread is done
63
+ def kill(*flags)
64
+ @finish_all = (flags.include? :finish_all)
65
+ @state=:dying
66
+ @thread.join if (dying? and flags.include? :blocking)
67
+ nil end
54
68
 
55
- nil end
56
-
57
- ##
58
- # Kill the Hub event loop (optional flags change thread behavior)
59
- #
60
- # valid flags:
61
- # [+:finish_all+] Hub thread won't be done until all child threads done
62
- # [+:blocking+] calling thread won't be done until Hub thread is done
63
- def kill(*flags)
64
- @finish_all = (flags.include? :finish_all)
65
- @state=:dying
66
- @thread.join if (dying? and flags.include? :blocking)
67
- nil end
68
-
69
- # Register hook to execute before kill - can call multiple times
70
- def before_kill(proc=nil, retain:false, &block)
71
- func = (block or proc)
72
- expect_type func, Proc
73
- @before_kills << [func, retain]
74
- nil end
75
-
76
- # Register hook to execute after kill - can call multiple times
77
- def after_kill(proc=nil, retain:false, &block)
78
- func = (block or proc)
79
- expect_type func, Proc
80
- @after_kills << [func, retain]
81
- nil end
82
-
83
- # Put x in the queue, and block until x is processed (if Hub is running)
84
- def fire(x)
85
- if not dead? # yield to event loop thread until awoken by it later
86
- @queue << [x, Thread.current]
87
- sleep
88
- else # don't wait if Hub isn't running - would cause lockup
89
- @queue << [x, nil]
90
- end
91
- nil end
92
- def <<(x); fire(x); end
93
-
94
- private
95
-
96
- # Kill all currently working child threads
97
- # Newly fired events could still queue up,
98
- # Waiting to be born until this thread is done killing
99
- def kill_children
100
- @child_threads_lock.synchronize do
101
- until @child_threads.empty?
102
- @child_threads.shift.exit
103
- end
104
- end
105
- nil end
106
-
107
- # Kill all currently working child threads
108
- # Newly fired events could still queue up,
109
- # But they will be cleared out and never be born
110
- def kill_children_and_clear
111
- @child_threads_lock.synchronize do
112
- until @child_threads.empty?
113
- @child_threads.shift.exit
69
+ # Register hook to execute before kill - can call multiple times
70
+ def before_kill(proc=nil, retain:false, &block)
71
+ func = (block or proc)
72
+ expect_type func, Proc
73
+ @before_kills << [func, retain]
74
+ nil end
75
+
76
+ # Register hook to execute after kill - can call multiple times
77
+ def after_kill(proc=nil, retain:false, &block)
78
+ func = (block or proc)
79
+ expect_type func, Proc
80
+ @after_kills << [func, retain]
81
+ nil end
82
+
83
+ # Put x in the queue, and block until x is processed (if Hub is running)
84
+ def fire(x)
85
+ if not dead? # yield to event loop thread until awoken by it later
86
+ @queue << [x, Thread.current]
87
+ sleep
88
+ else # don't wait if Hub isn't running - would cause lockup
89
+ @queue << [x, nil]
114
90
  end
115
- clear
116
- end
117
- nil end
91
+ nil end
92
+ def <<(x); fire(x); end
93
+
94
+ private
118
95
 
119
- # Join child threads, one by one, allowing more children to appear
120
- def join_children
121
- a_thread = Thread.new{nil}
122
- while a_thread
96
+ # Kill all currently working child threads
97
+ # Newly fired events could still queue up,
98
+ # Waiting to be born until this thread is done killing
99
+ def kill_children
123
100
  @child_threads_lock.synchronize do
124
- a_thread = @child_threads.shift
101
+ until @child_threads.empty?
102
+ @child_threads.shift.exit
103
+ end
125
104
  end
126
- a_thread.join if a_thread
127
- sleep 0 # Yield to other threads
128
- end
129
- nil end
130
-
131
- # Flush/run queue of [proc, retain]s, retaining those with retain==true
132
- def run_hooks(queue)
133
- retained = Queue.new
134
- while not queue.empty?
135
- proc, retain = queue.shift
136
- retained << [proc, retain] if retain
137
- proc.call
138
- end
139
- while not retained.empty?
140
- queue << retained.shift
141
- end
142
- nil end
143
-
144
- # Run before_kill hooks, optionally join child threads, then die
145
- def die
146
- run_hooks(@before_kills)
147
- join_children if @finish_all
148
- @state = :dead
149
- nil end
150
-
151
- # Run main event loop, not finished until Hub is killed
152
- def run_loop
153
- @state = :alive
154
-
155
- while not dead?
156
- if @queue.empty? then sleep(0)
157
- else process_item(@queue.shift) end
158
-
159
- if dying?
160
- die_thread ||= Thread.new { die }
105
+ nil end
106
+
107
+ # Kill all currently working child threads
108
+ # Newly fired events could still queue up,
109
+ # But they will be cleared out and never be born
110
+ def kill_children_and_clear
111
+ @child_threads_lock.synchronize do
112
+ until @child_threads.empty?
113
+ @child_threads.shift.exit
114
+ end
115
+ clear
161
116
  end
162
- end
117
+ nil end
163
118
 
164
- run_hooks(@after_kills)
165
- @finish_all = false
119
+ # Join child threads, one by one, allowing more children to appear
120
+ def join_children
121
+ a_thread = Thread.new{nil}
122
+ while a_thread
123
+ @child_threads_lock.synchronize do
124
+ a_thread = @child_threads.shift
125
+ end
126
+ a_thread.join if a_thread
127
+ sleep 0 # Yield to other threads
128
+ end
129
+ nil end
166
130
 
167
- nil end
131
+ # Flush/run queue of [proc, retain]s, retaining those with retain==true
132
+ def run_hooks(queue)
133
+ retained = Queue.new
134
+ while not queue.empty?
135
+ proc, retain = queue.shift
136
+ retained << [proc, retain] if retain
137
+ proc.call
138
+ end
139
+ while not retained.empty?
140
+ queue << retained.shift
141
+ end
142
+ nil end
168
143
 
169
- def process_item(x)
170
- x, waiting_thread = x
171
- string, event, blocking, proc = x
144
+ # Run before_kill hooks, optionally join child threads, then die
145
+ def die
146
+ run_hooks(@before_kills)
147
+ join_children if @finish_all
148
+ @state = :dead
149
+ nil end
172
150
 
173
- # Do all dealings with @child_threads under mutex
174
- @child_threads_lock.synchronize do
151
+ # Run main event loop, not finished until Hub is killed
152
+ def run_loop
153
+ @state = :alive
154
+
155
+ while not dead?
156
+ if @queue.empty? then sleep(0)
157
+ else process_item(@queue.shift) end
158
+
159
+ if dying?
160
+ die_thread ||= Thread.new { die }
161
+ end
162
+ end
175
163
 
176
- # Clear dead child threads to free up memory
177
- @child_threads.select! {|t| t.status}
164
+ run_hooks(@after_kills)
165
+ @finish_all = false
178
166
 
179
- # Start the new child thread
180
- @child_threads << Thread.new do
181
- begin
182
- waiting_thread.wakeup unless blocking or not waiting_thread
183
- proc.call(event)
184
- waiting_thread.wakeup if blocking and waiting_thread
185
-
186
- rescue Interrupt, SystemExit => e
187
- @state = :dying
188
- unhandled_exception(e)
189
-
190
- rescue Exception => e
191
- unhandled_exception(e)
167
+ nil end
168
+
169
+ def process_item(x)
170
+ x, waiting_thread = x
171
+ string, event, blocking, proc = x
172
+
173
+ # Do all dealings with @child_threads under mutex
174
+ @child_threads_lock.synchronize do
175
+
176
+ # Clear dead child threads to free up memory
177
+ @child_threads.select! {|t| t.status}
178
+
179
+ # Start the new child thread
180
+ @child_threads << Thread.new do
181
+ begin
182
+ waiting_thread.wakeup unless blocking or not waiting_thread
183
+ proc.call(event)
184
+ waiting_thread.wakeup if blocking and waiting_thread
185
+
186
+ rescue Interrupt, SystemExit => e
187
+ @state = :dying
188
+ unhandled_exception(e)
189
+
190
+ rescue Exception => e
191
+ unhandled_exception(e)
192
+ end
192
193
  end
194
+
193
195
  end
194
-
195
- end
196
-
197
- nil end
198
-
199
- def unhandled_exception(x)
200
- $stderr.puts $!
201
- $stderr.puts $@
202
- nil end
196
+
197
+ nil end
198
+
199
+ def unhandled_exception(x)
200
+ $stderr.puts $!
201
+ $stderr.puts $@
202
+ nil end
203
+ end
203
204
  end
204
- end
205
+ end