PencilCoder

Teacher's Guide: Event Polling

Overview

This lesson introduced event polling, a powerful coding idiom for working with events. The lesson's coding activities lead students to explore algorithms, using polling, to move sprites fluidly in response to end-user input, and consider advantages and disadvantages to using polling rather than using events directly.

More about the lesson

Event polling is an example of a coding idiom, or design pattern, which is a general, reusable solution to a commonly occuring problem in software design. In this case, the idiom solves the problem of easily referencing up-to-date information about user-input events when working with timing events. For example, in Pencil Code, polling facilitates referencing information about user-input events from within the callback bound to a timing interval created with forever.

pen black, 1
forever ->
  moveto lastmousemove, 2

In the case of mouse-related events, the variables provide direct access to the corresponding mouse event object. In addition to the four variables noted in the lesson, Pencil Code also provides lastmouse, which references the mouse event object of the most recent mouse event (regardless of type).

In the case of keyboard-related events, Pencil Code provides the pressed function, which can be called with a single argument or no argument. As the lesson illustrates, when passed a single argument that represents a keyboard key (either upper- or lower-case, as the function is case insensitive), pressed returns a boolean value noting whether that key is currently pressed, i.e., that it has been pressed down and not yet released. If called with no arguments, e.g., pressed(), the function returns an array of currently pressed keys.

The mechanics of polling is conceptually simple. Pencil Code maintains a set of variables to record information about events. Pencil Code keeps these variables up-to-date using timing intervals that fire every few milliseconds. The associated callback to this timing keeps these Pencil Code vairables every time their status changes. These built-in polling-related references are global variables, meaning we can reference them anywhere in our scripts.

Use of event polling has several advantages to working with the corresponding events directly. For starters, it is often easier to set up and maintain code with event polling. As an example, consider the coding snippets below, which effectively yield the same result. The explicit use of events requires the additional work of creating a global variable, coding a callback to update that variable, and binding the callback to an event listener. This is a trivial example, with only one listener; the difference becomes more dramatic the more events one needs to reference. Keep in mind, too, that the explicit use of event listeners not only requires more code, but, given the greater complexity of the program, opens up additional opportunities for coding bugs.

Despite what its name may suggest, the key events-related pressed funtion is not associated directly with the keypress event. Rather, Pencil Code maintains an array of the names of keys that have been pressed down (firing a keydown event) but not yet released (firing a keyup event). Calls to pressed either return this array, or, when referencing specific letters, a boolean value reflecting the "pressed down" status of that particular key.

As an aside: recall that the keydown and keyup events are not case sensitive, and that they also return values for non-character keys such as shift and alt. It is because pressed is defined using these events that it is not case sensitive. Thus, for example, pressed("A") and pressed("a") are equivalent, and strings in the array from calls to pressed() are entirely lower case.

The inner workings of pressed are explored in greater detail in the Beyond The Lesson section, below. But from the foregoing discussion, the ease-of-use benefits of polling with pressed should be fairly obvious: to match the result of a single call to pressed would require creating an array with global scope and setting up two event handlers that in tandem work to keep this array up-to-date.

Admittedly, in describing the advantages of polling over direct use of events, the foregoing discussion assumes that the code for polling itself was already written, as it is for us in the Pencil Code environment. More generally, however, we would have to write the code ourselves. But this is essentially a one-time task; once written, we can simply copy/paste this source code into programs when needed, or, as students will learn in a subsequent lesson, we can save it in a custom library and add simply import that into our current script.

Another benefit of polling is that it facilitates writing code that responds more fluidly to key events, a feature explored in the lesson's TurtleRace activity and also illustrated in this script:

The advantage pertains to situations when the key is pressed and held down. Efforts to replicate this soley using keydown events will fall short, for two reasons. The first owes to the fact key events re-fire with a lag, typically about every 400 ms after an initial keyboard event and 80ms after subsequent refirings. These timing delays corresponds to fps rates of of 1÷0.400 = 2.5 and 1÷0.800 = 12.5 times per second, respectively—much slower than the forever function's default rate of 30 times per second. The movements generate using keydown events thus look a bit uneven and halting. The second difficulty of relying on keydown event is that key events fire for any key. Once a second key is pressed down, refirings on previously pressed and held keys will cease.

