Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

504 lines (357 sloc) 18.189 kB


SDLx::Controller - Handles the loops for events, movement and rendering


Extension, Controller


 use SDLx::Controller;

 # create our controller object
 my $app = SDLx::Controller->new;

 # but we usually do:
 my $app = SDLx::App->new;
 # because App is also a controller

 # register some callbacks
 $app->add_event_handler( \&on_event );
 $app->add_move_handler( \&on_move );
 $app->add_show_handler( \&on_show );

 # run our game loop


The core of an SDL application/game is the main loop, where you handle events and display your elements on the screen until something signals the end of the program. This usually goes in the form of:

 while (1) {

The problem most developers face, besides the repetitive work, is to ensure the screen update is independent of the frame rate. Otherwise, your game will run at different speeds on different machines and this is never good (old MS-DOS games, anyone?).

One way to circumveint this is by capping the frame rate so it's the same no matter what, but this is not the right way to do it as it penalizes better hardware.

This module provides an industry-proven standard for frame independent movement. It calls the movement handlers based on time (hi-res seconds) rather than frame rate. You can add/remove these handlers and control your main loop with ease. This module also provides methods for your other timing needs, such as pausing the game.



     dt             => 0.05,
     min_t          => 0,
     delay          => 1 / 200,
     event_handlers => [ @event_callbacks ],
     move_handlers  => [ @move_callbacks ],
     show_handlers  => [ @show_callbacks ],
     stop_handler   => \&stop_handler,
     event          => $event,
     time           => 99,

Creates and returns a new controller object with the specified params. All params are optional and have sane defaults.


The length, in seconds, of a full movement step. Defaults to 0.1. In most cases, the dt could be set to any number and the game could run almost identically (except for floating-point precision differences). This is because the dt is an enforcement of the maximum time between calling movement handlers. The actual time between calling move handlers may be much less, as the movement handlers are called at least once per frame. The specifics of this are explained in "add_move_handler".

Usually you wouldn't need this value to be lower than the time it takes an average computer to complete a cycle and render the frame. When you do need to run multiple move handlers per frame though, such as if you were checking collision between fast-moving objects, you can set the dt to some low value (less than 1/60). Otherwise, leaving it at 0.1 is fine.

Regardless of whether you need to enforce a maximum time between move handlers, this system has its benefits. Modifying the dt without touching any of the other code in your program will result in a time-scaling effect. If you lower the dt, everything will move faster than it did with it set higher, and vice-versa. This is useful to add slo-mo and fast-forward features to the game. All you would have to do is change the dt.


The minimum time, in seconds, that has to accumulate before any move or show handlers are called. Defaults to 1 / 60. A min_t of 1 / 60 ensures that the controller can update the screen at a maximum of 60 times per second. A "V-Sync" such as this is necessary to prevent video "tear", which occurs when the app is updating faster than the monitor can display. Setting it to 0, as seen above, will not delay the loop at all.


The time, in seconds or milliseconds, to delay after every full "run" loop. Defaults to 0. If you specify a number greater than or equal to 1, it will be treated as milliseconds instead of seconds. NOTE: Picking a good delay based on the needs of your game can greatly reduce CPU load and pressure.


An array ref of the corresponding handler callbacks. All default to []. This is basically a shortcut way of adding handlers. They would otherwise be added with their corresponding add_*_handler method. See below for a full explanation of the "run" loop and handlers.


An extra, but separate, event callback to handle all stopping of the app. It is the same in almost every aspect to an event handler (see "add_event_handler"): same recieved arguments, called in the same place.

One difference is that it is called in "pause" so that the app can be stopped while paused. Another difference is that it should always apply to the app; while you add, remove and clear handlers it wont be touched. This is good, because you'd (probably) always want your app to able to be stopped. Because of this, it's a good idea to use the stop handler regardless of whether you will be using "pause".

Defaults to \&SDLx::Controller::default_stop_handler: a callback that stops the event loop on an SDL_QUIT event. Specify a code ref to use a different callback to handle stopping, or a false value to not use a stop handler. If you want to provide your own stop handler you should give it the code of the default stop handler:

 my ($event, $self) = @_;
 $self->stop() if $event->type == SDL_QUIT;

followed by any other code to handle events also triggering the app to stop, such as the user pressing Esc.


The SDL::Event object that events going to the event callbacks are polled in to. Defaults to SDL::Event->new().


The time, in seconds, that you want the "run" loop to say it has been going for. This has no affect on the run loop. All it will do is alter what "time" returns. See "time". Defaults to 0. You'll seldom have to set this param.



After creating and setting up your handlers (see below), call this method to enter the main loop. This loop will run until "stop" is called.

All added handlers will be called during the run loop, in this order:

1. Events
2. Movements
3. Displaying

Please refer to each handler below for full information on what they do. Note that the second argument every callback recieves is the app object.


 my $index = $app->add_event_handler(
     sub {
         my ($event, $app) = @_;
         # handle event ...

Adds a callback to the end of the event handler list. You can add as many subs as you need. For each SDL::Event from the user, all registered callbacks will be called in order and supplied with it. Returns the index of the added callback.

More specifically: events from the user will, one by one, be polled into the app's "event" object. This event will then be passed to all of the registered callbacks as the first argument. The second argument is the app.

Below is an example of an event handler that sets a variable true when the left mouse button is down and false otherwise.

 our $click = 0;

 sub on_click {
     my ($event, $app) = @_;
     my $state =
         $event->type == SDL_MOUSEBUTTONDOWN ? 1 :
         $event->type == SDL_MOUSEBUTTONUP   ? 0 :
     if($event->button_button == SDL_BUTTON_LEFT) {
         $click = $state;


 my $index = $app->add_move_handler(
     sub {
         my ($step, $app, $time) = @_;
         # handle moving ...

Adds a callback to the end of the movement handler list. You can add as many subs as you need. All registered callbacks will be triggered in order for as many dt as have happened between calls, and once more for the remaining time less than dt. A reasonable dt for a game will usually be a number greater than the time you would ever expect to have passed between frames. This means that your movement handlers will ordinarily only be called once per frame. See the discussion of dt in "new". Returns the index of the added callback.

The first argument passed to the callbacks is the fraction of dt time that the move callback should handle. This will be 1 for a full step and less than 1 for a partial step. Inversely, the time that the each move callback should handle is equal to the step argument multiplied by the dt. All movement values should be multiplied by the step value.

It is possible for the argument to be 0 if no time has passed since the last cycle. It's best to protect against this by supplying the app a small "delay" value.

The second argument passed to the callbacks is the app object.

The third is the value returned by "time". See "time".

You should use these handlers to update your in-game objects, check collisions, etc. Below is an example of how you might move an object. Note that the movement value, a velocity in this case, is multiplied by the step argument.

 our $ball = MyBall->new;

 sub move_ball {
     my ($step, $app, $t) = @_;
     $ball->move_x( $ball->x_vel * $step );
     $ball->move_y( $ball->y_vel * $step );


 my $index = $app->add_show_handler(
     sub {
         my ($delta, $app) = @_;
         # handle showing ...

Adds a callback to the end of the rendering handler list. You can add as many subs as you need. All registered callbacks will be triggered in order, once per cycle of the "run" loop. Returns the index of the added callback.

The first argument passed is the time, in seconds, since the previous show. This can be used to display a rough FPS value by dividing 1 by it.

The second argument is the app object.

 our $ball = MyBall->new;

 sub show_ball {
     my ($delta, $app) = @_;

     # the drawing below works if the app is an SDLx::App
     # and not just a controller
         [ $ball->x, $ball->y, $ball->size, $ball->size ],




 my $event_handlers = $app->event_handlers;
 my $move_handlers = $app->move_handlers;
 my $show_handlers = $app->show_handlers;

Returns the corresponding array ref so that you can directly modify the handler list.




 my $removed_handler = $app->remove_event_handler( $index );
 my $removed_handler = $app->remove_event_handler( $callback );
 my $removed_handler = $app->remove_move_handler( $index );
 my $removed_handler = $app->remove_move_handler( $callback );
 my $removed_handler = $app->remove_show_handler( $index );
 my $removed_handler = $app->remove_show_handler( $callback );

Removes the handler with the given index from the respective handler list.

You can also pass a coderef. The first coderef in the handler list that matches this will be removed.

Returns the removed handler.





Removes all handlers from the respective handler list. None of these will remove the app's "stop_handler".



Shortcut to remove all handlers at once. This will not remove the app's "stop_handler".



Tells the controller to end the run loop. This only has meaning when called from within a handler of the run loop. The "run" loop will complete the current cycle (handling events, moves and shows) and then return. This graceful way of ending the game loop is preferred and it is the way the default "stop_handler" does it.

Once the "run" loop has been stopped, it can be started again without problems. This technique should be used to do operations that take a long time outside of the timing of the app.




This code snippet could be used to play the first part of a game, then load the next part and resume playing. When the first run loop is stopped the expensive operation will be executed. Once that has completed the second run loop will resume the game, ignoring the time that passed outside the run loop. If the expensive operation was performed from within the run loop, upon completing the operation the move handlers would take into account all the time passed.


 my $stopped = $app->stopped;

Returns true if the run loop is stopped (before and after being in the run loop). Also returns true when the run loop is about to stop. That is, true when the app will complete the current run cycle before stopping.


     sub {
         my ($event, $app) = @_;
         # handle event ...

         return 1 if ... ; # unpause
         return; # stay paused

Pauses the application with a call to SDL::Events::wait_event|SDL::Events/wait_event. This only has meaning when called from within a handler of the run loop. Events can then be used to unpause the app. This is done outside the timing of the app with the same technique as explained in "stop".

Takes 1 argument which is a callback. The application completes the current run loop then starts waiting for the next event with wait_event. This means that pause can be called by any kind of handler in the run loop. If "stop" is called during the same run cycle, before or after calling pause, the app will just stop instead of pausing.

When wait_event receives an event, it is passed to the callback as the first argument. Just like an event handler, the second argument passed is the app. If the callback then returns a true value, pause will return. If the callback returns a false value, the app will stay paused and the process will be repeated.

If a "stop_handler" is defined, then each event will also be passed to that. This will allow the app to be stopped while being paused. If the stop handler calls "stop" then the app will unpause and then stop. If your app doesn't have a stop handler then you'll have to handle stopping yourself in the pause callback.

Below is an example of pause used to implement a pause and unpause when the app loses and gain focus. As a neat shortcut, the callback is recursively defined and used as both an event handler and the pause callback.

 sub window {
     my ($event, $app) = @_;
     if($event->type == SDL_ACTIVEEVENT) {
         if($event->active_state & SDL_APPINPUTFOCUS) {
             if($event->active_gain) { # gained focus
                 return 1; # unpause
             else {
                 # recursive, but only once since the window
                 # can't lose focus again without gaining it first


 my $paused = $app->paused;

Returns true if the run loop is currently paused. Also returns true when the run loop is about to pause. That is, true when the app will complete the current run cycle before pausing. This has dual purposes. Firstly, it is useful from within the paused callback as shown below.

 sub toggle_pause { # press P to toggle pause
     my ($event, $app) = @_;
     if($event->type == SDL_KEYDOWN) {
         if($event->key_sym == SDLK_p) {
             # We're paused, so end pause
             return 1 if $app->paused;

             # We're not paused, so pause

Secondly, it is useful for handlers to tell if the app is about to pause. If "pause" is called from within an event handler, then the move and show handlers can check and respond to "paused" in the remainder of the run cycle. For example, a show handler could make the screen say PAUSED when "paused" is true. This is preferred to having the event handler display this, because "stop" could be called in the meantime.






 my $dt           = $app->dt;
 my $min_t        = $app->min_t;
 my $delay        = $app->delay;
 my $stop_handler = $app->stop_handler;
 my $event        = $app->event;
 $app->dt          ($dt);
 $app->min_t       ($min_t);
 $app->delay       ($delay);
 $app->event       ($event);

If an argument is passed, modifies the corresponding value to the argument. dt and min_t will keep their old value until the beginning of the next run cycle. See "new" for details on what these params do.

Returns the corresponding value.


 my $time = $app->time;

Returns the sum of all the dts that have been handled by all move handlers. In other words, the total amount of time that has passed in the run loop. When the run loop is stopped and resumed, this value is not reset. This should be a useful value to have, but isn't a replacement for SDL::get_ticks. Use Time::HiRes::time instead of get_ticks.

Specify a value to count from that time. This will have no effect on the run loop itself, but may be useful for the code in your handlers.



Causes the app to sleep for the specified time, in hi-res seconds, or forever if no argument is specified. Use this instead of SDL::delay.




The idea and base for the "run" loop comes from Lazy Foo's Frame Independent Movement tutorial, and Glenn Fiedler's Fix Your Timestep article on timing.


SDLx::App, SDL::Event, SDLx::Surface

Jump to Line
Something went wrong with that request. Please try again.