pods::...roller(3pm)

πŸ’… SDLx::Controller

okay bestie, so this whole page is from pods::...roller(3pm) in the User Contributed Perl Documentation β€” yes that's a mouthful, we're moving on πŸ’–

✨ NAME ✨

SDLx::Controller β€” the thing that handles the loops for events, movement and rendering πŸŽ€

🌸 CATEGORY 🌸

Extension, Controller

πŸ¦‹ SYNOPSIS πŸ¦‹

use SDLx::Controller;

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

# we could also 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
$app->run;

✨ DESCRIPTION ✨

So the main character of literally any SDL app or game is the main loop β€” you handle events, you show your stuff on screen, and you keep doing that until something says "we're done here." And it usually looks like this:

while (1) {
    ...
}

Now, the problem every dev runs into (besides how repetitive this whole thing is 😭) is making sure the screen update doesn't depend on the frame rate. Because if it does, your game runs at different speeds on different machines, and that is never it. Old MS-DOS games, anyone? πŸ’€

One way people try to dodge this is by capping the frame rate so it's identical everywhere β€” but no, that's not the right way, because it punishes the people with better hardware. Not the vibe. πŸ™ˆ

So this module gives you an industry-proven standard for frame independent movement β€” it calls the movement handlers based on time (hi-res seconds) instead of frame rate. You can add and remove handlers and control your main loop with ease, slay. πŸ‘‘


πŸ”₯ METHODS πŸ”₯

🧚 new

SDLx::Controller->new(
    dt    => 0.5,
    min_t => 0,
    event => $event_object,
);
  • dt β€” the length, in seconds, of a full movement step, and it defaults to 0.1. Ngl, dt can be literally anything and the game will still look the same. The only time it makes a difference is when you change dt without also changing all the things in the movement step that get multiplied by that first move argument. If you lower the dt, everything moves faster than it did with it set higher, and vice versa. Which is genuinely so useful β€” instant slo-mo and fast-forward features, all you gotta do is change the dt πŸŽ€
  • min_t β€” the minimum time, in seconds, that has to accumulate before any move or show handlers get called, and it defaults to 1 / 60. Keeping min_t at 1 / 60 means the controller can update the screen at a maximum of 60 times per second. A "V-Sync" like this is necessary to prevent video "tear" β€” that's what happens when the app updates faster than the monitor can display. Setting it to 0, like in the example above, lets the app run as fast as it possibly can πŸ”₯
  • delay β€” a loop delay in millisecs to place on the controller loop. NOTE: picking a good delay based on your needs can help reduce CPU load and pressure 🫢
  • event β€” an SDL::Event object that events going to the event callbacks are polled into. Defaults to SDL::Event->new().

All parameters are optional, no pressure πŸ’…

Returns the new object.

πŸŽ€ run

Once you've created everything and set up your handlers (see below), call this to activate the main loop. It'll keep running until stop gets called.

All hooked functions get called during the main loop, in this exact order:

  1. Events
  2. Movements
  3. Displaying

Peep each handler below for the deets on received arguments. Also note that the second argument every single callback receives is the SDLx::Controller object πŸ’–

πŸ¦‹ stop

Returns from the run loop. That's it, that's the whole thing. ✨

🌈 pause

Attempts to pause the app with a call to SDL::Events::wait_event. See SDL::Events.

It takes 1 argument, which is a callback. The app waits for the next event with wait_event. When one arrives, it gets passed to the callback as the first argument, along with the SDLx::Controller object as the second argument. If the callback then returns a true value, pause returns. If the callback returns a false value, pause repeats the process.

This is how you can easily implement a pause when the app loses focus:

sub window {
    my ($e, $app) = @_;
    if($e->type == SDL_QUIT) {
        $app->stop;
        # quit handling is here so that the app
        # can be stopped while paused
    }
    elsif($e->type == SDL_ACTIVEEVENT) {
        if($e->active_state & SDL_APPINPUTFOCUS) {
            if($e->active_gain) {
                return 1;
            }
            else {
                $app->pause(\&window);
                # recursive, but only once since the window
                # can't lose focus again without gaining it first
            }
        }
    }
    return 0;
}
Note: if you write your own pause function, remember to update current_time to the current time when the app unpauses. Do it with Time::HiRes::time. Otherwise time keeps accumulating while the app is paused and then a whole bunch of movement steps get called all at once when it unpauses 😭
Note 2: a pause is potentially dangerous to the run cycle (even if you implement your own) unless it's called by an event callback. Fr fr, be careful.