To use events to create movements as smooth as those created with polling, you would have to use both keydown and keyup events, and also keep track of keys that have been pressed and not yet subsequently released. In other words, we'd basically have to do exactly what polling is set up to do.

Notes to activities

In KeyboardMover, experiment with different timer delays, turn angles, forward distances, and other aspects of your movement algorithm too. For example, polling facilitates moving based on two simultaneous key events (such as "up" and "right")...

In MouseMover, explore using the second argument of the moveto method. As an added challenge, limit the area the sprite can move in to a box that fills about half the area of the screen. Make sure the algorithm keeps the sprite in that area. For example, in MouseMoverX, the algorithm actually allows the sprite to move beyond the boundary. It happens so fast, you don't see it, but with pen down the evidence is clear... There are subtle differences to algorithms that can take the script to a higher level.. for example, consider the tweak that makes this better than this.

CopsAndRobbers can be done with either mouse or keyboard events.

The TurtleRace activity is aimed at helping head off confusion between the conceptual difference between using events directly and using events indirectly with polling. Encourage students to explore the model. For example, when and/or for which racer does pressing and holding work better than repeatedly pressing the key? The "event" racer can easily be the winner when the "polling" racer's fps argument is low (e.g., fps of 10 corresponds to timing event interval of 100ms, which is slower than the average auto-repeat rate, approximately 80). But the "event" racer can win even if the fps is higher, even at 30 fps—if one applies fleetingly-short taps and also gets a little lucky, so that the firing of the keydown event falls between polling events firings, so that the "event" racer gains on the "polling" racer.

Additional activities

  • Make a copy of KeyboardMover and turn it into a game of TurtleHunter: Add a number of includes randomly moving sprites in the contained area, which the sprite controlled with the keyboard should capture. Keep track of how long it takes to gobble up all your prey; stop all timers and report your results when the game is over.
  • TurtleSurvivor: make a variation of the TurtleHunter program, in which the turtle is no longer predator, but prey! Code the autonomous sprites to try to catch the turtle controlled with the arrow keys. Game over when they finally succeed you. Don’t make it too easy to win!
  • Code a DuellingTurtles program which lets you control two turtles using two sets of keys. For example, a-s-d-f could control left-up-down-right for one turtle, and h-j-k-l could control it for the other. Give each turtle the ability to blast the other turtle with a fireball.
  • HerdingCats: Code a script that allows you to use a mouse to move a dog sprite about the screen, whose job it is to herd a number of cat sprites into an enclosed area. When the dog is within close distance to any cat, that cat should move in the direction away from the dog. Otherwise, the cat should move randomly.

  • ChromeNoInternet: The Chrome web browser provides a game you can play when you don’t have an internet connection. Create a similar game of your own. Use a sprite for your equivalent of the dinosaur a sprite, but make your equivalents of the cacti images that you redraw with each loop of forever.
  • The classic video game Galaga lets you move a spaceship about the screen, while continually keeping the ship facing "north". Code a script that manuevers a sprite about the screen in this fashion, based on keyboard input.
  • In the classic video game Asteroids, the user controls a spaceship that can point in any direction and move anywhere about the screen. It can even leave one side of the screen—it then reappears on the opposite side, but continuing to move in the same direction. Code a script that manuevers a sprite about the screen in this fashion, based on keyboard input. The logic to the motions is challenging. Like many math problems, the key is to break it down into simple cases.
  • PauseOnPageExit: recall that the mouseenter event fires when the mouse pointer enters (comes over) the target element, and mouseleave fires when mouse pointer leaves (stops being over) the target element. When the target is the global document object, e.g., $(document).mouseleave, these events can be used to track if the mouse has left the browser window. Use this fact to improve a program that instructs the sprite to follow the mouse pointer: when the pointer is not over the browser window, the program should pause; when the pointer reenters the window, the program should continue as before.

Beyond the lesson

Historical context

In computing, polling originally referred to the frequent and repeated checking of the status of some feature of a currently-running system. For example, the operating system might regularly check the status of a measuring device (e.g., thermometer), certain input/output interfaces (e.g., a joystick), or of a specific, currently-running program. Note that this definition involves active checking, rather than relying on events for changes to effectively announce themselves. Thus, in the truest sense, polling is used for devices and other system resources which do not generate hardware interrupts and/or signal any events until you call them.

