Skip to main content

Listeners

note

Listeners are always executed in the order that they are hooked, so the order in which they're hooked may be important.

danger

Keep in mind these and nearly everything else in this Scheduler API section only run while the Scheduler itself is running.

Overview

okay, now that that's out of the way...

Listeners... the pièce de résistance of the Scheduler API!

They allow you to tersely subscribe to events to do something when said event happens.

If we were to strip all the fancy abstraction off of a listener that's been hooked to the Scheduler, we would end up with something like this:

while (true) { // The loop is the scheduler
if (condition) { // The if is the listener
// do something
}
}

Pretty simple, right? Well, that's exactly what a listener is. It just checks for a condition, and does something if it's true. Except it's more versatile than just that.

For example, let's say we want to alert our driver when the lift is too high, and slow the robot down...

// Creates the listener by passing in the condition to listen to
new Listener(() -> lift.getHeight() > 1000)

// Runs only once when the condition becomes true
.onRise(() -> telemetry.addLine("Lift is too high, we're slowing the bot down!"))

// Constantly called while the condition is true
.whileHigh(() -> drive.setPowerMultiplier(.5));

Scheduler.launch(this) // Listener only works when the Scheduler is running, needs to be in a LinearOpMode
tip

onRise, onFall, whileHigh, and whileLow all return the same Listener object, so you can chain them together as seen above, instead of explicitly stating the object name every time.

Every time the Scheduler loops, it'll evaluate the passed in lambda, and fire all the events subscribed to the corresponding state.

You might have noticed the onRise and onFall, and may be a little confused right now... Well here's a signal diagram to help:

Listener Diagram

You can see four distinct words:

  • Low: The condition is false
  • Rise: The condition went from false to true
  • High: The condition is true
  • Fall: The condition went from true to false

Similarly, the Listener class has four subscription methods:

  • whileHigh - (or whileTrue)
  • whileLow -- (or whileFalse)
  • onRise ---- (or onJustTrue)
  • onFall ---- (or onJustFalse)

You're (probably) a smart (enough) cookie; you can probably figure out what they mean. If not, it's fine! Just look at the method documentation for more detailed examples!

The concept of hooking🪝

This isn't super important to know, so you could skip it if you really wanted—I just wanted to explain it real quick as it comes up a bit.

When you first create a Listener, nothing happens. It's not evaluated and updated every loop by the Scheduler; it just sits there and looks pretty.

It's only when you subscribe to the listener (or run Listener.hook) that it starts being evaluated and updated (i.e. it gets hooked).

Listener listener = new Listener(() -> true); // Not hooked (not in Scheduler's list of listeners)

listener.whileHigh(() -> ...); // Now it's hooked automagically

listener.hook(); // Also hooks it w/out subscribing to any events (No diff made here b/c it's already hooked)

listener.whileHigh(() -> ...); // It's already hooked, so nothing changes
tip

This can actually be used to your advantage with listener.hook by pre-hooking a listener before some other to run the former listener first.

I mean, that is a very niche situation, so do with it what you will Ig lol.

Listener methods

Construction

Constructs a new Listener without hooking it (see: hooking).

