Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

SDLx::App event loop

by Ransom (Beadle)
on Jun 11, 2012 at 14:59 UTC ( [id://975587]=perlquestion: print w/replies, xml ) Need Help??

Ransom has asked for the wisdom of the Perl Monks concerning the following question:

Hey monks,

UPDATE: When I say event in the following question, it is referring to a keyboard event. However, the question should still stand for any type of event.

UPDATE 2: CODE BELOW.

I've been working (playing) with Perl's SDL implementation, specifically SDLx::App wrappings. Overall I'm pleased with the ease of prototyping and how simple some things are to implement. My headache comes from using the provided SDL::Controller main loops in regards to event callbacks.

Basic documentation is provided here http://search.cpan.org/~jtpalmer/SDL-2.540/lib/pods/SDLx/Controller.pod. In order to describe my problem, I'll walk through a scenario. You are an object on screen which can react to various events. Since there are no events in the SDL queue, none of your event handlers are called. Makes sense. Now the user depresses the space bar, and holds it down. A single key_down event is added to the event queue which triggers your space_bar_handler method. The issue deals with repeating events or reacting to state outside of the presense of events.

I would like the user to be able to hold down the space bar and have things "keep happening" as long as the key is held. Preferrably, I'll be able to control the time between events.

My first solution was to use the SDL key_repeat feature. This is adequate for a single situation, but has the side effect of limiting ALL repeating events to the particular delay. In my example, if delay between bullets (space_bar) was changed, so was my movement step.

My second solution was to remove the key_repeat and simply pump a blank event every frame, in essence making sure all event handlers were called each frame. This was completely viable but not very controllable. It's fine to react to a movement command every frame, but creating a new bullet object every frame doesn't make much sence in my context.

My question revolves around this callback loop event scheme. Is there a particularly "good" way to time events in this system. Should I be firing blank events every frame? Would an experienced SDLx::App developer be able to explain why this scheme was used and how to use it effectively?

My current code even uses SDL_key_state information to determine if a key is down (rather that just pressed for an event). This makes so much sense to use, but alas, the function is only called when an event occurs. My thought was to do away with the event loop for my desired behavior. I moved all my event callback subs into move callback subs, which seems to have the same effect of pumping a blank event every frame. This still leaves the problem of timing events. Is there a way to asynchronously delay this behavior if default is to fire every frame?

This has ended up being long winded and a bit vague I'm sure. It's pretty in depth SDLx::Controller stuff as well, but I'm fairly certain this is a common design pattern. I'm just looking for information on using this type of system effectively.

package main; use strict; use warnings; use SDL; use SDLx::App; use SDL::Event; use SDL::Events; use Data::Dumper; use player; # Set up Application Options. Let SDLx::App take care of looping my $app = SDLx::App->new (w => 450, h => 300, t => "Bob Loblaw", hw_surface => 1, double_buf => 1, dt => .2, delay => 20,); #SDL::Events::enable_key_repeat(10, 10); # Instantiate game objects my $player = player->new(); $app->add_move_handler(\&move_handler); $app->add_show_handler(\&show_handler); $app->add_move_handler(\&event_handler); #$app->add_event_handler(\&event_handler); # Handle game object reaction to events sub event_handler { my $event = shift; #if ($event->type == SDL_QUIT) { exit; } $player->move_react(@_); $player->fire_react(@_); } # Handle game object movement sub move_handler { # add empty event to queue to force processing of event handlers #SDL::Events::push_event(SDL::Event->new); $player->move_bullets(@_); $player->move(@_); } # Handle game object drawing sub show_handler { $app->draw_rect([0,0,$app->w,$app->h], [0,0,0,0]); $player->draw_bullets(@_); $player->draw(@_); $app->update; } # Finally, run the app $app->run; 1;

#! /opt/perl/bin/perl package Mob; use Moose; use SDL::Events; use Data::Dumper; has 'position' => (is => 'rw', builder => 'build_pos'); has 'velocity' => (is => 'rw', builder => 'build_vel'); has 'max_speed' => (is => 'rw', default => 50); # Delta velocity, or how much we change the speed each event has 'dv' => (is => 'rw', default => 3); sub build_pos { return {x => 200, y=> 200}; } sub build_vel { return {x => 2, y=> 2}; } sub move_react { my $self = shift; my $event = shift; SDL::Events::pump_events; my $keys = SDL::Events::get_key_state; if ($keys->[SDLK_LEFT]) { $self->{velocity}->{x} -= $self->{dv} unless ($self->{velo +city}->{x} < -$self->{max_speed}); } if ($keys->[SDLK_RIGHT]) { $self->{velocity}->{x} += $self->{dv} unless ($self->{velo +city}->{x} > $self->{max_speed}); } # Slow horizontal movement if neither modifier keys are presse +d if (!$keys->[SDLK_LEFT] && !$keys->[SDLK_RIGHT]) { if ($self->{velocity}->{x} > 0) { $self->{velocity}->{x} -= 1; } elsif ($self->{velocity}->{x} < 0) { $self->{velocity}->{x} += 1; } } if ($keys->[SDLK_UP]) { $self->{velocity}->{y} -= $self->{dv} unless ($self->{velo +city}->{y} < -$self->{max_speed}); } if ($keys->[SDLK_DOWN]) { $self->{velocity}->{y} += $self->{dv} unless ($self->{velo +city}->{y} > $self->{max_speed}); } # Slow vertical movement if neither modifier keys are pressed if (!$keys->[SDLK_UP] && !$keys->[SDLK_DOWN]) { if ($self->{velocity}->{y} > 0) { $self->{velocity}->{y} -= 1; } elsif ($self->{velocity}->{y} < 0) { $self->{velocity}->{y} += 1; } } } sub move { my ($self, $step, $app, $time) = @_; SDL::Events::pump_events; my $keys = SDL::Events::get_key_state; $self->{position}->{x} += $self->{velocity}->{x} * $step; $self->{position}->{y} += $self->{velocity}->{y} * $step; # No keys? Slow them ALL DOWN if (!$keys->[SDLK_UP] && !$keys->[SDLK_DOWN] && !$keys->[SDLK_LEFT +] && !$keys->[SDLK_RIGHT]) { if ($self->{velocity}->{x} > 0) { $self->{velocity}->{x} -= 1; } elsif ($self->{velocity}->{x} < 0) { $self->{velocity}->{x} += 1; } if ($self->{velocity}->{y} > 0) { $self->{velocity}->{y} -= 1; } elsif ($self->{velocity}->{y} < 0) { $self->{velocity}->{y} += 1; } } } sub draw { my ($self, $step, $app) = @_; $app->draw_circle_filled([$self->{position}->{x}, $self->{position +}->{y}], 10, [250,255,250,255]); } 1;
#! /opt/perl/bin/perl package player; use Moose; use List::Util qw[min max]; use SDL::Events; use Time::HiRes; use Data::Dumper; use btype; extends 'ship'; has 'score' => (is => 'rw', isa => 'Int', default => 0); has 'bullets' => (is => 'rw', isa => 'ArrayRef[bullet]'); has 'ammo' => (is => 'rw', isa => 'Int', default => 1000); has 'max_ammo' => (is => 'rw', isa => 'Int', default => 1000); sub fire_react { my $self = shift; my $event = shift; my $keys = SDL::Events::get_key_state; if ($keys->[SDLK_SPACE] && $self->{ammo} > 0) { push (@{$self->{bullets}}, btype->get_shot(1, $self->{position +})); $self->{ammo} -= 1; } elsif (!$keys->[SDLK_SPACE]) { $self->{ammo} = min($self->{ammo} + 1, $self->{max_ammo}); } } sub move_bullets { my ($self, $step, $app, $time) = @_; my @tbullets; my $length = 0; foreach my $bullet (@{$self->{bullets}}) { if ($bullet->{position}->{y} < -5 || $bullet->{position}->{y} +> $app->h + 5) { push(@tbullets, $bullet); } elsif ($bullet->{position}->{x} < -5 || $bullet->{position}->{ +x} > $app->w + 5) { push(@tbullets, $bullet); } else { unshift(@tbullets, $bullet); $length += 1; } } @{$self->{bullets}} = splice(@tbullets, 0, $length); foreach my $bullet (@{$self->{bullets}}) { $bullet->move($step); } } sub draw_bullets { my $self = shift; foreach my $bullet (@{$self->{bullets}}) { $bullet->draw(@_); } } 1;

Thanks,

Ransom

Replies are listed 'Best First'.
Re: SDLx::App event loop
by zentara (Archbishop) on Jun 11, 2012 at 16:58 UTC
    I would like the user to be able to hold down the space bar and have things "keep happening" as long as the key is held. Preferrably, I'll be able to control the time between events.

    Just poking thru the /examples/SDLx programs, the SDLx_controller_two_squares.pl seems to do that. You can control the arrow keys, but maybe your needs are more demanding? A minimal example might be helpful if you want some of us to look at it.

    From my experience with Gtk2, the callbacks are designed to allow continued processing depending on whether you return a 1 or 0, TRUE or FALSE, from the callback. It looks like SDL does the same thing. I may have it slightly wrong, as it gets a bit confusing, for normal callbacks that would be stacked, returning a 1 means "I handled it, stop other callbacks"; while in a timer, returning 1 means continue the timer and a 0 means stop the timer. So if you want to play with timers within keyboard event callbacks, you may be able to return 0 or 1, depending on the state of the key you are interested in. Like set a global variable for whether a particular key is up or down, and in the timer callback, return a 0 or 1 accordingly. I hope I made sense. :-)


    I'm not really a human, but I play one on earth.
    Old Perl Programmer Haiku ................... flash japh

      The example provided follows the SDL_key_repeat solution I explained below. It is implicit when creating the app. As an aside, my exaple is essentially exactly like theirs except I've got things split up into Moose classes and it's a bit more messy. It is hard to post code of that nature in any sort of intelligible way. It is entirely safe to assume my example is the same as the two squares example.

      Following what their example is doing, the longer I hold an arrow key, the more events get pumped, the more the square moves. It is essentially a timed release of an event, by limiting how many key_repeat signals get translated into events.

      This satisfies my need for some things. Maybe my question is more a timer question then, since I would like to have a certain event only happen at specific intervals. With the above example and solution, I could only have one specific delay for many events. Imagine I wanted to shoot bullets frontwards at my key_repeat speed, but then fire one backwards every 2*key_repeat. I don't have a way to do that with the built in key_repeat functions, and am also just as clueless about how to use a timer without blocking.

      With these things being called "callbacks" is this non-blicking by default? I've only used callbacks in a Net::SNMP non-blocking context. Are these essentially the same types of things? Is SDLx already trying to make things this easy for me?

      Thanks for your quick reply

      Ransom
        With these things being called "callbacks" is this non-blicking by default?

        Well

        my $app = SDLx::App->new(); .... $app->run;
        is an eventloop system. It schedules events as you request them. You can use SDLx without the eventloop, or make your own. See Re: Tk Game Sound demo-with SDL where I use the Tk eventloop with SDL.

        Generally in eventloop systems, timers and IO watchers can run simultaneously in a non-blocking manner, but keyboard input may need to be in a separate thread. If you are using Moose, that may be having an unexpected effect.

        It's all hard to say, when we are just speculating on unseen code, but timers can run non-blocking, it depends on what the timer is doing. Possibly you could use a key_press_down to start a timer, which every 20 forward-fire cycles, will do one backward fire. Then on the keypress_up, you cancel the timer. I probably could do it in Tk or Gtk2, but I am unfamiliar with all the SDL code. SDL timers seem to address your blocking ( timeslice ) problems.

        Generally timers can be used 2 ways, one is a one-shot timer, the other will repeat itself until you tell it to stop. This is where the returning a 1 or 0 , TRUE or FALSE, comes into play.


        I'm not really a human, but I play one on earth.
        Old Perl Programmer Haiku ................... flash japh

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://975587]
Approved by rovf
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others exploiting the Monastery: (6)
As of 2024-04-18 21:04 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found