timers 4.1.1 → 4.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.
data/lib/timers/group.rb CHANGED
@@ -1,132 +1,132 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # This file is part of the "timers" project and released under the MIT license.
4
+ #
5
+ # Copyright, 2018, by Samuel Williams. All rights reserved.
6
+ #
1
7
 
2
- require 'set'
3
- require 'forwardable'
4
- require 'hitimes'
8
+ require "set"
9
+ require "forwardable"
5
10
 
6
- require 'timers/timer'
7
- require 'timers/events'
11
+ require_relative "interval"
12
+ require_relative "timer"
13
+ require_relative "events"
8
14
 
9
15
  module Timers
10
- class Group
11
- include Enumerable
12
-
13
- extend Forwardable
14
- def_delegators :@timers, :each, :empty?
15
-
16
- def initialize
17
- @events = Events.new
18
-
19
- @timers = Set.new
20
- @paused_timers = Set.new
21
-
22
- @interval = Hitimes::Interval.new
23
- @interval.start
24
- end
25
-
26
- # Scheduled events:
27
- attr :events
28
-
29
- # Active timers:
30
- attr :timers
31
-
32
- # Paused timers:
33
- attr :paused_timers
34
-
35
- # Call the given block after the given interval. The first argument will be
36
- # the time at which the group was asked to fire timers for.
37
- def after(interval, &block)
38
- Timer.new(self, interval, false, &block)
39
- end
40
-
41
- # Call the given block immediately, and then after the given interval. The first
42
- # argument will be the time at which the group was asked to fire timers for.
43
- def now_and_after(interval, &block)
44
- block.call
45
- after(interval, &block)
46
- end
47
-
48
- # Call the given block periodically at the given interval. The first
49
- # argument will be the time at which the group was asked to fire timers for.
50
- def every(interval, recur = true, &block)
51
- Timer.new(self, interval, recur, &block)
52
- end
53
-
54
- # Call the given block immediately, and then periodically at the given interval. The first
55
- # argument will be the time at which the group was asked to fire timers for.
56
- def now_and_every(interval, recur = true, &block)
57
- block.call
58
- every(interval, recur, &block)
59
- end
60
-
61
- # Wait for the next timer and fire it. Can take a block, which should behave
62
- # like sleep(n), except that n may be nil (sleep forever) or a negative
63
- # number (fire immediately after return).
64
- def wait(&block)
65
- if block_given?
66
- yield wait_interval
67
-
68
- while interval = wait_interval and interval > 0
69
- yield interval
70
- end
71
- else
72
- while interval = wait_interval and interval > 0
73
- # We cannot assume that sleep will wait for the specified time, it might be +/- a bit.
74
- sleep interval
75
- end
76
- end
77
-
78
- return fire
79
- end
80
-
81
- # Interval to wait until when the next timer will fire.
82
- # - nil: no timers
83
- # - -ve: timers expired already
84
- # - 0: timers ready to fire
85
- # - +ve: timers waiting to fire
86
- def wait_interval(offset = self.current_offset)
87
- if handle = @events.first
88
- return handle.time - Float(offset)
89
- end
90
- end
91
-
92
- # Fire all timers that are ready.
93
- def fire(offset = self.current_offset)
94
- @events.fire(offset)
95
- end
96
-
97
- # Pause all timers.
98
- def pause
99
- @timers.dup.each do |timer|
100
- timer.pause
101
- end
102
- end
103
-
104
- # Resume all timers.
105
- def resume
106
- @paused_timers.dup.each do |timer|
107
- timer.resume
108
- end
109
- end
110
-
111
- alias_method :continue, :resume
112
-
113
- # Delay all timers.
114
- def delay(seconds)
115
- @timers.each do |timer|
116
- timer.delay(seconds)
117
- end
118
- end
119
-
120
- # Cancel all timers.
121
- def cancel
122
- @timers.dup.each do |timer|
123
- timer.cancel
124
- end
125
- end
126
-
127
- # The group's current time.
128
- def current_offset
129
- @interval.to_f
130
- end
131
- end
16
+ # A collection of timers which may fire at different times
17
+ class Group
18
+ include Enumerable
19
+
20
+ extend Forwardable
21
+ def_delegators :@timers, :each, :empty?
22
+
23
+ def initialize
24
+ @events = Events.new
25
+
26
+ @timers = Set.new
27
+ @paused_timers = Set.new
28
+
29
+ @interval = Interval.new
30
+ @interval.start
31
+ end
32
+
33
+ # Scheduled events:
34
+ attr_reader :events
35
+
36
+ # Active timers:
37
+ attr_reader :timers
38
+
39
+ # Paused timers:
40
+ attr_reader :paused_timers
41
+
42
+ # Call the given block after the given interval. The first argument will be
43
+ # the time at which the group was asked to fire timers for.
44
+ def after(interval, &block)
45
+ Timer.new(self, interval, false, &block)
46
+ end
47
+
48
+ # Call the given block immediately, and then after the given interval. The first
49
+ # argument will be the time at which the group was asked to fire timers for.
50
+ def now_and_after(interval, &block)
51
+ yield
52
+ after(interval, &block)
53
+ end
54
+
55
+ # Call the given block periodically at the given interval. The first
56
+ # argument will be the time at which the group was asked to fire timers for.
57
+ def every(interval, recur = true, &block)
58
+ Timer.new(self, interval, recur, &block)
59
+ end
60
+
61
+ # Call the given block immediately, and then periodically at the given interval. The first
62
+ # argument will be the time at which the group was asked to fire timers for.
63
+ def now_and_every(interval, recur = true, &block)
64
+ yield
65
+ every(interval, recur, &block)
66
+ end
67
+
68
+ # Wait for the next timer and fire it. Can take a block, which should behave
69
+ # like sleep(n), except that n may be nil (sleep forever) or a negative
70
+ # number (fire immediately after return).
71
+ def wait
72
+ if block_given?
73
+ yield wait_interval
74
+
75
+ while (interval = wait_interval) && interval > 0
76
+ yield interval
77
+ end
78
+ else
79
+ while (interval = wait_interval) && interval > 0
80
+ # We cannot assume that sleep will wait for the specified time, it might be +/- a bit.
81
+ sleep interval
82
+ end
83
+ end
84
+
85
+ fire
86
+ end
87
+
88
+ # Interval to wait until when the next timer will fire.
89
+ # - nil: no timers
90
+ # - -ve: timers expired already
91
+ # - 0: timers ready to fire
92
+ # - +ve: timers waiting to fire
93
+ def wait_interval(offset = current_offset)
94
+ handle = @events.first
95
+ handle.time - Float(offset) if handle
96
+ end
97
+
98
+ # Fire all timers that are ready.
99
+ def fire(offset = current_offset)
100
+ @events.fire(offset)
101
+ end
102
+
103
+ # Pause all timers.
104
+ def pause
105
+ @timers.dup.each(&:pause)
106
+ end
107
+
108
+ # Resume all timers.
109
+ def resume
110
+ @paused_timers.dup.each(&:resume)
111
+ end
112
+
113
+ alias continue resume
114
+
115
+ # Delay all timers.
116
+ def delay(seconds)
117
+ @timers.each do |timer|
118
+ timer.delay(seconds)
119
+ end
120
+ end
121
+
122
+ # Cancel all timers.
123
+ def cancel
124
+ @timers.dup.each(&:cancel)
125
+ end
126
+
127
+ # The group's current time.
128
+ def current_offset
129
+ @interval.to_f
130
+ end
131
+ end
132
132
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # This file is part of the "timers" project and released under the MIT license.
4
+ #
5
+ # Copyright, 2018, by Samuel Williams. All rights reserved.
6
+ #
7
+
8
+ module Timers
9
+ # A collection of timers which may fire at different times
10
+ class Interval
11
+ # Get the current elapsed monotonic time.
12
+ def initialize
13
+ @total = 0.0
14
+ @current = nil
15
+ end
16
+
17
+ def start
18
+ return if @current
19
+
20
+ @current = now
21
+ end
22
+
23
+ def stop
24
+ return unless @current
25
+
26
+ @total += duration
27
+
28
+ @current = nil
29
+ end
30
+
31
+ def to_f
32
+ @total + duration
33
+ end
34
+
35
+ protected def duration
36
+ now - @current
37
+ end
38
+
39
+ protected def now
40
+ ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
41
+ end
42
+ end
43
+ end
data/lib/timers/timer.rb CHANGED
@@ -1,128 +1,134 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # This file is part of the "timers" project and released under the MIT license.
4
+ #
5
+ # Copyright, 2018, by Samuel Williams. All rights reserved.
6
+ #
1
7
 