πŸ’œ paused

Returns 1 if the app is paused, undef otherwise. This is only useful when used inside code that'll be run by pause:

sub pause {
    # press P to toggle pause

    my ($e, $app) = @_;
    if($e->type == SDL_QUIT) {
        $app->stop;
        # quit handling is here so that the app
        # can be stopped while paused
    }
    elsif($e->type == SDL_KEYDOWN) {
        if($e->key_sym == SDLK_p) {
            # We're paused, so end pause
            return 1 if $app->paused;

            # We're not paused, so pause
            $app->pause(\&pause);
        }
    }
    return 0;
}

🌟 add_event_handler

Registers a callback to handle events. You can add as many subs as you need. Whenever an SDL::Event occurs, all registered callbacks get triggered in order. Returns the order queue number of the added callback.

The first argument passed to registered callbacks is the SDL::Event object. The second is the SDLx::Controller object.

sub stop {
   my ($event, $app) = @_;
   if($event->type == SDL_QUIT) {
       $app->stop;
   }
}
$app->add_event_handler(\&stop);

πŸ“ add_move_handler

Registers a callback to update your objects. You can add as many subs as you need. Returns the order queue number of the added callback.

All registered callbacks get triggered in order for as many dt as have happened between calls, plus once more for any remaining time less than dt. The first argument passed to the callbacks is the portion of the step β€” it'll be 1 for a full step, and less than 1 for a partial step. Movement values should be multiplied by this value, iykyk. The full steps correspond to the amount of dt passed between calls, and the partial step corresponds to the call with the remaining time less than dt. Heads up: that argument can be 0 if no time has passed since the last cycle. If you need to protect against that, set a min_t, or drop a return unless $_[0] at the start of every move handler πŸ’…

The second argument passed to the callbacks is the SDLx::Controller object. The third is the total amount of time passed since run was called.

You should use these handlers to update your in-game objects, check collisions, etc., so you can check and/or update things as necessary.

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

πŸŽ€ add_show_handler

Registers a callback to render objects. You can add as many subs as you need. Returns the order queue number of the added callback. All registered callbacks get triggered in order, once per run of the run loop.

The first argument passed is the time, in seconds, since the previous call. The second is the SDLx::Controller object.

sub show_ball {
    my ($delta, $app) = @_;
    $app->draw_rect(
        [ $ball->x, $ball->y, $ball->size, $ball->size ],
        $ball->colour
    );
}

🧚 remove_move_handler( $index ), remove_event_handler( $index ), remove_show_handler( $index )

Removes the handler with the given index from the respective calling queue.

You can also pass a coderef β€” the first coderef in the handler list that matches it gets removed.

Returns the removed handler. 🫢

πŸ¦‹ remove_all_move_handlers, remove_all_event_handlers, remove_all_show_handlers

Removes all handlers from the respective calling queue. Clean slate energy ✨

πŸ’– remove_all_handlers

Quick access to removing all handlers at once. The nuclear option, iconic.

πŸ‘‘ dt, min_t, current_time

If you pass an argument, it modifies the corresponding value to that argument. dt and min_t will keep their old value until the beginning of the next run cycle.

Returns the corresponding value.


🌸 AUTHORS 🌸

See "AUTHORS" in SDL.

⭐ ACKNOWLEDGEMENTS

The idea and base for this module comes from Lazy Foo's Frame Independent Movement <http://www.lazyfoo.net/SDL_tutorials/lesson32/index.php> tutorial, and Glenn Fiedler's Fix Your Timestep <http://gafferongames.com/game-physics/fix-your-timestep/> article on timing. Genuinely obsessed with these, go read them πŸ’œ


okay that's the whole page bestie πŸ’… this was perl v5.42.3, dated 2026-08-19, and the page itself is pods::...roller(3pm) β€” go make something frame-independent and gorgeous, mwah πŸŽ€πŸŒˆβœ¨

πŸ“¦ where this page comes from