dilation 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format progress
@@ -0,0 +1,4 @@
1
+ --no-private
2
+ --hide-void-return
3
+ -
4
+ LICENSE
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in dilation.gemspec
4
+ gemspec
5
+
6
+ gem 'yard', :require => nil
7
+ gem 'redcarpet', :require => nil
8
+
9
+ group :extras do
10
+ gem 'pry'
11
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Jim Deville
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,122 @@
1
+ # Dilation
2
+
3
+ A timer that provides an event based interface to hook into for your app. In addition, the back end timer can be swapped out, so for testing, you can control the passage of time.
4
+
5
+ ## Synopsis
6
+
7
+ ``` ruby
8
+ require 'rubygems'
9
+ require 'dilation'
10
+
11
+ class WakeHandler
12
+ def call
13
+ puts 'awake'
14
+ end
15
+ end
16
+
17
+ def stop_handler
18
+ puts 'stop'
19
+ end
20
+
21
+ c = Dilation::Core.new
22
+
23
+ trap 'INT' do
24
+ c.stop
25
+ abort
26
+ end
27
+
28
+ c.listen_for :start do puts 'start' end
29
+ c.listen_for :stop, method(:stop_handler)
30
+ c.listen_for :sleep, lambda { puts 'sleep' }
31
+ c.listen_for :wake, WakeHandler.new
32
+ c.listen_for :tick, lambda { puts 'tick' }
33
+
34
+ puts 'sleeping'
35
+
36
+ c.sleep 5
37
+
38
+ puts 'starting'
39
+ counter = 0
40
+
41
+ c.listen_for :tick, lambda { counter += 1 }
42
+
43
+ c.start
44
+ while counter < 5
45
+ end
46
+
47
+ c.stop
48
+
49
+ puts 'ok'
50
+ ```
51
+
52
+ TODO
53
+
54
+ ## Installation
55
+
56
+ Add this line to your application's Gemfile:
57
+
58
+ gem 'dilation'
59
+
60
+ And then execute:
61
+
62
+ $ bundle
63
+
64
+ Or install it yourself as:
65
+
66
+ $ gem install dilation
67
+
68
+ ## Usage
69
+
70
+ The [rubydoc.info docs](http://rubydoc.info/gems/dilation/frames) contain
71
+ API documentation. The API docs contain detailed info about all of Dilation's public API.
72
+
73
+ ## Release Policy
74
+
75
+ Dilation follows the principles of [semantic versioning](http://semver.org/).
76
+ The [API documentation](http://rubydoc.info/gems/dilation/frames) define
77
+ Dilation's public API. Patch level releases contain only bug fixes. Minor
78
+ releases contain backward-compatible new features. Major new releases
79
+ contain backwards-incompatible changes to the public API.
80
+
81
+ ## Ruby Interpreter Compatibility
82
+
83
+ Dilation has been tested on the following ruby interpreters:
84
+
85
+ * MRI 1.9.2
86
+
87
+ ## Development
88
+
89
+ * Source hosted on [GitHub](http://github.com/jredville/dilation).
90
+ * Report issues on [GitHub Issues](http://github.com/jredville/dilation/issues).
91
+ * Pull requests are very welcome! Please include spec and/or feature coverage for every patch,
92
+ and create a topic branch for every separate change you make.
93
+ * Documentation is generated with [YARD](http://yardoc.org/) ([cheat sheet](http://cheat.errtheblog.com/s/yard/)).
94
+ To generate while developing:
95
+
96
+ ```
97
+ yard server --reload
98
+ ```
99
+
100
+ ## Todo
101
+
102
+ * Better interface to swap backend
103
+ * dilation/test_support
104
+ * [Code Climate](http://codeclimate.com)
105
+ * [Travis](http://travis-ci.org/)
106
+ ** test on interpreters
107
+ *** 1.8.7
108
+ *** 1.9.3
109
+ *** REE
110
+ *** Rubinius
111
+ * make Celluloid optional
112
+ * ability to override Kernel#sleep
113
+
114
+ ## Thanks
115
+
116
+ * [Myron Marston](http://github.com/myronmarston) for [VCR](http://github.com/myronmarston/vcr), which wasn't used here, but influences how I want to run this project and this readme.
117
+ * [Microsoft's Reactive Extensions](http://msdn.microsoft.com/en-us/data/gg577609.aspx) team for the idea of [controlling the flow of time](http://channel9.msdn.com/Shows/Going+Deep/Wes-Dyer-and-Jeffrey-Van-Gogh-Inside-Rx-Virtual-Time)
118
+
119
+ ## Copyright
120
+
121
+ Copyright (c) 2010-2012 Jim Deville. Released under the terms of the
122
+ MIT license. See LICENSE for details.
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new do |t|
6
+ # Put spec opts in a file named .rspec in root
7
+ end
8
+
9
+ task :default => :spec
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/dilation/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "dilation"
6
+ gem.authors = ["Jim Deville"]
7
+ gem.email = ["james.deville@gmail.com"]
8
+ gem.description = %q{A controllable timer}
9
+ gem.summary = %q{A timer that provides an event based interface to hook into for your app. In addition, the back end timer can be swapped out, so for testing, you can control the passage of time.}
10
+ gem.homepage = "http://github.com/jredville/dilation"
11
+
12
+ gem.files = `git ls-files`.split($\)
13
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Dilation::VERSION
17
+ gem.platform = Gem::Platform::RUBY
18
+ gem.required_ruby_version = '>= 1.9.2'
19
+ gem.required_rubygems_version = '>= 1.3.5'
20
+
21
+ gem.add_dependency('celluloid')
22
+ gem.add_development_dependency('rake', '~> 0.9.2')
23
+ gem.add_development_dependency('rspec')
24
+ end
@@ -0,0 +1,17 @@
1
+ require "dilation/version"
2
+ require "dilation/core"
3
+
4
+ # Top level namespace for Dilation
5
+ # @see Dilation::Core
6
+ #
7
+ # @author Jim Deville <james.deville@gmail.com>
8
+ module Dilation
9
+ # Contains classes to be used for tests
10
+ module TestSupport end
11
+
12
+ # Contains timers for the backend of Core
13
+ module Timers end
14
+
15
+ # Utility classes that support the implementation
16
+ module Utils end
17
+ end
@@ -0,0 +1,135 @@
1
+ require_relative 'utils/events'
2
+ require_relative 'utils/dilator'
3
+ require_relative 'timers/coarse'
4
+
5
+ module Dilation
6
+ # Dilation::Core is the main object that allows you to interact
7
+ # with and test timers.
8
+ #
9
+ # @author Jim Deville <james.deville@gmail.com>
10
+ # @todo better interface to swap the timer
11
+ class Core
12
+ include Utils::Events
13
+ event :tick, :start, :stop, :sleep, :wake
14
+
15
+ # @param [#call] value The factory for the timer
16
+ attr_writer :timer_source
17
+
18
+ def initialize
19
+ @dilator = Utils::Dilator.new
20
+ end
21
+
22
+ # Slows down (dilates) time for your code
23
+ #
24
+ # @example Run at half time
25
+ # core.dilate(2)
26
+ # core.tick #=> nothing
27
+ # core.tick #=> fires tick event
28
+ #
29
+ # @overload dilate
30
+ # Sets a factor of 1 which is the same as no dilation
31
+ # @overload dilate(factor)
32
+ # Sets a factor of `factor` which implies one `Core#tick` per
33
+ # `factor` `Core#timer` ticks
34
+ # @param [Number] factor the dilation factor
35
+ # @return [Number] the dilation factor
36
+ # @see Dilation::Utils::Dilator
37
+ def dilate(factor = 1)
38
+ @dilator.factor = factor
39
+ @dilator.invert
40
+ factor
41
+ end
42
+
43
+ # Speeds up (contracts) time for your code
44
+ #
45
+ # @example Run at 2x time
46
+ # core.contract(2)
47
+ # core.tick #=> fires 2 tick events
48
+ #
49
+ # @overload contract
50
+ # Sets a factor of 1 which is the same as no contraction
51
+ # @overload contract(factor)
52
+ # Sets a factor of `factor` which implies `factor` `Core#tick`s
53
+ # per one `Core#timer` tick
54
+ # @param [Number] factor the contraction factor
55
+ # @return [Number] the contraction factor
56
+ # @see Dilation::Utils::Dilator
57
+ def contract(factor = 1)
58
+ @dilator.factor = factor
59
+ @dilator.uninvert
60
+ factor
61
+ end
62
+
63
+ # @private
64
+ # Called by the timer on each tick. Fires tick events based on
65
+ # the dilation/contraction factor
66
+ #
67
+ # @see #dilate
68
+ # @see #contract
69
+ def tick
70
+ if started?
71
+ @dilator.run { __tick }
72
+ end
73
+ end
74
+
75
+ # Starts the timer and fires the start event
76
+ def start
77
+ timer.start
78
+ @started = true
79
+ __start
80
+ end
81
+
82
+ # Stops the timer and fires the stop event
83
+ def stop
84
+ timer.stop
85
+ @started = false
86
+ __stop
87
+ end
88
+
89
+ # Sleeps for `time` (based on Core#tick).
90
+ #
91
+ # @note This method blocks the caller
92
+ #
93
+ # @example Sleep for 5 seconds
94
+ # core.sleep 5 #=> 5
95
+ # @example Sleep for 1 second
96
+ # core.sleep #=> 1
97
+ # @overload sleep(time)
98
+ # Sleep for the given number of second
99
+ # @param [Number] time the time to sleep for in seconds
100
+ # @overload sleep
101
+ # Sleep for 1 second
102
+ # @return [Number] time
103
+ def sleep(time=1)
104
+ sleep_handler = lambda { time -= 1}
105
+ listen_for :tick, sleep_handler
106
+ __sleep
107
+ start
108
+ while time > 0
109
+ end
110
+ stop
111
+ wake
112
+ time
113
+ ensure
114
+ dont_listen_for :tick, sleep_handler
115
+ end
116
+
117
+ # @return [Timer] the timer object for this core
118
+ #
119
+ # @see Dilation::Timers::Coarse
120
+ def timer
121
+ @timer ||= timer_source.call(self)
122
+ end
123
+
124
+ # @todo does this need to be public?
125
+ # @return [Boolean] is this core started or not
126
+ def started?
127
+ defined?(@started) && @started
128
+ end
129
+
130
+ private
131
+ def timer_source
132
+ @timer_source ||= lambda { |obj| Timers::Coarse.new(obj) }
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,40 @@
1
+ module Dilation
2
+ module TestSupport
3
+ # A handler that can be used in tests to confirm that events are hooked up
4
+ #
5
+ # @example Rspec example
6
+ # describe "Example" do
7
+ # let(:handler) { Dilation::TestSupport::Handler.new }
8
+ # subject { MyCoreBasedClass.new }
9
+ #
10
+ # it "triggers handler" do
11
+ # subject.listen_for :tick, handler
12
+ # subject.tick
13
+ # handler.should be_triggered
14
+ # end
15
+ # end
16
+ #
17
+ # @author Jim Deville <james.deville@gmail.com>
18
+ class Handler
19
+ # @return [Number] the number of times triggered
20
+ attr_reader :count
21
+
22
+ def initialize
23
+ @count = 0
24
+ end
25
+
26
+ # Trigger this handler
27
+ # @note increments the counter
28
+ #
29
+ # @return [Number] the new count
30
+ def call
31
+ @count += 1
32
+ end
33
+
34
+ # @return [Boolean] true if this handler has been called
35
+ def triggered?
36
+ @count > 0
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,28 @@
1
+ require 'celluloid'
2
+ require_relative 'timer'
3
+
4
+ Celluloid.logger = nil
5
+
6
+ module Dilation
7
+ module Timers
8
+ # A Celluloid based timer that is the default for Dilation::Core
9
+ # @see http://celluloid.io/
10
+ #
11
+ # @author Jim Deville <james.deville@gmail.com>
12
+ class Coarse < Timer
13
+ include Celluloid
14
+
15
+ # Stop this timer
16
+ def stop
17
+ defined?(@timer) && @timer.cancel
18
+ super
19
+ end
20
+
21
+ # Start this timer, ticking every second
22
+ def start
23
+ @timer = every(1) { tick }
24
+ super
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'timer'
2
+
3
+ module Dilation
4
+ module Timers
5
+ # A test timer that does not fire by itself. Primarily meant to
6
+ # be used inside of tests so you can control the passage of time
7
+ #
8
+ # @author Jim Deville <james.deville@gmail.com>
9
+ # @todo make this default for tests
10
+ class Test < Timer
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,33 @@
1
+ module Dilation
2
+ module Timers
3
+ # @abstract Subclass and override {#start} and {#stop}
4
+ #
5
+ # @author Jim Deville <james.deville@gmail.com>
6
+ class Timer
7
+ # @param [#tick] target the object to notify on ticks
8
+ def initialize(target)
9
+ @target = target
10
+ end
11
+
12
+ # This method should be called by subclasses on each tick
13
+ def tick
14
+ @target.tick
15
+ end
16
+
17
+ # @return [Boolean] true if this timer is running
18
+ def running?
19
+ defined?(@started) && @started
20
+ end
21
+
22
+ # Stop this timer
23
+ def stop
24
+ @started = false
25
+ end
26
+
27
+ # Start this timer
28
+ def start
29
+ @started = true
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,80 @@
1
+ module Dilation
2
+ module Utils
3
+ # This class is used for controlling the dilation and contraction
4
+ # factors of Core
5
+ #
6
+ # @author Jim Deville <james.deville@gmail.com>
7
+ # @todo Fix floating point issue (see specs)
8
+ class Dilator
9
+ def initialize
10
+ self.factor = 1
11
+ end
12
+
13
+ # Set the factor and reset the dilator
14
+ #
15
+ # @param [Number] val the new factor
16
+ def factor=(val)
17
+ @factor = val
18
+ reset
19
+ end
20
+
21
+ # Invert the dilator. When inverted, `factor` calls to {#run}
22
+ # yield once. Also resets the dilator
23
+ #
24
+ # @see {#run}
25
+ def invert
26
+ @invert = true
27
+ reset
28
+ end
29
+
30
+ # Uninvert the dilator. When uninverted, one call to {#run}
31
+ # yield `factor` times. Also resets the dilator
32
+ #
33
+ # @see {#run}
34
+ def uninvert
35
+ @invert = false
36
+ reset
37
+ end
38
+
39
+ # @return [Boolean] true if this dilator is inverted
40
+ def inverted?
41
+ defined?(@invert) && @invert
42
+ end
43
+
44
+ # Reset this dilator
45
+ # @todo should this be public?
46
+ def reset
47
+ @count = 0
48
+ end
49
+
50
+ # Run ths dilator based on the factor
51
+ #
52
+ # @yield if the factor is met
53
+ # @see {#invert}
54
+ # @see {#uninvert}
55
+ def run(&blk)
56
+ begin
57
+ blk.call
58
+ end while run_again? if ready?
59
+ end
60
+
61
+ private
62
+ def ready?
63
+ @count += factor
64
+ @count >= 1
65
+ end
66
+
67
+ def factor
68
+ @invert ? 1/@factor.to_f : @factor
69
+ end
70
+
71
+ def run_again?
72
+ @count -= 1
73
+ if @count < 1
74
+ return false
75
+ end
76
+ true
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,91 @@
1
+ module Dilation
2
+ module Utils
3
+ # Defines an event handler system loosely based off of the DOM
4
+ # addEventListener pattern
5
+ #
6
+ # @example
7
+ # class Foo
8
+ # include Dilation::Utils::Events
9
+ # event :bar, :baz
10
+ # def baz
11
+ # bar
12
+ # __baz
13
+ # end
14
+ # end
15
+ # f = Foo.new
16
+ # f.listen_for :bar, { puts 'bar' }
17
+ # f.listen_for :baz, { puts 'baz' }
18
+ # f.baz #=> prints bar then baz
19
+ #
20
+ # @author Jim Deville <james.deville@gmail.com>
21
+ # @todo fix return types
22
+ module Events
23
+ # @private
24
+ def self.included(base)
25
+ base.send :extend, ClassMethods
26
+ end
27
+
28
+ module ClassMethods
29
+ # Defines one or more events with the name and the __name variant
30
+ #
31
+ # @param [Array<String>, Array<Symbol>] names the names of the events to create
32
+ def event(*names)
33
+ names.each do |name|
34
+ define_method(name) { fire name }
35
+ alias_method :"__#{name}", name
36
+ end
37
+ end
38
+ end
39
+
40
+ # Registers a block or callable for the given event
41
+ #
42
+ # @overload listen_for(name, handler)
43
+ # registers the handler for the event
44
+ # @param [String, Symbol, #to_sym] name the event to listen for
45
+ # @param [#call] handler the handler to trigger for this event
46
+ # @overload listen_for(name, &blk)
47
+ # registers the block for the event
48
+ # @param [String, Symbol, #to_sym] name the event to listen for
49
+ # @param blk the block to trigger for this event
50
+ # @raise ArgumentError if handler or blk is not present
51
+ def listen_for(name, handler = nil, &blk)
52
+ to_add = handler || blk
53
+ raise ArgumentError.new "handler or block required" unless to_add
54
+ handlers[name.to_sym] << to_add
55
+ end
56
+
57
+ # Removes a callable for the given event
58
+ #
59
+ # @param [String, Symbol, #to_sym] name the event to remove from
60
+ # @param [#call] handler the handler to remove
61
+ def dont_listen_for(name, handler)
62
+ handlers[name.to_sym].delete(handler)
63
+ end
64
+
65
+ # Clears all handlers for the event
66
+ #
67
+ # @param [String, Symbol, #to_sym] name the event to clear
68
+ def clear(name)
69
+ handlers[name.to_sym] = []
70
+ end
71
+
72
+ # Clear all tracked handlers
73
+ def clear_all
74
+ self.handlers = Hash.new { |h, k| h[k] = [] }
75
+ end
76
+
77
+ # Triggers the given event
78
+ #
79
+ # @param [String, Symbol, #to_sym] name event to trigger
80
+ def fire(name)
81
+ handlers[name.to_sym].each(&:call)
82
+ end
83
+
84
+ private
85
+ attr_writer :handlers
86
+ def handlers
87
+ @handlers || clear_all
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,4 @@
1
+ module Dilation
2
+ # The version of this library
3
+ VERSION = "0.0.2"
4
+ end
@@ -0,0 +1,144 @@
1
+ require 'spec_mini'
2
+ require 'dilation/core'
3
+ require 'dilation/timers/test'
4
+
5
+ describe Dilation::Core do
6
+ let(:core) { Dilation::Core.new }
7
+ let(:timer) { Dilation::Timers::Test.new(subject) }
8
+
9
+ include_context "core event"
10
+
11
+ subject do
12
+ core.tap do |c|
13
+ c.timer_source = lambda { |o| timer }
14
+ end
15
+ end
16
+
17
+ context "starting and stopping" do
18
+ before do
19
+ subject.listen_for :tick, handler
20
+ end
21
+
22
+ it "starts timer when start is called" do
23
+ timer.should_receive(:start)
24
+ subject.start
25
+ end
26
+
27
+ it "stops timer when stop is called" do
28
+ timer.should_receive(:stop)
29
+ subject.stop
30
+ end
31
+
32
+ it "does not get ticks before timer is started" do
33
+ expect { timer.tick }.not_to trigger(handler)
34
+ end
35
+
36
+ it "gets ticks after timer is started" do
37
+ subject.start
38
+ expect { timer.tick }.to trigger(handler)
39
+ end
40
+
41
+ it "does not get ticks after timer is stopped" do
42
+ subject.start
43
+ subject.stop
44
+ expect { timer.tick }.not_to trigger(handler)
45
+ end
46
+
47
+ it "ignores multiple start calls"
48
+ it "ignores multiple stop calls"
49
+ it "stop when not started is ignored"
50
+ end
51
+
52
+ context "timer" do
53
+ before do
54
+ subject.start
55
+ end
56
+
57
+ it "gets tick called by the timer" do
58
+ subject.listen_for :tick, handler
59
+ expect { timer.tick }.to trigger(handler)
60
+ end
61
+ end
62
+
63
+ context "dilation" do
64
+ before do
65
+ subject.listen_for :tick, handler
66
+ subject.start
67
+ end
68
+
69
+ it "returns the new factor" do
70
+ subject.dilate.should == 1
71
+ subject.dilate(2).should == 2
72
+ subject.contract.should == 1
73
+ subject.contract(2).should == 2
74
+ end
75
+
76
+ it "receives one tick per tick of the timer by default" do
77
+ 10.times { timer.tick }
78
+ handler.count.should == 10
79
+ end
80
+
81
+ it "dilates time" do
82
+ subject.dilate(2) #2 real ticks per tick
83
+ 10.times { timer.tick }
84
+ handler.count.should == 5
85
+ end
86
+
87
+ it "defaults to a dilation factor of 1" do
88
+ subject.dilate
89
+ 10.times { timer.tick }
90
+ handler.count.should == 10
91
+ end
92
+
93
+ it "contracts time" do
94
+ subject.contract(2) #2 ticks for every real tick
95
+ 10.times { timer.tick }
96
+ handler.count.should == 20
97
+ end
98
+
99
+ it "defaults to a contraction factor of 1" do
100
+ subject.contract
101
+ 10.times { timer.tick }
102
+ handler.count.should == 10
103
+ end
104
+
105
+ it "resets dilation with contraction" do
106
+ subject.dilate(2)
107
+ subject.contract(2)
108
+ 10.times { timer.tick }
109
+ handler.count.should == 20
110
+ end
111
+
112
+ it "resets contraction with dilation" do
113
+ subject.contract(2)
114
+ subject.dilate(2)
115
+ 10.times { timer.tick }
116
+ handler.count.should == 5
117
+ end
118
+ end
119
+
120
+ context "events" do
121
+ context "tick" do
122
+ before do
123
+ subject.start
124
+ end
125
+ it_behaves_like "a core event", :tick, :tick
126
+ end
127
+
128
+ context "start" do
129
+ it_behaves_like "a core event", :start, :start
130
+ end
131
+
132
+ context "stop" do
133
+ it_behaves_like "a core event", :stop, :stop
134
+ end
135
+
136
+ # context "sleep" do
137
+ # it_behaves_like "a core event", :sleep, :sleep
138
+ # end
139
+
140
+ context "wake" do
141
+ it_behaves_like "a core event", :wake, :wake
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,85 @@
1
+ require 'dilation/utils/dilator'
2
+
3
+ describe Dilation::Utils::Dilator do
4
+ before do
5
+ @counter = 0
6
+ @blk = lambda { @counter += 1 }
7
+ end
8
+
9
+ shared_examples "an inverter" do
10
+ it "inverts" do
11
+ subject.invert
12
+ subject.should be_inverted
13
+ end
14
+
15
+ it "uninverts" do
16
+ subject.uninvert
17
+ subject.should_not be_inverted
18
+ end
19
+
20
+ it "defaults to 1 run per tick" do
21
+ expect { |b| subject.run(&b) }.to yield_control
22
+ end
23
+ end
24
+
25
+ context "when uninverted" do
26
+ it_behaves_like "an inverter"
27
+
28
+ it { subject.should_not be_inverted }
29
+
30
+ it "runs n times per tick if factor is set to n" do
31
+ subject.factor = 2
32
+ subject.run &@blk
33
+ @counter.should == 2
34
+ end
35
+
36
+ it "runs fractional factors" do
37
+ subject.factor = 1.5
38
+ subject.run &@blk
39
+ @counter.should == 1
40
+ subject.run &@blk
41
+ @counter.should == 3
42
+ end
43
+ end
44
+
45
+ context "when inverted" do
46
+ before do
47
+ subject.invert
48
+ end
49
+
50
+ it_behaves_like "an inverter"
51
+
52
+ it { subject.should be_inverted }
53
+
54
+ it "runs once every n times if factor is set to n and object is inverted" do
55
+ subject.factor = 3
56
+ 3.times { subject.run &@blk }
57
+ @counter.should == 1
58
+ end
59
+
60
+ it "resets the count when factor is changed" do
61
+ subject.factor = 2
62
+ subject.run &@blk
63
+ subject.factor = 3
64
+ 2.times { subject.run &@blk }
65
+ @counter.should == 0
66
+ end
67
+
68
+ it "runs fractional factors" do
69
+ pending "floating point issue"
70
+ subject.factor = 1.5
71
+ 3.times { subject.run &@blk }
72
+ @counter.should == 2
73
+ end
74
+
75
+ it "resets the count when the inversion is changed" do
76
+ pending "floating point"
77
+ subject.invert
78
+ subject.factor = 1.5
79
+ subject.run &@blk
80
+ subject.invert
81
+ subject.run &@blk
82
+ @counter.should == 1
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,42 @@
1
+ require 'rubygems'
2
+ require 'dilation'
3
+
4
+ class WakeHandler
5
+ def call
6
+ puts 'awake'
7
+ end
8
+ end
9
+
10
+ def stop_handler
11
+ puts 'stop'
12
+ end
13
+
14
+ c = Dilation::Core.new
15
+
16
+ trap 'INT' do
17
+ c.stop
18
+ abort
19
+ end
20
+
21
+ c.listen_for :start do puts 'start' end
22
+ c.listen_for :stop, method(:stop_handler)
23
+ c.listen_for :sleep, lambda { puts 'sleep' }
24
+ c.listen_for :wake, WakeHandler.new
25
+ c.listen_for :tick, lambda { puts 'tick' }
26
+
27
+ puts 'sleeping'
28
+
29
+ c.sleep 5
30
+
31
+ puts 'starting'
32
+ counter = 0
33
+
34
+ c.listen_for :tick, lambda { counter += 1 }
35
+
36
+ c.start
37
+ while counter < 5
38
+ end
39
+
40
+ c.stop
41
+
42
+ puts 'ok'
@@ -0,0 +1,2 @@
1
+ require_relative 'support/shared_examples'
2
+ require_relative 'support/matchers'
@@ -0,0 +1,19 @@
1
+ RSpec::Matchers.define(:trigger) do |handler|
2
+ match do |actual|
3
+ @not_to_trigger ||= []
4
+ @to_trigger ||= []
5
+ @to_trigger << handler
6
+ actual.call
7
+ @to_trigger.all?(&:triggered?) && @not_to_trigger.none?(&:triggered?)
8
+ end
9
+
10
+ chain :but_not do |handler|
11
+ @not_to_trigger ||= []
12
+ @not_to_trigger << handler
13
+ end
14
+
15
+ chain :and_also do |handler|
16
+ @to_trigger ||= []
17
+ @to_trigger << handler
18
+ end
19
+ end
@@ -0,0 +1,69 @@
1
+ require 'dilation/test_support/handler'
2
+
3
+ shared_context "core event" do
4
+ let(:handler) { Dilation::TestSupport::Handler.new }
5
+ let(:handler2) { Dilation::TestSupport::Handler.new }
6
+ end
7
+
8
+ shared_examples "a core event" do |event_name, trigger|
9
+ it_behaves_like "a core event details", event_name, trigger
10
+ it_behaves_like "a core event details", event_name, "__#{trigger}"
11
+ end
12
+
13
+ shared_examples "a core event details" do |event_name, trigger|
14
+ include_context "core event"
15
+
16
+ it "can trigger without handlers" do
17
+ expect { subject.send trigger }.to_not raise_error
18
+ end
19
+
20
+ it "can be subscribed via #listen_for with :#{event_name}" do
21
+ subject.listen_for event_name, handler
22
+ expect { subject.send trigger }.to trigger(handler)
23
+ end
24
+
25
+ it "can be subscribed multiple times via #listen_for with :#{event_name}" do
26
+ subject.listen_for event_name, handler
27
+ subject.listen_for event_name.to_s, handler2
28
+ expect { subject.send trigger }.to trigger(handler).and_also(handler2)
29
+ end
30
+
31
+ it "listen_for requires a block or a handler" do
32
+ expect { subject.listen_for event_name }.to raise_error ArgumentError
33
+ end
34
+
35
+ it "listen_for ignores the block if a handler is given" do
36
+ subject.listen_for event_name, handler do
37
+ handler2.call
38
+ end
39
+ expect { subject.send trigger }.to trigger(handler).but_not(handler2)
40
+ end
41
+
42
+ it "can subscribe to a block" do
43
+ subject.listen_for event_name do
44
+ handler.call
45
+ end
46
+ expect { subject.send trigger }.to trigger(handler)
47
+ end
48
+
49
+ it "can clear a listener" do
50
+ subject.listen_for event_name, handler
51
+ subject.listen_for event_name, handler2
52
+ subject.dont_listen_for event_name, handler2
53
+ expect { subject.send trigger }.to trigger(handler).but_not(handler2)
54
+ end
55
+
56
+ it "works after clearing listeners" do
57
+ subject.listen_for event_name, handler
58
+ subject.clear event_name
59
+ subject.listen_for event_name, handler2
60
+ expect { subject.send trigger }.to trigger(handler2).but_not(handler)
61
+ end
62
+
63
+ it "works after clearing all listeners" do
64
+ subject.listen_for event_name, handler
65
+ subject.clear_all
66
+ subject.listen_for event_name, handler2
67
+ expect { subject.send trigger }.to trigger(handler2).but_not(handler)
68
+ end
69
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dilation
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jim Deville
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: celluloid
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 0.9.2
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.9.2
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: A controllable timer
63
+ email:
64
+ - james.deville@gmail.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - .rspec
71
+ - .yardopts
72
+ - Gemfile
73
+ - LICENSE
74
+ - README.md
75
+ - Rakefile
76
+ - dilation.gemspec
77
+ - lib/dilation.rb
78
+ - lib/dilation/core.rb
79
+ - lib/dilation/test_support/handler.rb
80
+ - lib/dilation/timers/coarse.rb
81
+ - lib/dilation/timers/test.rb
82
+ - lib/dilation/timers/timer.rb
83
+ - lib/dilation/utils/dilator.rb
84
+ - lib/dilation/utils/events.rb
85
+ - lib/dilation/version.rb
86
+ - spec/dilation/core_spec.rb
87
+ - spec/dilation/utils/dilator_spec.rb
88
+ - spec/integration.rb
89
+ - spec/spec_mini.rb
90
+ - spec/support/matchers.rb
91
+ - spec/support/shared_examples.rb
92
+ homepage: http://github.com/jredville/dilation
93
+ licenses: []
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: 1.9.2
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: 1.3.5
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 1.8.24
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: A timer that provides an event based interface to hook into for your app.
116
+ In addition, the back end timer can be swapped out, so for testing, you can control
117
+ the passage of time.
118
+ test_files:
119
+ - spec/dilation/core_spec.rb
120
+ - spec/dilation/utils/dilator_spec.rb
121
+ - spec/integration.rb
122
+ - spec/spec_mini.rb
123
+ - spec/support/matchers.rb
124
+ - spec/support/shared_examples.rb
125
+ has_rdoc: