Class: Rubygame::Clock
- Inherits:
-
Object
- Object
- Rubygame::Clock
- Defined in:
- lib/rubygame/clock.rb
Overview
Clock provides class methods for tracking running time and delaying execution of the program for specified time periods. This is used to provide a consistent framerate, prevent the program from using all the processor time, etc.
Clock also provides instance methods to make it convenient to monitor and limit application framerate. See #tick.
An in-depth tutorial on using Clock is available. See doc/managing_framerate.rdoc[files/doc/managing_framerate_rdoc.html] in the Rubygame source distribution or in the online documentation.
Instance Attribute Summary collapse
-
#granularity ⇒ Object
Granularity used for framerate limiting delays in #tick.
-
#nice ⇒ Object
Whether to try to let other ruby threads run during framerate limiting delays in #tick.
-
#start ⇒ Object
readonly
The runtime when the Clock was initialized.
-
#ticks ⇒ Object
readonly
The number of times #tick has been called.
Class Method Summary collapse
-
.delay(time, gran = 12, nice = false) ⇒ Object
- time
-
The target delay time, in milliseconds.
-
.runtime ⇒ Object
Return the number of milliseconds since the Rubygame timer system was initialized.
-
.wait(time, nice = false) ⇒ Object
- time
-
The target wait time, in milliseconds.
Instance Method Summary collapse
-
#calibrate(max_time = 0.5) ⇒ Object
Calibrate some Clock settings to match the current computer.
-
#enable_tick_events ⇒ Object
Enable tick events, so that #tick will return a ClockTicked instance instead of a number of milliseconds.
-
#framerate ⇒ Object
call-seq: framerate -> Float.
-
#frametime ⇒ Object
call-seq: frametime -> Float.
-
#initialize {|_self| ... } ⇒ Clock
constructor
Create a new Clock instance.
-
#lifetime ⇒ Object
call-seq: lifetime -> Integer.
-
#target_framerate ⇒ Object
Returns the current target framerate (frames/second), or nil if there is no target.
-
#target_framerate=(framerate) ⇒ Object
Sets the target number of frames per second to
framerate. -
#target_frametime ⇒ Object
Returns the current target frametime (milliseconds/frame), or nil if there is no target.
-
#target_frametime=(frametime) ⇒ Object
Sets the target milliseconds per frame to
frametime. -
#tick ⇒ Object
Returns the number of milliseconds since you last called this method.
Constructor Details
#initialize {|_self| ... } ⇒ Clock
Create a new Clock instance.
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/rubygame/clock.rb', line 227 def initialize() @start = self.class.runtime() @last_tick = nil @ticks = 0 @target_frametime = nil # Frametime samples for framerate calculation @samples = [] @max_samples = 20 @granularity = 12 @nice = false # Should #tick return a ClockTicked event? @tick_events = false # Cache for past tick events with specific ms values @tick_cache = {} yield self if block_given? end |
Instance Attribute Details
#granularity ⇒ Object
Granularity used for framerate limiting delays in #tick. You can calibrate this easily with #calibrate. See also #tick and Clock.delay. (Non-negative integer. Default: 12.)
218 219 220 |
# File 'lib/rubygame/clock.rb', line 218 def granularity @granularity end |
#nice ⇒ Object
Whether to try to let other ruby threads run during framerate limiting delays in #tick. See #tick and Clock.delay. (true or false. Default: false.)
223 224 225 |
# File 'lib/rubygame/clock.rb', line 223 def nice @nice end |
#start ⇒ Object (readonly)
The runtime when the Clock was initialized.
208 209 210 |
# File 'lib/rubygame/clock.rb', line 208 def start @start end |
#ticks ⇒ Object (readonly)
The number of times #tick has been called.
211 212 213 |
# File 'lib/rubygame/clock.rb', line 211 def ticks @ticks end |
Class Method Details
.delay(time, gran = 12, nice = false) ⇒ Object
- time
-
The target delay time, in milliseconds. (Non-negative integer. Required.)
- gran
-
The assumed granularity (in ms) of the system clock. (Non-negative integer. Optional. Default: 12.)
- nice
-
If true, try to let other ruby threads run during the delay. (true or false. Optional. Default: false.)
- Returns
-
The actual delay time, in milliseconds.
Pause the program for time milliseconds. This function is more accurate than Clock.wait, but uses slightly more CPU time. Both this function and Clock.wait can be used to slow down the framerate so that the application doesn’t use too much CPU time. See also Clock#tick for a good and easy way to limit the framerate.
This function uses “busy waiting” during the last part of the delay, for increased accuracy. The value of gran affects how many milliseconds of the delay are spent in busy waiting, and thus how much CPU it uses. A smaller gran value uses less CPU, but if it’s smaller than the true system granularity, this function may delay a few milliseconds too long. The default value (12ms) is very safe, but a value of approximately 5ms would give a better balance between accuracy and CPU usage on most modern computers. A granularity of 0ms makes this method act the same as Clock.wait (i.e. no busy waiting at all, very low CPU usage).
If nice is true, this function will try to allow other ruby threads to run during this function. Otherwise, other ruby threads will probably also be paused. Setting nice to true is only useful if your application is multithreaded. It’s safe (but pointless) to use this feature for single threaded applications.
The Rubygame timer system will be initialized when you call this function, if it has not been already. See Clock.runtime.
83 84 85 86 87 88 |
# File 'lib/rubygame/clock.rb', line 83 def delay( time, gran=12, nice=false ) _init_sdl_timer time = 0 if time < 0 gran = 0 if gran < 0 _accurate_delay( time, gran, nice ) end |
.runtime ⇒ Object
Return the number of milliseconds since the Rubygame timer system was initialized.
The Rubygame timer system will be initialized when you call this function, if it has not been already.
131 132 133 |
# File 'lib/rubygame/clock.rb', line 131 def runtime SDL.GetTicks().to_i end |
.wait(time, nice = false) ⇒ Object
- time
-
The target wait time, in milliseconds. (Non-negative Integer. Required.)
- nice
-
If true, try to let other ruby threads run during the delay. (true or false. Optional.)
- Returns
-
The actual wait time, in milliseconds.
Pause the program for approximately time milliseconds. Both this function and Clock.delay can be used to slow down the framerate so that the application doesn’t use too much CPU time. See also Clock#tick for a good and easy way to limit the framerate.
The accuracy of this function depends on processor scheduling, which varies with operating system and hardware. The actual delay time may be up to 10ms longer than time. If you need more accuracy use Clock.delay, which is more accurate but uses slightly more CPU time.
If nice is true, this function will try to allow other ruby threads to run during this function. Otherwise, other ruby threads will probably also be paused. Setting nice to true is only useful if your application is multithreaded. It’s safe (but pointless) to use this feature for single threaded applications.
The Rubygame timer system will be initialized when you call this function, if it has not been already. See Clock.runtime.
118 119 120 121 122 |
# File 'lib/rubygame/clock.rb', line 118 def wait( time, nice=false ) _init_sdl_timer time = 0 if time < 0 _threaded_delay( time, nice ) end |
Instance Method Details
#calibrate(max_time = 0.5) ⇒ Object
Calibrate some Clock settings to match the current computer. This improves efficiency and minimizes CPU usage without reducing accuracy.
As of Rubygame 2.5, this method calibrates @granularity. See #tick and Clock.delay for more information about the effect of setting granularity. In future versions of Rubygame, this method may also calibrate additional Clock attributes.
By default, the calibration takes a maximum of 0.5 seconds to complete. You can specify a different maximum length by passing a different value for max_time. In future versions of Rubygame, calibration may take less than max_time, but will not take more. Also, the default max_time may be lowered in future versions, but will not be raised.
You usually only need to call this once, after you create the Clock instance at the start of your application. You should not run any other ruby threads at the same time, as doing so will skew the calibration.
–
I’m not 100% sure that this is a valid way to measure granularity, or that the granularity of ruby sleep is always the same as that of SDL_Delay. But it can be improved later if needed…
++
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/rubygame/clock.rb', line 280 def calibrate( max_time = 0.5 ) samples = [] end_time = Time.now + max_time while( Time.now < end_time ) t = Time.now sleep 0.01 samples << (Time.now - t - 0.01) end average = samples.inject{|sum,n| sum + n} / samples.length # convert to ms, add some padding gran = (average * 1000).to_i + 1 @granularity = gran return nil end |
#enable_tick_events ⇒ Object
Enable tick events, so that #tick will return a ClockTicked instance instead of a number of milliseconds.
This option is available starting in Rubygame 2.5, and will become the default in Rubygame 3.0.
308 309 310 |
# File 'lib/rubygame/clock.rb', line 308 def enable_tick_events @tick_events = true end |
#framerate ⇒ Object
call-seq:
framerate -> Float
Return the actual framerate (frames per second) recorded by the Clock. See #tick.
387 388 389 390 391 392 393 394 |
# File 'lib/rubygame/clock.rb', line 387 def framerate sum = @samples.inject(0){|sum, n| sum + n} if sum == 0 return 0.0 else 1000.0 * @samples.length / sum end end |
#frametime ⇒ Object
call-seq:
frametime -> Float
Return the actual frametime (milliseconds per frame) recorded by the Clock. See #tick.
403 404 405 406 407 |
# File 'lib/rubygame/clock.rb', line 403 def frametime @samples.inject(0){|sum, n| sum + n} / (@samples.length) rescue ZeroDivisionError 0.0 end |
#lifetime ⇒ Object
call-seq:
lifetime -> Integer
Returns time in milliseconds since this Clock instance was created.
376 377 378 |
# File 'lib/rubygame/clock.rb', line 376 def lifetime self.class.runtime() - @start end |
#target_framerate ⇒ Object
Returns the current target framerate (frames/second), or nil if there is no target.
This is another to access #target_frametime. Same as: 1000.0 / #target_frametime
342 343 344 345 346 347 348 349 350 |
# File 'lib/rubygame/clock.rb', line 342 def target_framerate if @target_frametime 1000.0 / @target_frametime else nil end rescue ZeroDivisionError return nil end |
#target_framerate=(framerate) ⇒ Object
Sets the target number of frames per second to framerate. If framerate is nil, the target is unset, and #tick will no longer apply any delay.
This is another way to access #target_frametime. Same as: #target_frametime = 1000.0 / framerate
360 361 362 363 364 365 366 367 368 |
# File 'lib/rubygame/clock.rb', line 360 def target_framerate=( framerate ) if framerate @target_frametime = 1000.0 / framerate else @target_frametime = nil end rescue ZeroDivisionError @target_frametime = nil end |
#target_frametime ⇒ Object
Returns the current target frametime (milliseconds/frame), or nil if there is no target.
This is another way to access #target_framerate. Same as: 1000.0 / #target_framerate
319 320 321 |
# File 'lib/rubygame/clock.rb', line 319 def target_frametime @target_frametime end |
#target_frametime=(frametime) ⇒ Object
Sets the target milliseconds per frame to frametime. If frametime is nil, the target is unset, and #tick will no longer apply any delay.
This is another way to access #target_framerate. Same as: #target_framerate = 1000.0 / frametime
331 332 333 |
# File 'lib/rubygame/clock.rb', line 331 def target_frametime=( frametime ) @target_frametime = frametime end |
#tick ⇒ Object
Returns the number of milliseconds since you last called this method. Or, if you have called #enable_tick_events, this returns a ClockTicked event representing the time since you last called this method. (ClockTicked was added in Rubygame 2.5, and will become the default and only option in Rubygame 3.0.)
You must call this method once per frame (i.e. per iteration of your main loop) if you want to use the framerate monitoring and/or framerate limiting features.
Framerate monitoring allows you to check the #framerate (frames per second) or #frametime (milliseconds per frame) of your game.
Framerate limiting allows you to prevent the application from running too fast (and using 100% of processor time) by pausing the program very briefly each frame. The pause duration is calculated each frame to maintain a stable framerate.
Framerate limiting is only enabled if you have set the #target_framerate= or #target_frametime=. If you have done that, this method will automatically perform the delay each time you call it.
There are two other attributes which affect framerate limiting, #granularity and #nice. These are passed as parameters to Clock.delay for the brief pause each frame. See Clock.delay for the effects of those parameters on CPU usage and threading.
(Please note that no effort is made to correct a framerate which is slower than the target framerate. Clock can’t make your code run faster, only slow it down if it is running too fast.)
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 |
# File 'lib/rubygame/clock.rb', line 442 def tick() # how long since the last tick? passed = 0 if @last_tick passed += self.class.runtime() - @last_tick end if @target_frametime extra = @target_frametime - passed if( extra > 0 ) passed += self.class.delay( extra, @granularity, @nice ) end end if @tick_events return (@tick_cache[passed] or (@tick_cache[passed] = Rubygame::Events::ClockTicked.new( passed ) )) else return passed end ensure @last_tick = self.class.runtime() @ticks += 1 # Save the frametime for framerate calculation @samples.push(passed) @samples.shift if @samples.length > @max_samples end |