76public class Trigger implements BooleanSupplier {
77 private final BooleanSupplier m_condition;
83 private Signal m_previousSignal;
86 private Signal m_cachedSignal;
88 private final Map<BindingType, List<Binding>> m_bindings =
new EnumMap<>(
BindingType.class);
89 private final Runnable m_eventLoopCallback = this::poll;
111 requireNonNullParam(scheduler,
"scheduler",
"Trigger"),
113 requireNonNullParam(condition,
"condition",
"Trigger"));
135 @SuppressWarnings(
"this-escape")
137 m_scheduler = requireNonNullParam(scheduler,
"scheduler",
"Trigger");
138 m_loop = requireNonNullParam(loop,
"loop",
"Trigger");
139 m_condition = requireNonNullParam(condition,
"condition",
"Trigger");
154 m_scheduler.addBoundTrigger(
this);
155 m_loop.bindWeak(m_eventLoopCallback);
165 requireNonNullParam(command,
"command",
"onTrue");
181 requireNonNullParam(command,
"command",
"ifTrue");
193 requireNonNullParam(command,
"command",
"onFalse");
209 requireNonNullParam(command,
"command",
"whileTrue");
225 requireNonNullParam(command,
"command",
"whileFalse");
242 requireNonNullParam(command,
"command",
"retryWhileTrue");
259 requireNonNullParam(command,
"command",
"retryWhileFalse");
271 requireNonNullParam(command,
"command",
"toggleOnTrue");
283 requireNonNullParam(command,
"command",
"toggleOnFalse");
354 var debouncer =
new Debouncer(duration.in(Seconds), type);
370 m_scheduler, m_loop, () -> m_cachedSignal ==
Signal.
HIGH && m_previousSignal ==
Signal.
LOW);
385 m_scheduler, m_loop, () -> m_cachedSignal ==
Signal.
LOW && m_previousSignal ==
Signal.
HIGH);
399 requireNonNullParam(duration,
"duration",
"multiTap");
402 if (duration.baseUnitMagnitude() <= 0) {
404 return new Trigger(m_scheduler, m_loop, () ->
false);
405 }
else if (pressCount <= 0) {
407 return new Trigger(m_scheduler, m_loop, () ->
true);
410 final double durationSeconds = duration.in(Seconds);
415 new BooleanSupplier() {
420 private final Deque<Double> m_timestamps =
new ArrayDeque<>();
421 private boolean m_risingEdgeOccurred =
false;
426 if (!m_risingEdgeOccurred) {
428 m_risingEdgeOccurred =
true;
431 m_risingEdgeOccurred =
false;
435 while (!m_timestamps.isEmpty()
436 && currentTime - m_timestamps.peekFirst() > durationSeconds + 1e-9) {
437 m_timestamps.removeFirst();
440 return m_timestamps.size() >= pressCount;
445 private void poll() {
449 clearStaleBindings();
451 m_previousSignal = m_cachedSignal;
452 m_cachedSignal = readSignal();
455 if (m_cachedSignal == Signal.HIGH) {
458 }
else if (m_cachedSignal == Signal.LOW) {
459 scheduleBindings(BindingType.CONTINUOUSLY_SCHEDULE_WHILE_LOW);
462 if (m_cachedSignal == m_previousSignal) {
467 if (m_cachedSignal == Signal.HIGH) {
469 scheduleBindings(BindingType.SCHEDULE_ON_RISING_EDGE);
470 scheduleBindings(BindingType.RUN_WHILE_HIGH);
471 cancelBindings(BindingType.RUN_WHILE_LOW);
472 cancelBindings(BindingType.CONTINUOUSLY_SCHEDULE_WHILE_LOW);
473 toggleBindings(BindingType.TOGGLE_ON_RISING_EDGE);
478 scheduleBindings(BindingType.SCHEDULE_ON_FALLING_EDGE);
479 scheduleBindings(BindingType.RUN_WHILE_LOW);
480 cancelBindings(BindingType.RUN_WHILE_HIGH);
481 cancelBindings(BindingType.CONTINUOUSLY_SCHEDULE_WHILE_HIGH);
482 toggleBindings(BindingType.TOGGLE_ON_FALLING_EDGE);
486 private Signal readSignal() {
487 if (m_condition.getAsBoolean()) {
495 private void clearStaleBindings() {
497 (_bindingType, bindings) -> {
498 for (var iterator = bindings.iterator(); iterator.hasNext(); ) {
499 var binding = iterator.next();
500 if (binding.scope().active()) {
507 m_scheduler.cancel(binding.command());
517 private void scheduleBindings(BindingType bindingType) {
518 m_bindings.getOrDefault(bindingType, List.of()).forEach(m_scheduler::schedule);
526 private void cancelBindings(BindingType bindingType) {
528 .getOrDefault(bindingType, List.of())
529 .forEach(binding -> m_scheduler.cancel(binding.command()));
538 private void toggleBindings(BindingType bindingType) {
540 .getOrDefault(bindingType, List.of())
543 var command = binding.command();
544 if (m_scheduler.isScheduledOrRunning(command)) {
545 m_scheduler.cancel(command);
547 m_scheduler.schedule(binding);
558 Signal getCachedSignal() {
559 return m_cachedSignal;
568 Signal getPreviousSignal() {
569 return m_previousSignal;
573 boolean isScopeActive() {
574 return m_lifetimeScope.active();
578 void addBinding(BindingScope scope, BindingType bindingType, Command command) {
582 .computeIfAbsent(bindingType, _k ->
new ArrayList<>())
583 .add(
new Binding(scope, bindingType, command,
new Throwable()));
586 private void addBinding(BindingType bindingType, Command command) {
587 var scope = BindingScope.createNarrowestScope(m_scheduler);
588 addBinding(scope, bindingType, command);