wires 0.1.12 → 0.2.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 +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
|