The term "polling" in an event-driven environment such as JavaScript can be a bit vague. You may see the term used to refer to the tools used to provide a snapshot of the status of events at any time. In the Pencil Code context, then, polling refers to the global mouse-event variables such as lastmousemove and the key-event function pressed. In line with the more historic definition, however, this lesson defines "polling" as the design pattern of applying these tools in a "frequent and repeated" way. Thus, polling is applying the Pencil Code-provided polling tools within a repeated timer, such as is set up using forever.

User-defined event polling

Pencil Code sets up event polling variables for us, greatly simplifying the use of this powerful coding idiom. Pencil Code adds in some advanced features, but the basic logic is straightforward, which students at this level could easily work through themselves, even for the relatively more complicated application for tracking key presses.

Consider the following simplified alternative to the pressed function (provided in this script) which only works for direction keys, such as arrow keys ("left", "up", etc.) or the popular combination of "a", "w", "s", and "d". We need four variables to track the pressed status of each option:

To complete the polling setup, establish two simple key event listeners, one for keydown and one for keyup events, with event handlers that work together to update these variables:

All that's left is to write whetever script you had in mind, such as this simple basic Galaga-like sprite motion:

Polling versus events

Polling provides an effective way to interact with events in some situations, but it is not the optimal or even an appropriate design choice for all tasks. For example, polling is a poor choice for repeated capture of a single key-related event. This is illustrated in this script, which attempts to toggle pen up/down status based on presses of the space key.

For a richer, and more authentic example of when polling is not an appropriate idiom, consider the Snake activity described in the notes to the previous lesson. Individual turns were decided based on keydown events; in an ideal solution to that activity, repeat firings of keydown events should be prevented (i.e., if the player wants to turn twice to the left, they should press the left arrow twice). Polling fails in that case because of the ambiguity that arises if the user pressed more than one button. The exact timing of the events would no longer dictate the order of the events. Instead, we would only know that as of a specific firing of the timer, one or the other or both keys were currently pressed. In the case that both keys were currently pressed, we would have to arbitrarily decide which direction to choose. For a very fast keystroke, we could have missed a key event between timing events. With events, we would be assured to catch every keydown event and also to receive them in unambiguous sequential order (because two events can't happen at exactly the same time).

Queue-based animation

In programs using queue-based animation logic, references to event polling data, e.g., via object references such as lastmousemove or calls to pressed, will fail to provide information reflecting the current state of animation. This is analogous to calls to functions such as touches, getxy, and inside, because, as was explained in lessons Array Destructuring! and Conditional Logic!, these functions do not get executed "at the right time."

To ensure the references are evaluated at the right point in the animation, use the same tools as were used to get the desired results from functions such as getxy: put references to polling resources within the callback to plan or done or following a call to await done defer().

While there certainly are exceptions, as a general rule, especially for beginners, it is best to work with frame-based animation when using polling, i.e., by setting speed Infinity as the first line of each script.

What can go wrong

Uninitialized polling values

Event data is not (indeed, cannot) be updated until a program starts running. In the case of mouse-related events, until timer events for polling are set up and active, Pencil Code initializes the event variables for mouse events to an object similar to this:

{
  type: undefined,
  timeStamp: 1659803243180,
  jQuery214026756673904831474: true
}

In the unlikely event that a student writes code that trips up on this feature, a simple workaround is to test if the mouse event object's type property is defined. This can be done using a comparison operator,

if lastmouse.type != undefined
        #do something...

or more simply using the CoffeeScript existential operator (?), e.g.,

if lastmouse.type?
  #do something...

Timing mismatches

As noted above, polling can occasionally fail to produce the desired results. The issue is one of timing. One type of problem that can arise owes to the fact that polling data are maintained by a timing interval that fires very frequently, yet not truly continuously. As a result, when a keydown event is quickly followed by a keyup event, the event might not be captured by the callback fired by the timing event, because the entire keystroke (both down and up) occurred between firing. This is more likley to happen in timing events with lower fps values

Another timing-related bug is more likely to occur when referencing polling data from callbacks associated with timing events with very short delays (i.e., higher fps). As illustrated in this script, the same pressed event is likely to be referenced multiple times. In this illustration, the result is that conditional statement if pressed 'space' will be true in several firings of the callback, ultimately leaving the resulting choice of pd or pu to be effectively random.