2
8
  module Timers
3
- # An individual timer set to fire a given proc at a given time. A timer is
4
- # always connected to a Timer::Group but it would ONLY be in @group.timers
5
- # if it also has a @handle specified. Otherwise it is either PAUSED or has
6
- # been FIRED and is not recurring. You can manually enter this state by
7
- # calling #cancel and resume normal operation by calling #reset.
8
- class Timer
9
- include Comparable
10
- attr_reader :interval, :offset, :recurring
11
-
12
- def initialize(group, interval, recurring = false, offset = nil, &block)
13
- @group = group
14
-
15
- @interval = interval
16
- @recurring = recurring
17
- @block = block
18
- @offset = offset
19
-
20
- @handle = nil
21
-
22
- # If a start offset was supplied, use that, otherwise use the current timers offset.
23
- reset(@offset || @group.current_offset)
24
- end
25
-
26
- def paused?
27
- @group.paused_timers.include? self
28
- end
29
-
30
- def pause
31
- return if paused?
32
-
33
- @group.timers.delete self
34
- @group.paused_timers.add self
35
-
36
- @handle.cancel! if @handle
37
- @handle = nil
38
- end
39
-
40
- def resume
41
- return unless paused?
42
-
43
- @group.paused_timers.delete self
44
-
45
- # This will add us back to the group:
46
- reset
47
- end
48
-
49
- alias_method :continue, :resume
50
-
51
- # Extend this timer
52
- def delay(seconds)
53
- @handle.cancel! if @handle
54
-
55
- @offset += seconds
56
-
57
- @handle = @group.events.schedule(@offset, self)
58
- end
59
-
60
- # Cancel this timer. Do not call while paused.
61
- def cancel
62
- return unless @handle
63
-
64
- @handle.cancel! if @handle
65
- @handle = nil
66
-
67
- # This timer is no longer valid:
68
- @group.timers.delete self if @group
69
- end
70
-
71
- # Reset this timer. Do not call while paused.
72
- def reset(offset = @group.current_offset)
73
- # This logic allows us to minimise the interaction with @group.timers.
74
- # A timer with a handle is always registered with the group.
75
- if @handle
76
- @handle.cancel!
77
- else
78
- @group.timers << self
79
- end
80
-
81
- @offset = Float(offset) + @interval
82
-
83
- @handle = @group.events.schedule(@offset, self)
84
- end
85
-
86
- # Fire the block.
87
- def fire(offset = @group.current_offset)
88
- if recurring == :strict
89
- # ... make the next interval strictly the last offset + the interval:
90
- reset(@offset)
91
- elsif recurring
92
- reset(offset)
93
- else
94
- @offset = offset
95
- end
96
-
97
- @block.call(offset)
98
-
99
- cancel unless recurring
100
- end
101
-
102
- alias_method :call, :fire
103
-
104
- # Number of seconds until next fire / since last fire
105
- def fires_in
106
- @offset - @group.current_offset if @offset
107
- end
108
-
109
- # Inspect a timer
110
- def inspect
111
- str = "#<Timers::Timer:#{object_id.to_s(16)} "
112
-
113
- if @offset
114
- if fires_in >= 0
115
- str << "fires in #{fires_in} seconds"
116
- else
117
- str << "fired #{fires_in.abs} seconds ago"
118
- end
119
-
120
- str << ", recurs every #{interval}" if recurring
121
- else
122
- str << "dead"
123
- end
124
-
125
- str << ">"
126
- end
127
- end
9
+ # An individual timer set to fire a given proc at a given time. A timer is
10
+ # always connected to a Timer::Group but it would ONLY be in @group.timers
11
+ # if it also has a @handle specified. Otherwise it is either PAUSED or has
12
+ # been FIRED and is not recurring. You can manually enter this state by
13
+ # calling #cancel and resume normal operation by calling #reset.
14
+ class Timer
15
+ include Comparable
16
+ attr_reader :interval, :offset, :recurring
17
+
18
+ def initialize(group, interval, recurring = false, offset = nil, &block)
19
+ @group = group
20
+
21
+ @interval = interval
22
+ @recurring = recurring
23
+ @block = block
24
+ @offset = offset
25
+
26
+ @handle = nil
27
+
28
+ # If a start offset was supplied, use that, otherwise use the current timers offset.
29
+ reset(@offset || @group.current_offset)
30
+ end
31
+
32
+ def paused?
33
+ @group.paused_timers.include? self
34
+ end
35
+
36
+ def pause
37
+ return if paused?
38
+
39
+ @group.timers.delete self
40
+ @group.paused_timers.add self
41
+
42
+ @handle.cancel! if @handle
43
+ @handle = nil
44
+ end
45
+
46
+ def resume
47
+ return unless paused?
48
+
49
+ @group.paused_timers.delete self
50
+
51
+ # This will add us back to the group:
52
+ reset
53
+ end
54
+
55
+ alias continue resume
56
+
57
+ # Extend this timer
58
+ def delay(seconds)
59
+ @handle.cancel! if @handle
60
+
61
+ @offset += seconds
62
+
63
+ @handle = @group.events.schedule(@offset, self)
64
+ end
65
+
66
+ # Cancel this timer. Do not call while paused.
67
+ def cancel
68
+ return unless @handle
69
+
70
+ @handle.cancel! if @handle
71
+ @handle = nil
72
+
73
+ # This timer is no longer valid:
74
+ @group.timers.delete self if @group
75
+ end
76
+
77
+ # Reset this timer. Do not call while paused.
78
+ def reset(offset = @group.current_offset)
79
+ # This logic allows us to minimise the interaction with @group.timers.
80
+ # A timer with a handle is always registered with the group.
81
+ if @handle
82
+ @handle.cancel!
83
+ else
84
+ @group.timers << self
85
+ end
86
+
87
+ @offset = Float(offset) + @interval
88
+
89
+ @handle = @group.events.schedule(@offset, self)
90
+ end
91
+
92
+ # Fire the block.
93
+ def fire(offset = @group.current_offset)
94
+ if recurring == :strict
95
+ # ... make the next interval strictly the last offset + the interval:
96
+ reset(@offset)
97
+ elsif recurring
98
+ reset(offset)
99
+ else
100
+ @offset = offset
101
+ end
102
+
103
+ @block.call(offset)
104
+
105
+ cancel unless recurring
106
+ end
107
+
108
+ alias call fire
109
+
110
+ # Number of seconds until next fire / since last fire
111
+ def fires_in
112
+ @offset - @group.current_offset if @offset
113
+ end
114
+
115
+ # Inspect a timer
116
+ def inspect
117
+ str = "#{to_s[0..-2]} ".dup
118
+
119
+ if @offset
120
+ str << if fires_in >= 0
121
+ "fires in #{fires_in} seconds"
122
+ else
123
+ "fired #{fires_in.abs} seconds ago"
124
+ end
125
+
126
+ str << ", recurs every #{interval}" if recurring
127
+ else
128
+ str << "dead"
129
+ end
130
+
131
+ str << ">"
132
+ end
133
+ end
128
134
  end