Params
  • condition: () -> Boolean - The condition to listen to
    long startTime = System.currentTimeMillis();

    // Creates a new Listener that checks if 5 seconds have passed
    Listener timer = new Listener(() -> System.currentTimeMillis() - startTime > 5000);

    // Creates a listener which always evaluates to true, so it'll always be high
    Listener alwaysTrue = new Listener(() -> true);

    listener.onRise()

    Runs the given callback whenever the condition goes from false to true (i.e. it's on the rising edge)

    Equivalent to:
    boolean wasHigh = false;

    while (true) {
    boolean isHigh = condition();

    if (isHigh && !wasHigh) {
    // Do thing
    }

    wasHigh = isHigh;
    }
    Params
  • callback: Runnable - The callback to run on the rising edge
  • Returns
  • Listener - The Listener itself to allow for method chaining
    Listener gamepadA = new Listener(() -> gamepad1.a);

    // Runs only once when the gamepad1.a goes from false to true
    // i.e. when gamepad1.a was JUST pressed
    gamepadA.onRise(() -> telemetry.addLine("A was pressed!"));

    listener.onFall()

    Runs the given callback whenever the condition goes from true to false (i.e. it's on the falling edge)

    Equivalent to:
    boolean wasHigh = false;

    while (true) {
    boolean isHigh = condition();

    if (!isHigh && wasHigh) {
    // Do thing
    }

    wasHigh = isHigh;
    }
    Params
  • callback: Runnable - The callback to run on the falling edge
  • Returns
  • Listener - The Listener itself to allow for method chaining
    Listener gamepadA = new Listener(() -> gamepad1.a);

    // Runs only once when the gamepad1.a goes from true to false
    // i.e. when the A button was JUST released
    gamepadA.onFall(() -> telemetry.addLine("A button released!"))

    listener.whileHigh()

    Runs the given callback while the condition is true.

    Equivalent to:
    while (true) {
    if (condition()) {
    // Do thing
    }
    }
    Params
  • callback: Runnable - The callback to run while the condition is true
  • Returns
  • Listener - The Listener itself to allow for method chaining
    Listener gamepadA = new Listener(() -> gamepad1.a);

    // Runs every loop while the A button is pressed
    gamepadA.whileHigh(() -> telemetry.addLine("A button is being held!"));

    listener.whileLow()

    Runs the given callback while the condition is false.

    Equivalent to:
    while (true) {
    if (!condition()) {
    // Do thing
    }
    }
    Params
  • callback: Runnable - The callback to run while the condition is false
  • Returns
  • Listener - The Listener itself to allow for method chaining
    Listener gamepadA = new Listener(() -> gamepad1.a);

    // Runs every loop while the A button is not pressed
    gamepadA.whileLow(() -> telemetry.addLine("A button is not being held!"));

    listener.hook()

    Hooks the Listener to the Scheduler, does nothing if it's already hooked.

    Only reason I'd imagine you'd want to do this is if you did Scheduler.reset and want to hook the Listener back up. I don't know why you'd reset the Scheduler in the first place, but you go, girl!!!

    Actually, I guess you can use it for hooking up a listener before another certain listener in advance, to keep things running in the right order, but that's a very nice case...

    Anyway, here's an example:

    Listener listener = new Listener(() -> ...);

    listener.hook(); // Hooks the listener to the Scheduler

    Scheduler.reset(); // Resets the Scheduler

    listener.hook(); // Hooks the listener back up

    listener.hook(); // Does nothing since it's already hooked

    listener.destroy()

    Destroys the Listener, removing it from the Scheduler, and clearing its actions. Leaves the condition alone, though.

    Again, not entirely sure why you'd do this, but if you have a good enough reason to do it, you can probably figure out how to use it yourself. Knock yourself out!!!

    Listener method aliases

    Supported aliases for the subscription methods are:

    • onJustTrue(callback: Runnable) = onRise(callback)
    • onJustFalse(callback: Runnable) = onFall(callback)
    • whileTrue(callback: Runnable) = whileHigh(callback)
    • whileFalse(callback: Runnable) = whileLow(callback)

    Static Listener methods

    Listener.always()

    A static instance of a Listener that's always true, useful for when you want to run something every loop, but don't want to (or can't) use the beforeEach or afterEach blocks for whatever reason.

    Note that you can only pass in a method to run every loop; you can't use onRise, onFall or whileLow with this Listener; It implicitly calls whileHigh

    Keep in mind that this is only hooked the first time it's used (unless you explicitly hook it, of course)

    Params
  • callback: Runnable - The callback to run every loop
  • Returns
  • Listener - The Listener itself to allow for method chaining
    Listener.always(() -> telemetry.addLine("This will run every loop!"));

    Listener builders

    For convenience, you can combine multiple listeners/conditions into one using a variety of logical operators.

    The syntax is as follows: listener1.whateveroperator(listener2) or listener.whateveroperator(() -> someBoolean)

    Listener gamepadA = new Listener(() -> gamepad1.a);
    Listener gamepadB = new Listener(() -> gamepad1.b);

    // True if both A and B are pressed
    Listener gamepadA_and_B = gamepadA.and(gamepadB);

    // True if either A or X are pressed
    Listener gamepadA_or_X = gamepadA.or(() -> gamepad1.x);

    // True if A is pressed and B is not pressed
    Listener gamepadA_and_notB = gamepadA.and(gamepadB.not());

    // True if A is pressed and either B or X are pressed
    Listener gamepadA_or_B_or_X = gamepadA.or(gamepadB).or(() -> gamepad1.x);

    // True if A is pressed and B is not pressed, or if B is pressed and A is not pressed
    Listener gamepadA_xor_B = gamepadA.xor(gamepadB);
    Important

    If you're using Kotlin, please read the note in the code above about using parentheses to make sure stuff works as expected.

    // works as expected
    (gamepadA + gamepadB).onRise { ... }

    // also fine
    (gamepadA / { !gamepad1.x }).onRise { ... }

    // performs the `onRise` on `gamepadB` only, THEN performs the `and` on the gamepadA with gamepadB
    // aka it's probably not what you intend lol
    gamepadA + gamepadB.onRise { ... }

    Currently supported functions/operators are:

    • listener.and( otherListener )
    • listener.or( otherListener )
    • listener.xor( otherListener )
    • listener.nand( otherListener )
    • listener.nor( otherListener )
    • listener.xnor( otherListener )
    • listener.not( otherListener )

    and

    • listener.and( () -> someBoolean )
    • listener.or( () -> someBoolean )
    • listener.xor( () -> someBoolean )
    • listener.nand( () -> someBoolean )
    • listener.nor( () -> someBoolean )
    • listener.xnor( () -> someBoolean )
    • listener.not( () -> someBoolean )