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 +4 -4
- data/lib/wires.rb +2 -2
- data/lib/wires/channel.rb +109 -0
- data/lib/wires/event.rb +161 -0
- data/lib/wires/hub.rb +180 -179
- data/lib/wires/time.rb +166 -162
- metadata +32 -4
- data/lib/wires/channels.rb +0 -107
- data/lib/wires/events.rb +0 -159
data/lib/wires/time.rb
CHANGED
@@ -1,198 +1,202 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
class TimeSchedulerAnonEvent < Event; end
|
4
|
-
|
5
|
-
|
6
|
-
class TimeSchedulerItem
|
2
|
+
module Wires
|
7
3
|
|
8
|
-
|
4
|
+
class TimeSchedulerStartEvent < Event; end
|
5
|
+
class TimeSchedulerAnonEvent < Event; end
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
ignore_past:false, cancel:false)
|
13
|
-
|
14
|
-
expect_type time, Time
|
7
|
+
|
8
|
+
class TimeSchedulerItem
|
15
9
|
|
16
|
-
|
17
|
-
tempcount = count
|
10
|
+
attr_reader :time, :event, :channel, :interval
|
18
11
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
12
|
+
def initialize(time, event, channel='*',
|
13
|
+
interval:0.seconds, count:1,
|
14
|
+
ignore_past:false, cancel:false)
|
15
|
+
|
16
|
+
expect_type time, Time
|
17
|
+
|
18
|
+
@active = (not cancel)
|
19
|
+
tempcount = count
|
20
|
+
|
21
|
+
while (time < Time.now) and (tempcount > 0)
|
22
|
+
time += interval
|
23
|
+
tempcount -= 1
|
24
|
+
end
|
25
|
+
if not ignore_past
|
26
|
+
time -= interval
|
27
|
+
self.count = count
|
28
|
+
else
|
29
|
+
self.count = tempcount
|
30
|
+
end
|
31
|
+
|
32
|
+
@time = time
|
33
|
+
@event = Event.new_from(event)
|
34
|
+
@channel = channel
|
35
|
+
@interval = interval
|
36
|
+
|
28
37
|
end
|
29
38
|
|
30
|
-
@
|
31
|
-
@
|
32
|
-
@
|
33
|
-
@
|
39
|
+
def active?; @active end
|
40
|
+
def inactive?; not @active end
|
41
|
+
def ready?; @active and (Time.now >= @time) end
|
42
|
+
def time_until; (@active ? [(Time.now - @time), 0].max : nil) end
|
34
43
|
|
35
|
-
|
36
|
-
|
37
|
-
def active?; @active end
|
38
|
-
def inactive?; not @active end
|
39
|
-
def ready?; @active and (Time.now >= @time) end
|
40
|
-
def time_until; (@active ? [(Time.now - @time), 0].max : nil) end
|
41
|
-
|
42
|
-
def cancel; @active = false ;nil end
|
43
|
-
|
44
|
-
# Get/set @count (and apply constraints on set)
|
45
|
-
def count; @count end
|
46
|
-
#TODO: handle explicit cancel?
|
47
|
-
def count=(x); @count=[x,0].max; @active&&=(count>0) ;nil end
|
48
|
-
|
49
|
-
# Inc/dec @count. Necessary because += and -= outside of lock are not atomic!
|
50
|
-
def count_inc(x=1); self.count=(@count+x) end
|
51
|
-
def count_dec(x=1); self.count=(@count-x) end
|
52
|
-
|
53
|
-
# Fire the event now, regardless of time or active status
|
54
|
-
def fire(*args)
|
55
|
-
Channel.new(@channel).fire(@event, *args)
|
56
|
-
count_dec
|
57
|
-
@time += @interval if @active
|
58
|
-
nil end
|
59
|
-
|
60
|
-
# Fire the event only if it is ready
|
61
|
-
def fire_if_ready(*args); self.fire(*args) if ready? end
|
62
|
-
|
63
|
-
# Block until event is ready
|
64
|
-
def wait_until_ready; sleep 0 until ready? end
|
65
|
-
|
66
|
-
# Block until event is ready, then fire and block until it is done
|
67
|
-
def fire_when_ready(*args);
|
68
|
-
wait_until_ready
|
69
|
-
fire_if_ready(*args)
|
70
|
-
end
|
71
|
-
|
72
|
-
# Lock (almost) all instance methods with common re-entrant lock
|
73
|
-
threadlock instance_methods-superclass.instance_methods-[
|
74
|
-
:block_until_ready,
|
75
|
-
:fire_when_ready]
|
76
|
-
end
|
77
|
-
|
78
|
-
# A singleton class to schedule future firing of events
|
79
|
-
class TimeScheduler
|
80
|
-
@schedule = Array.new
|
81
|
-
@thread = Thread.new {nil}
|
82
|
-
@schedule_lock = Monitor.new
|
83
|
-
@dont_sleep = false
|
84
|
-
|
85
|
-
# Operate on the metaclass as a type of singleton pattern
|
86
|
-
class << self
|
44
|
+
def cancel; @active = false ;nil end
|
87
45
|
|
88
|
-
#
|
89
|
-
def
|
90
|
-
|
91
|
-
|
92
|
-
wakeup
|
93
|
-
nil end
|
94
|
-
# Add an event to the schedule using << operator
|
95
|
-
alias_method :<<, :add
|
46
|
+
# Get/set @count (and apply constraints on set)
|
47
|
+
def count; @count end
|
48
|
+
#TODO: handle explicit cancel?
|
49
|
+
def count=(x); @count=[x,0].max; @active&&=(count>0) ;nil end
|
96
50
|
|
97
|
-
#
|
98
|
-
def
|
99
|
-
|
100
|
-
def clear; @schedule.clear end
|
51
|
+
# Inc/dec @count. Necessary because += and -= outside of lock are not atomic!
|
52
|
+
def count_inc(x=1); self.count=(@count+x) end
|
53
|
+
def count_dec(x=1); self.count=(@count-x) end
|
101
54
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
@
|
55
|
+
# Fire the event now, regardless of time or active status
|
56
|
+
def fire(*args)
|
57
|
+
Channel.new(@channel).fire(@event, *args)
|
58
|
+
count_dec
|
59
|
+
@time += @interval if @active
|
107
60
|
nil end
|
108
61
|
|
109
|
-
|
110
|
-
|
111
|
-
schedule_reshuffle
|
112
|
-
nil end
|
62
|
+
# Fire the event only if it is ready
|
63
|
+
def fire_if_ready(*args); self.fire(*args) if ready? end
|
113
64
|
|
114
|
-
|
115
|
-
|
116
|
-
schedule_reshuffle
|
117
|
-
nil end
|
65
|
+
# Block until event is ready
|
66
|
+
def wait_until_ready; sleep 0 until ready? end
|
118
67
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
end
|
124
|
-
[pending, @schedule[0]]
|
68
|
+
# Block until event is ready, then fire and block until it is done
|
69
|
+
def fire_when_ready(*args);
|
70
|
+
wait_until_ready
|
71
|
+
fire_if_ready(*args)
|
125
72
|
end
|
126
73
|
|
127
|
-
#
|
128
|
-
threadlock
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
74
|
+
# Lock (almost) all instance methods with common re-entrant lock
|
75
|
+
threadlock instance_methods-superclass.instance_methods-[
|
76
|
+
:block_until_ready,
|
77
|
+
:fire_when_ready]
|
78
|
+
end
|
79
|
+
|
80
|
+
# A singleton class to schedule future firing of events
|
81
|
+
class TimeScheduler
|
82
|
+
@schedule = Array.new
|
83
|
+
@thread = Thread.new {nil}
|
84
|
+
@schedule_lock = Monitor.new
|
85
|
+
@dont_sleep = false
|
86
|
+
|
87
|
+
# Operate on the metaclass as a type of singleton pattern
|
88
|
+
class << self
|
89
|
+
|
90
|
+
# Add an event to the schedule
|
91
|
+
def add(new_item)
|
92
|
+
expect_type new_item, TimeSchedulerItem
|
93
|
+
schedule_add(new_item)
|
94
|
+
wakeup
|
95
|
+
nil end
|
96
|
+
# Add an event to the schedule using << operator
|
97
|
+
alias_method :<<, :add
|
98
|
+
|
99
|
+
# Get a copy of the event schedule from outside the class
|
100
|
+
def list; @schedule.clone end
|
101
|
+
# Clear the event schedule from outside the class
|
102
|
+
def clear; @schedule.clear end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def schedule_reshuffle
|
107
|
+
@schedule.select! {|x| x.active?}
|
108
|
+
@schedule.sort! {|a,b| a.time <=> b.time}
|
109
|
+
nil end
|
110
|
+
|
111
|
+
def schedule_add(new_item)
|
112
|
+
@schedule << new_item
|
113
|
+
schedule_reshuffle
|
114
|
+
nil end
|
138
115
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
116
|
+
def schedule_concat(other_list)
|
117
|
+
@schedule.concat other_list
|
118
|
+
schedule_reshuffle
|
119
|
+
nil end
|
143
120
|
|
144
|
-
|
121
|
+
def schedule_pull
|
122
|
+
pending = Array.new
|
123
|
+
while ((not @schedule.empty?) and @schedule[0].ready?)
|
124
|
+
pending << @schedule.shift
|
125
|
+
end
|
126
|
+
[pending, @schedule[0]]
|
127
|
+
end
|
128
|
+
|
129
|
+
# Put all functions dealing with @schedule under @schedule_lock
|
130
|
+
threadlock :list,
|
131
|
+
:clear,
|
132
|
+
:schedule_reshuffle,
|
133
|
+
:schedule_add,
|
134
|
+
:schedule_concat,
|
135
|
+
:schedule_pull,
|
136
|
+
lock: :@schedule_lock
|
137
|
+
|
138
|
+
# Do scheduled firing of events as long as Hub is alive
|
139
|
+
def main_loop
|
145
140
|
|
146
|
-
|
147
|
-
|
148
|
-
pending
|
149
|
-
|
141
|
+
@keepgoing = true
|
142
|
+
@thread = Thread.current
|
143
|
+
pending = Array.new
|
144
|
+
on_deck = nil
|
150
145
|
|
151
|
-
@
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
146
|
+
while @keepgoing
|
147
|
+
|
148
|
+
# Pull, fire, and requeue relevant events
|
149
|
+
pending, on_deck = schedule_pull
|
150
|
+
pending.each { |x| x.fire }
|
151
|
+
schedule_concat pending
|
152
|
+
|
153
|
+
@sleepzone = true
|
154
|
+
# Calculate the time to sleep based on next event's time
|
155
|
+
if on_deck
|
156
|
+
sleep on_deck.time_until
|
157
|
+
else # sleep until wakeup if no event is on deck
|
158
|
+
sleep
|
159
|
+
end
|
160
|
+
@sleepzone = false
|
157
161
|
end
|
158
|
-
|
159
|
-
end
|
162
|
+
|
163
|
+
nil end
|
160
164
|
|
161
|
-
|
165
|
+
def wakeup
|
166
|
+
sleep 0 until @sleepzone==true
|
167
|
+
sleep 0 until @thread.status=='sleep'
|
168
|
+
@thread.wakeup
|
169
|
+
nil end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
# Use fired event to only start scheduler when Hub is running
|
174
|
+
# This also gets the scheduler loop its own thread within the Hub's threads
|
175
|
+
on :time_scheduler_start, self do; main_loop; end;
|
176
|
+
Channel.new(self).fire(:time_scheduler_start)
|
162
177
|
|
163
|
-
|
178
|
+
# Stop the main loop upon death of Hub
|
179
|
+
Hub.before_kill(retain:true) do
|
164
180
|
sleep 0 until @sleepzone==true
|
165
|
-
|
166
|
-
|
167
|
-
|
181
|
+
@keepgoing=false
|
182
|
+
wakeup
|
183
|
+
end
|
184
|
+
|
185
|
+
# Refire the start event after Hub dies in case it restarts
|
186
|
+
Hub.after_kill(retain:true) do
|
187
|
+
Channel.new(self).fire(:time_scheduler_start)
|
188
|
+
end
|
168
189
|
|
169
190
|
end
|
170
191
|
|
171
|
-
|
172
|
-
# This also gets the scheduler loop its own thread within the Hub's threads
|
173
|
-
on :time_scheduler_start, self do; main_loop; end;
|
174
|
-
Channel.new(self).fire(:time_scheduler_start)
|
175
|
-
|
176
|
-
# Stop the main loop upon death of Hub
|
177
|
-
Hub.before_kill(retain:true) do
|
178
|
-
sleep 0 until @sleepzone==true
|
179
|
-
@keepgoing=false
|
180
|
-
wakeup
|
181
|
-
end
|
182
|
-
|
183
|
-
# Refire the start event after Hub dies in case it restarts
|
184
|
-
Hub.after_kill(retain:true) do
|
185
|
-
Channel.new(self).fire(:time_scheduler_start)
|
186
|
-
end
|
187
|
-
|
188
|
-
end
|
189
|
-
|
192
|
+
end # End Wires module.
|
190
193
|
|
191
194
|
# Reopen the Time class and add the fire method to enable nifty syntax like:
|
192
195
|
# 32.minutes.from_now.fire :event
|
193
196
|
class Time
|
194
197
|
def fire(event, channel='*', **kwargs)
|
195
|
-
TimeScheduler <<
|
198
|
+
Wires::TimeScheduler << \
|
199
|
+
Wires::TimeSchedulerItem.new(self, event, channel, **kwargs)
|
196
200
|
end
|
197
201
|
end
|
198
202
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wires
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.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-07-
|
11
|
+
date: 2013-07-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -38,6 +38,34 @@ dependencies:
|
|
38
38
|
- - '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
41
69
|
description: An asynchronous (threaded) event routing framework in Ruby. Patch your
|
42
70
|
objects together with wires. Inspired by the python 'circuits' framework.
|
43
71
|
email: joe.eli.mac@gmail.com
|
@@ -49,8 +77,8 @@ files:
|
|
49
77
|
- lib/wires/time.rb
|
50
78
|
- lib/wires/hub.rb
|
51
79
|
- lib/wires/expect_type.rb
|
52
|
-
- lib/wires/
|
53
|
-
- lib/wires/
|
80
|
+
- lib/wires/channel.rb
|
81
|
+
- lib/wires/event.rb
|
54
82
|
- LICENSE
|
55
83
|
- README.md
|
56
84
|
homepage: https://github.com/jemc/wires/
|
data/lib/wires/channels.rb
DELETED
@@ -1,107 +0,0 @@
|
|
1
|
-
|
2
|
-
def on(events, channels='*', &codeblock)
|
3
|
-
channels = [channels] unless channels.is_a? Array
|
4
|
-
for channel in channels
|
5
|
-
Channel.new(channel).register(events, codeblock)
|
6
|
-
end
|
7
|
-
nil end
|
8
|
-
|
9
|
-
|
10
|
-
def fire(event, channel='*')
|
11
|
-
Channel.new(channel).fire(event, blocking:false)
|
12
|
-
nil end
|
13
|
-
|
14
|
-
def fire_and_wait(event, channel='*')
|
15
|
-
Channel.new(channel).fire(event, blocking:true)
|
16
|
-
nil end
|
17
|
-
|
18
|
-
|
19
|
-
def Channel(*args) Channel.new(*args) end
|
20
|
-
|
21
|
-
class Channel
|
22
|
-
|
23
|
-
attr_reader :name
|
24
|
-
attr_reader :target_list
|
25
|
-
|
26
|
-
def initialize(name)
|
27
|
-
@name = name
|
28
|
-
@target_list = Set.new
|
29
|
-
nil end
|
30
|
-
|
31
|
-
|
32
|
-
# Redefine this class method to use an alternate Hub
|
33
|
-
def self.hub; Hub; end
|
34
|
-
# Don't redefine this instance method!
|
35
|
-
def hub; self.class.hub; end
|
36
|
-
|
37
|
-
# Channel registry hash and star channel reference are values
|
38
|
-
# In this Hash with the key being the reference to the Hub
|
39
|
-
@@channel_hash = Hash.new
|
40
|
-
@@channel_star = Hash.new
|
41
|
-
|
42
|
-
# Give out references to the star channel
|
43
|
-
def self.channel_star; @@channel_star[self.hub]; end
|
44
|
-
def channel_star; @@channel_star[self.hub]; end
|
45
|
-
|
46
|
-
# Ensure that there is only one instance of Channel per name
|
47
|
-
@@new_lock = Mutex.new
|
48
|
-
def self.new(*args, &block)
|
49
|
-
@@channel_star[self.hub] ||= Channel.new('*') unless (args[0]=='*')
|
50
|
-
@@new_lock.synchronize do
|
51
|
-
@@channel_hash[self.hub] ||= Hash.new
|
52
|
-
@@channel_hash[self.hub][args[0]] ||= super(*args, &block)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
# Register a proc to be triggered by an event on this channel
|
57
|
-
def register(events, proc)
|
58
|
-
|
59
|
-
if not proc.is_a?(Proc) then raise SyntaxError, \
|
60
|
-
"No Proc given to execute on event: #{events}" end
|
61
|
-
|
62
|
-
# Convert all events to strings
|
63
|
-
events = [events] unless events.is_a? Array
|
64
|
-
events.flatten!
|
65
|
-
events.map! { |e| (e.is_a?(Class) ? e.codestring : e.to_s) }
|
66
|
-
events.uniq!
|
67
|
-
|
68
|
-
@target_list << [events, proc]
|
69
|
-
nil end
|
70
|
-
|
71
|
-
# Fire an event on this channel
|
72
|
-
def fire(event, blocking:false)
|
73
|
-
|
74
|
-
# Create an instance object from one of several acceptable input forms
|
75
|
-
event = Event.new_from event
|
76
|
-
|
77
|
-
# Fire to each relevant target on each channel
|
78
|
-
for chan in relevant_channels()
|
79
|
-
for target in chan.target_list
|
80
|
-
for string in target[0] & event.class.codestrings
|
81
|
-
self.class.hub << [string, event, blocking, *target[1..-1]]
|
82
|
-
end end end
|
83
|
-
|
84
|
-
nil end
|
85
|
-
|
86
|
-
def relevant_channels
|
87
|
-
return @@channel_hash[hub].values if self==channel_star
|
88
|
-
|
89
|
-
if self.name.is_a?(Regexp) then raise TypeError,
|
90
|
-
"Cannot fire on Regexp channel: #{self.name}."\
|
91
|
-
" Regexp channels can only used in event handlers." end
|
92
|
-
|
93
|
-
relevant = [channel_star]
|
94
|
-
for c in @@channel_hash[hub].values
|
95
|
-
relevant << c if \
|
96
|
-
if c.name.is_a?(Regexp)
|
97
|
-
self.name =~ c.name
|
98
|
-
elsif (defined?(c.name.channel_name) and
|
99
|
-
defined?(self.name.channel_name))
|
100
|
-
self.name.channel_name == c.name.channel_name
|
101
|
-
else
|
102
|
-
self.name.to_s == c.name.to_s
|
103
|
-
end
|
104
|
-
end
|
105
|
-
return relevant.uniq
|
106
|
-
end
|
107
|
-
end
|