Class: Rubygame::Clock

Inherits:
Object
  • Object
show all
Defined in:
lib/rubygame/clock.rb,
ext/rubygame/rubygame_clock.c

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

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize {|_self| ... } ⇒ Clock

Create a new Clock instance.

Yields:

  • (_self)

Yield Parameters:



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/rubygame/clock.rb', line 64

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

#granularityObject

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.)



55
56
57
# File 'lib/rubygame/clock.rb', line 55

def granularity
  @granularity
end

#niceObject

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.)



60
61
62
# File 'lib/rubygame/clock.rb', line 60

def nice
  @nice
end

#startObject (readonly)

The runtime when the Clock was initialized.



45
46
47
# File 'lib/rubygame/clock.rb', line 45

def start
  @start
end

#ticksObject (readonly)

The number of times #tick has been called.



48
49
50
# File 'lib/rubygame/clock.rb', line 48

def ticks
  @ticks
end

Class Method Details

.delay(time, gran = 12, nice = false) ⇒ Integer

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.

Returns:

  • (Integer)


225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'ext/rubygame/rubygame_clock.c', line 225

VALUE rbgm_clock_delay(int argc, VALUE *argv, VALUE module)
{
  rg_init_sdl_timer();

  VALUE vtime, vgran, vnice;

  rb_scan_args(argc,argv,"12", &vtime, &vgran, &vnice);

  int delay = NUM2INT(vtime);
  if( delay < 0 )
  {
    delay = 0;
  }

  int gran;
  if( RTEST(vgran) )
  {
    gran = NUM2UINT(vgran);
    if( gran < 0 )
    {
      gran = 0;
    }
  }
  else
  {
    gran = WORST_CLOCK_ACCURACY;
  }

  int nice = (vnice == Qtrue) ? 1 : 0;

  return UINT2NUM( accurate_delay(delay, gran, nice) );
}

.runtimeInteger

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.

Returns:

  • (Integer)


270
271
272
273
274
275
# File 'ext/rubygame/rubygame_clock.c', line 270

VALUE rbgm_clock_runtime( VALUE module )
{
  rg_init_sdl_timer();

  return UINT2NUM(SDL_GetTicks());
}

.wait(time, nice = false) ⇒ Integer

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.

Returns:

  • (Integer)


124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'ext/rubygame/rubygame_clock.c', line 124

VALUE rbgm_clock_wait(int argc, VALUE *argv, VALUE module)
{
  rg_init_sdl_timer();

  VALUE  vtime, vnice;

  rb_scan_args(argc,argv,"11", &vtime, &vnice);

  int delay = NUM2INT(vtime);
  if( delay < 0 )
  {
    delay = 0;
  }

  int nice = (vnice == Qtrue) ? 1 : 0;

  return UINT2NUM( rg_threaded_delay(delay, nice) );
}

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…

++



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/rubygame/clock.rb', line 117

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_eventsObject

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.



145
146
147
# File 'lib/rubygame/clock.rb', line 145

def enable_tick_events
  @tick_events = true
end

#framerateObject

call-seq:

framerate  ->  Float

Return the actual framerate (frames per second) recorded by the Clock. See #tick.



224
225
226
227
228
# File 'lib/rubygame/clock.rb', line 224

def framerate
  1000.0 * @samples.length / @samples.inject(0){|sum, n| sum + n}
rescue ZeroDivisionError
  0.0
end

#frametimeObject

call-seq:

frametime  ->  Float

Return the actual frametime (milliseconds per frame) recorded by the Clock. See #tick.



237
238
239
240
241
# File 'lib/rubygame/clock.rb', line 237

def frametime
  @samples.inject(0){|sum, n| sum + n} / (@samples.length)
rescue ZeroDivisionError
  0.0
end

#lifetimeObject

call-seq:

lifetime  ->  Integer

Returns time in milliseconds since this Clock instance was created.



213
214
215
# File 'lib/rubygame/clock.rb', line 213

def lifetime
  self.class.runtime() - @start
end

#target_framerateObject

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



179
180
181
182
183
184
185
186
187
# File 'lib/rubygame/clock.rb', line 179

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



197
198
199
200
201
202
203
204
205
# File 'lib/rubygame/clock.rb', line 197

def target_framerate=( framerate )
  if framerate
    @target_frametime = 1000.0 / framerate
  else
    @target_frametime = nil
  end
rescue ZeroDivisionError
  @target_frametime = nil
end

#target_frametimeObject

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



156
157
158
# File 'lib/rubygame/clock.rb', line 156

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



168
169
170
# File 'lib/rubygame/clock.rb', line 168

def target_frametime=( frametime )
  @target_frametime = frametime
end

#tickObject

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.)



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/rubygame/clock.rb', line 276

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