@@ -1,3 +1,10 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # This file is part of the "timers" project and released under the MIT license.
4
+ #
5
+ # Copyright, 2018, by Samuel Williams. All rights reserved.
6
+ #
7
+
1
8
  module Timers
2
- VERSION = "4.1.1"
9
+ VERSION = "4.2.0"
3
10
  end
data/lib/timers/wait.rb CHANGED
@@ -1,48 +1,52 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # This file is part of the "timers" project and released under the MIT license.
4
+ #
5
+ # Copyright, 2018, by Samuel Williams. All rights reserved.
6
+ #
1
7
 
2
- require 'hitimes'
8
+ require_relative "interval"
3
9
 
4
10
  module Timers
5
- # An exclusive, monotonic timeout class.
6
- class Wait
7
- def self.for(duration, &block)
8
- if duration
9
- timeout = self.new(duration)
10
-
11
- timeout.while_time_remaining(&block)
12
- else
13
- while true
14
- yield(nil)
15
- end
16
- end
17
- end
18
-
19
- def initialize(duration)
20
- @duration = duration
21
- @remaining = true
22
- end
23
-
24
- attr :duration
25
- attr :remaining
26
-
27
- # Yields while time remains for work to be done:
28
- def while_time_remaining(&block)
29
- @interval = Hitimes::Interval.new
30
- @interval.start
31
-
32
- while time_remaining?
33
- yield @remaining
34
- end
35
- ensure
36
- @interval.stop
37
- @interval = nil
38
- end
39
-
40
- private
41
-
42
- def time_remaining?
43
- @remaining = (@duration - @interval.duration)
44
-
45
- return @remaining > 0
46
- end
47
- end
11
+ # An exclusive, monotonic timeout class.
12
+ class Wait
13
+ def self.for(duration, &block)
14
+ if duration
15
+ timeout = new(duration)
16
+
17
+ timeout.while_time_remaining(&block)
18
+ else
19
+ loop do
20
+ yield(nil)
21
+ end
22
+ end
23
+ end
24
+
25
+ def initialize(duration)
26
+ @duration = duration
27
+ @remaining = true
28
+ end
29
+
30
+ attr_reader :duration
31
+ attr_reader :remaining
32
+
33
+ # Yields while time remains for work to be done:
34
+ def while_time_remaining
35
+ @interval = Interval.new
36
+ @interval.start
37
+
38
+ yield @remaining while time_remaining?
39
+ ensure
40
+ @interval.stop
41
+ @interval = nil
42
+ end
43
+
44
+ private
45
+
46
+ def time_remaining?
47
+ @remaining = (@duration - @interval.to_f)
48
+
49
+ @remaining > 0
50
+ end
51
+ end
48
52
  end