Roku SDK Documentation : Event Loops

Table of Contents


Events

An event is a thing like an on-screen button press, a remote button press, a video that has finished playing back, etc. When an event occurs, a "message" describing the event is created.  In this documentation, we often refer to messages or events interchangably.

Event loops

A script will normally interact with events via an event loop.  At a high level, an event loop is a piece of code with this structure:

while true
    ' wait for an event
    ' process the event
end while

Message ports

Messages describing events are sent to and received from objects of type "roMessagePort". By calling ifSetMessagePort.SetMessagePort() in a particular component, you instruct that component to deliver messages to a particular roMessagePort.  Your script can then receive messages from that port to be notified of the events related to that component.

Script structure

Normally a script will have a structure like this:

  • The script creates a BrightScript Component of type "roMessagePort" .
  • The script instructs one or more other BrightScript Components to send their events to this message port, by calling SetMessagePort().
  • The script waits for an event to be delivered to the message port. The actual function to do this is ifMessagePort.WaitMessage(), but BrightScript also provides the built-in statement WAIT to make this easier.
  • When the script receives a message, it determines what type of event occurred and handles the event.
  • If a script receives an unknown type of event, it should ignore it; then just continue processing other events. Roku may occasionally add new events, and if your script is written to exit on unknown events, any future events that Roku may add will cause your application to misbehave.

Here is an example of a simple script that processes events.

port = CreateObject("roMessagePort")
screen = CreateObject("roSpringboardScreen") 
screen.SetMessagePort(port) ' instruct screen to send events to port
screen.Show()
while true
	msg = wait(0, port) ' wait for a message
	if type(msg) = "roSpringboardScreenEvent" then
		if msg.isScreenClosed() then
			return -1
		elseif msg.isButtonPressed()
			print "button pressed: ";msg.GetIndex()
		else
			' ignore other unknown or uninteresting roSpringBoardScreenEvents
		endif
	else
		' ignore other events, not type roSpringboardScreenEvent
	endif
end while

Game scripts

Real-time games which use the ifDraw2D interface typically cannot wait like this for a message in their event loop, because the screen may need to be updated with new animation frames even if no buttons are pressed or other events occur.  One way to deal with this is to use a timeout as the first parameter of the call to wait:

msg = wait(5, port)

This waits for a message, but if no message is received within 5 milliseconds, the wait returns and msg is set to 'invalid'.  When feasible, this is a simple approach.

However, an approach that usually offers more predictable performance is to use ifMessagePort.GetMessage instead of wait.  This is because GetMessage will return immediately if no message is available, while the actual amount of time before a timed wait returns can vary depending on various factors.  A game script is often structured like this.

port = CreateObject("roMessagePort")
screen = CreateObject("roScreen") 
screen.SetMessagePort(port) ' instruct screen to send events to port
while true
	msg = port.GetMessage() ' get a message, if available
	if type(msg) = "roUniversalControlEvent" then
		print "button pressed: ";msg.GetInt()
	endif
	DrawOntoScreen(screen) ' call some ifDraw2D methods to draw on screen
	screen.SwapBuffers()
end while

Note that in this case, it is the call to SwapBuffers, not GetMessage, that causes the wait in each iteration of the loop.

Timed events

Sometimes you may wish to have certain code executed periodically, or a certain amount of time after another event has occurred.  The best way to handle this is usually to incorporate the handling of the timed event into your main event loop, and use an roTimespan object as a clock to determine when it is time to perform the next action.  For example, this loop is similar to the Springboard screen event loop shown above, but it also calls the function DoSomething once every 5 seconds.

clock = CreateObject("roTimespan")
next_call = clock.TotalMilliseconds() + 5000
while true
	msg = wait(250, port) ' wait for a message
	if type(msg) = "roSpringboardScreenEvent" then
		if msg.isScreenClosed() then
			return -1
		elseif msg.isButtonPressed()
			print "button pressed: ";msg.GetIndex()
		else
			' ignore other unknown or uninteresting roSpringBoardScreenEvents
		endif
	else
		' ignore other events, not type roSpringboardScreenEvent
	endif
	if clock.TotalMilliseconds() > next_call then
		DoSomething()
		next_call = clock.TotalMilliseconds() + 5000
	end if
end while

Notice that the first parameter of wait has been changed so that it returns after 250 milliseconds even if no event has occurred.  This ensures that DoSomething gets called even in the absence of other activity on the port.

Types of messages

Different Brightscript components generate different types of messages. For example, the component "roSpringboardScreen" sends event messages of type "roSpringboardScreenEvent". In general, a Brightscript function dealing with a specific component should only react to events generated by that component.  For example, as in the example above, code that deals with events generated by an roSpringboardScreen should be inside an if statement like this:

    if type(message) = "roSpringboardScreenEvent"

When a message of the desired type is received, the code will process it.  For example, one might be looking for a "button pressed" event type. The example above shows that the way to test for this is to call the method isButtonPressed:

    if message.isButtonPressed()

An event loop needs to be aware of the possible event classes and types which it can receive and process them.


The following is the list of tests for event types. If new types are added, these tests will remain valid.

    isListItemSelected() as Boolean
isScreenClosed() as Boolean
isListFocused() as Boolean
isListSelected() as Boolean
isListItemFocused() as Boolean
isButtonPressed() as Boolean
isPlaybackPosition() as Boolean
isRemoteKeyPressed() as Boolean
isRequestSucceeded() as Boolean
isRequestFailed() as Boolean
isRequestInterrupted() as Boolean
isStatusMessage() as Boolean
isPaused() as Boolean
isResumed() as Boolean
isCleared() as Boolean
isPartialResult() as Boolean
isFullResult() as Boolean
isAdSelected() as Boolean
    isStorageDeviceAdded() as Boolean
isStorageDeviceRemoved() as Boolean
isStreamStarted() as Boolean
    isListItemInfo() as Boolean
isButtonInfo() as Boolean
  • Instant replay button. Upon pressing this button, the video will skip back 7 seconds without re-buffering. Active only in video mode.  App developers do not need to do anything to support it.
  • Info button. This button must be programmed by app developers, or it will do nothing.  Developers should provide contextually relevant information overlaid on video or other UI screens by using the predicates isListItemInfo() and isButtonInfo() listed above.
  • Back button. Closes the current screen and pops the display stack in the UI. Active only in non-video mode. In most screens developers handle this by exiting the event loop when an isScreenClosed event is received.  On some screens like roMessageDialog and roPinEntryDialog, developers must explicitly enable the Back button by calling EnableBackButton.