vexide_core/competition.rs
1//! Competition control and state.
2//!
3//! This module provides functionality for interacting with competition control in VEXos, allowing
4//! you to read competition state and respond to changes in competition modes. This is useful for
5//! situations where the robot's behavior must adapt based on the state of the competition, such as
6//! transitioning between autonomous and driver-controlled modes.
7//!
8//! # The [`Compete`] Trait
9//!
10//! The most important item in this module is the [`Compete`] trait, which serves as the foundation
11//! for defining competition-specific behavior in vexide programs. This trait allows you to declare
12//! different functions on a robot struct to be executed when competition state changes. By
13//! implementing [`Compete`], your robot can respond to changes in the various phases of a
14//! competition, such as autonomous operation, driver control, or downtime between modes.
15//!
16//! ```no_run
17//! use vexide::prelude::*;
18//!
19//! struct MyRobot {}
20//!
21//! impl Compete for MyRobot {
22//! async fn autonomous(&mut self) {
23//! println!("Running in autonomous mode!");
24//! }
25//!
26//! async fn driver(&mut self) {
27//! println!("Running in driver mode!");
28//! }
29//! }
30//!
31//! #[vexide::main]
32//! async fn main(_peripherals: Peripherals) {
33//! let my_robot = MyRobot {};
34//! my_robot.compete().await;
35//! }
36//! ```
37//!
38//! By awaiting the [`compete()`] function on our robot, we are handing over execution to vexide's
39//! [`CompetitionRuntime`], which will run a different function on the [`Compete`] trait depending
40//! on what is happening in the match.
41//!
42//! [`compete()`]: CompeteExt::compete
43//!
44//! # Reading Competition State
45//!
46//! In addition to providing hooks into different competition modes, this module also provides
47//! functions for reading information about the competition environment, such as the current match
48//! mode, match control hardware, and whether the robot is enabled or disabled. This is provided by
49//! the [`is_connected`], [`system`], [`mode`], and [`status`] functions.
50
51use alloc::boxed::Box;
52use core::{
53 cell::UnsafeCell,
54 future::{Future, IntoFuture},
55 marker::{PhantomData, PhantomPinned},
56 ops::ControlFlow,
57 pin::Pin,
58 task::{self, Poll},
59};
60
61use bitflags::bitflags;
62use futures_core::Stream;
63use pin_project::pin_project;
64use vex_sdk::vexCompetitionStatus;
65
66bitflags! {
67 /// The raw status bits returned by [`vex_sdk::vexCompetitionStatus`].
68 ///
69 /// These flags contain all the data made available to user code by VEXos about the state of the match.
70 #[derive(Debug, Clone, Copy, Eq, PartialEq)]
71 pub struct CompetitionStatus: u32 {
72 /// Robot is disabled by field control.
73 const DISABLED = 1 << 0;
74
75 /// Robot is in autonomous mode.
76 const AUTONOMOUS = 1 << 1;
77
78 /// Robot is connected to competition control (either competition switch or field control).
79 const CONNECTED = 1 << 2;
80
81 /// Robot is connected to field control (NOT competition switch)
82 const SYSTEM = 1 << 3;
83 }
84}
85
86/// A match mode in the competition lifecycle.
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum CompetitionMode {
89 /// The Disabled competition mode.
90 ///
91 /// When in disabled mode, voltage commands to motors are disabled. Motors are forcibly locked
92 /// to the "coast" brake mode and cannot be moved.
93 ///
94 /// Robots may be placed into disabled mode at any point in the competition after connecting,
95 /// but are typically disabled before the autonomous period, between autonomous and opcontrol
96 /// periods, and following the opcontrol period of a match.
97 Disabled,
98
99 /// The Autonomous competition mode.
100 ///
101 /// When in autonomous mode, all motors and sensors may be accessed, however user input from
102 /// controller buttons and joysticks is not available to be read.
103 ///
104 /// Robots may be placed into autonomous mode at any point in the competition after connecting,
105 /// but are typically placed into this mode at the start of a match.
106 Autonomous,
107
108 /// The Driver Control (i.e. opcontrol) competition mode.
109 ///
110 /// When in opcontrol mode, all device access is available including access to controller
111 /// joystick values for reading user-input from drive team members.
112 ///
113 /// Robots may be placed into opcontrol mode at any point in the competition after connecting,
114 /// but are typically placed into this mode following the autonomous period.
115 Driver,
116}
117
118/// A type of system used to control match state.
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub enum CompetitionSystem {
121 /// Competition state is controlled by a VEX Field Controller (either [legacy] or [smart] field
122 /// control).
123 ///
124 /// [legacy]: https://www.vexrobotics.com/275-1401.html
125 /// [smart]: https://kb.vex.com/hc/en-us/articles/9121731684756-VEX-Field-Control-User-Manual
126 FieldControl,
127
128 /// Competition state is controlled by a [VEXnet competition switch].
129 ///
130 /// [VEXnet competition switch]: https://www.vexrobotics.com/276-2335.html
131 CompetitionSwitch,
132}
133
134impl CompetitionStatus {
135 /// Checks if the robot is connected to a competition control system.
136 ///
137 /// This is equivalent to the standalone [`is_connected`] function in this module.
138 ///
139 /// # Example
140 ///
141 /// ```
142 /// let status = vexide::competition::status();
143 ///
144 /// if status.is_connected() {
145 /// println!("Connected to competition control");
146 /// }
147 /// ```
148 #[must_use]
149 pub const fn is_connected(&self) -> bool {
150 self.contains(CompetitionStatus::CONNECTED)
151 }
152
153 /// Returns the current competition mode, or phase from these status flags.
154 ///
155 /// This is equivalent to the standalone [`mode`] function in this module.
156 ///
157 /// # Example
158 ///
159 /// ```
160 /// use vexide::competition::{self, CompetitionMode};
161 ///
162 /// let status = competition::status();
163 ///
164 /// match status.mode() {
165 /// CompetitionMode::Driver => println!("Driver control"),
166 /// CompetitionMode::Autonomous => println!("Auton"),
167 /// CompetitionMode::Disabled => println!("DIsabled"),
168 /// }
169 /// ```
170 #[must_use]
171 pub const fn mode(&self) -> CompetitionMode {
172 if self.contains(Self::DISABLED) {
173 CompetitionMode::Disabled
174 } else if self.contains(Self::AUTONOMOUS) {
175 CompetitionMode::Autonomous
176 } else {
177 CompetitionMode::Driver
178 }
179 }
180
181 /// Returns the type of system currently controlling the robot's competition state, or [`None`]
182 /// if the robot is not tethered to a competition controller.
183 ///
184 /// This is equivalent to the standalone [`system`] function in this module.
185 ///
186 /// # Example
187 ///
188 /// ```
189 /// use vexide::competition::{self, CompetitionSystem};
190 ///
191 /// let status = competition::status();
192 ///
193 /// match status.system() {
194 /// None => println!("Not connected to a match controller"),
195 /// Some(CompetitionSystem::FieldControl) => println!("Connected to field controller"),
196 /// Some(CompetitionSystem::CompetitionSwitch) => println!("Connected to competition switch"),
197 /// }
198 /// ```
199 #[must_use]
200 pub const fn system(&self) -> Option<CompetitionSystem> {
201 if self.contains(CompetitionStatus::CONNECTED) {
202 if self.contains(Self::SYSTEM) {
203 Some(CompetitionSystem::FieldControl)
204 } else {
205 Some(CompetitionSystem::CompetitionSwitch)
206 }
207 } else {
208 None
209 }
210 }
211}
212
213/// Returns all competition status flags reported by VEXos.
214///
215/// See [`CompetitionStatus`] for usage and examples.
216#[must_use]
217pub fn status() -> CompetitionStatus {
218 CompetitionStatus::from_bits_retain(unsafe { vexCompetitionStatus() })
219}
220
221/// Checks if the robot is connected to a competition control system.
222///
223/// # Example
224///
225/// ```
226/// if vexide::competition::is_connected() {
227/// println!("Connected to competition control");
228/// }
229/// ```
230#[must_use]
231pub fn is_connected() -> bool {
232 status().is_connected()
233}
234
235/// Returns the type of system currently controlling the robot's competition state, or [`None`] if
236/// the robot is not tethered to a competition controller.
237///
238/// # Example
239///
240/// ```
241/// use vexide::competition::{self, CompetitionSystem};
242///
243/// match competition::system() {
244/// None => println!("Not connected to a match controller"),
245/// Some(CompetitionSystem::FieldControl) => println!("Connected to field controller"),
246/// Some(CompetitionSystem::CompetitionSwitch) => println!("Connected to competition switch"),
247/// }
248/// ```
249#[must_use]
250pub fn system() -> Option<CompetitionSystem> {
251 status().system()
252}
253
254/// Returns the current competition mode, or phase.
255///
256/// ```
257/// use vexide::competition::{self, CompetitionMode};
258///
259/// let status = competition::status();
260///
261/// match vexide::competition::mode() {
262/// CompetitionMode::Driver => println!("Driver control"),
263/// CompetitionMode::Autonomous => println!("Auton"),
264/// CompetitionMode::Disabled => println!("DIsabled"),
265/// }
266/// ```
267#[must_use]
268pub fn mode() -> CompetitionMode {
269 status().mode()
270}
271
272/// A stream of updates to the competition status.
273///
274/// See [`updates`] for more information.
275pub struct CompetitionUpdates {
276 last_status: Option<CompetitionStatus>,
277}
278
279impl Stream for CompetitionUpdates {
280 type Item = CompetitionStatus;
281
282 fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
283 let current = status();
284
285 // TODO: This should probably be done on a timer in the reactor.
286 cx.waker().wake_by_ref();
287
288 if self.last_status == Some(current) {
289 Poll::Pending
290 } else {
291 self.get_mut().last_status = Some(current);
292 Poll::Ready(Some(current))
293 }
294 }
295}
296
297impl CompetitionUpdates {
298 /// Returns the last status update.
299 ///
300 /// This is slightly more efficient than calling [`status`] as it does not require another poll,
301 /// however, it can be out of date if the stream has not been polled recently.
302 pub fn last(&self) -> CompetitionStatus {
303 self.last_status.unwrap_or_else(status)
304 }
305}
306
307/// Returns an async stream of updates to the competition status.
308///
309/// Yields the current status when first polled, and thereafter whenever the status changes.
310#[must_use]
311pub const fn updates() -> CompetitionUpdates {
312 CompetitionUpdates { last_status: None }
313}
314
315/// A future which delegates to different futures depending on the current competition mode. i.e., a
316/// tiny async runtime specifically for writing competition programs.
317///
318/// This runtime provides the internal implementation behind the [`Compete`] trait and the
319/// [`CompetitionBuilder`] struct.
320#[pin_project]
321pub struct CompetitionRuntime<
322 Shared: 'static,
323 Return,
324 MkConnected,
325 MkDisconnected,
326 MkDisabled,
327 MkAutonomous,
328 MkDriver,
329> where
330 MkConnected:
331 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
332 MkDisconnected:
333 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
334 MkDisabled:
335 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
336 MkAutonomous:
337 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
338 MkDriver:
339 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
340{
341 // Functions to generate tasks for each mode.
342 mk_connected: MkConnected,
343 mk_disconnected: MkDisconnected,
344 mk_disabled: MkDisabled,
345 mk_autonomous: MkAutonomous,
346 mk_driver: MkDriver,
347
348 /// A stream of updates to the competition status.
349 #[pin]
350 updates: CompetitionUpdates,
351
352 /// The current status bits of the competition.
353 status: CompetitionStatus,
354
355 /// The current phase of the competition runtime.
356 phase: CompetitionRuntimePhase,
357
358 /// The task currently running, or [`None`] if no task is running.
359 ///
360 /// SAFETY:
361 /// - The `'static` lifetime is a lie to the compiler, it actually borrows `self.shared`.
362 /// Therefore, tasks MUST NOT move their `&'static mut` references, or else they will still
363 /// be around when we call a `mk_*` function with a new mutable reference to it. We rely on
364 /// lifetime parametricity of the `mk_*` functions for this (see the HRTBs above).
365 /// - This field MUST come before `shared`, as struct fields are dropped in declaration order.
366 #[allow(clippy::type_complexity)]
367 task: Option<Pin<Box<dyn Future<Output = ControlFlow<Return>> + 'static>>>,
368
369 /// A cell containing the data shared between all tasks.
370 ///
371 /// SAFETY: This field MUST NOT be mutated while a task is running, as tasks may still hold
372 /// references to it. This is enforced to owners of this struct by the `Pin`, but we have no
373 /// such guardrails, as we cannot project the pin to it without creating an invalid `Pin<&mut
374 /// Shared>` before possibly (legally) moving it during task creation.
375 shared: UnsafeCell<Shared>,
376
377 /// Keep `self.shared` in place while `self.task` references it.
378 _pin: PhantomPinned,
379}
380
381#[derive(Debug, Clone, Copy, PartialEq, Eq)]
382enum CompetitionRuntimePhase {
383 Initial,
384 Disconnected,
385 Connected,
386 Mode(CompetitionMode),
387}
388
389impl<Shared, Return, MkConnected, MkDisconnected, MkDisabled, MkAutonomous, MkDriver> Future
390 for CompetitionRuntime<
391 Shared,
392 Return,
393 MkConnected,
394 MkDisconnected,
395 MkDisabled,
396 MkAutonomous,
397 MkDriver,
398 >
399where
400 MkConnected:
401 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
402 MkDisconnected:
403 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
404 MkDisabled:
405 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
406 MkAutonomous:
407 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
408 MkDriver:
409 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
410{
411 type Output = Return;
412
413 fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
414 let mut this = self.as_mut().project();
415
416 let old_phase = *this.phase;
417
418 // Poll any updates to competition status.
419 match this.updates.as_mut().poll_next(cx) {
420 Poll::Ready(Some(new_status)) => {
421 let old_status = *this.status;
422
423 // Connected and Disconnected should not be interrupted by other changes.
424 if *this.phase != CompetitionRuntimePhase::Connected
425 && *this.phase != CompetitionRuntimePhase::Disconnected
426 {
427 // Decide which phase we're in based on the status update.
428 *this.phase = if !old_status.is_connected() && new_status.is_connected() {
429 CompetitionRuntimePhase::Connected
430 } else if old_status.is_connected() && !new_status.is_connected() {
431 CompetitionRuntimePhase::Disconnected
432 } else {
433 CompetitionRuntimePhase::Mode(new_status.mode())
434 };
435 }
436
437 *this.status = new_status;
438 }
439 Poll::Ready(None) => unreachable!(),
440 _ => {}
441 }
442
443 if let Some(Poll::Ready(res)) = this.task.as_mut().map(|task| task.as_mut().poll(cx)) {
444 // If a task says to break out of the competition lifecycle, then we pass the return
445 // value up.
446 if let ControlFlow::Break(val) = res {
447 return Poll::Ready(val);
448 }
449
450 // Reset the current task to nothing, since we're done with the previous task.
451 *this.task = None;
452
453 match *this.phase {
454 // Transition into the current mode if we previously ran the connected/disconnected
455 // task and it completed.
456 CompetitionRuntimePhase::Connected | CompetitionRuntimePhase::Disconnected => {
457 *this.phase = CompetitionRuntimePhase::Mode(this.updates.last().mode());
458 }
459 _ => {}
460 }
461 }
462
463 // We're now in a different competition phase, so we need to start a new task.
464 if old_phase != *this.phase {
465 // SAFETY: Before we make a new `&mut Shared`, we ensure that the existing task is
466 // dropped. Note that although this would not normally ensure that the reference is
467 // dropped, because the task could move it elsewhere, this is not the case here, because
468 // the task generator functions (and therefore their returned futures) are valid for any
469 // _arbitrarily small_ lifetime `'t`. Therefore, they are unable to move it elsewhere
470 // without proving that the reference will be destroyed before the task returns.
471 drop(this.task.take());
472 let shared = unsafe { &mut *this.shared.get() };
473
474 // Create a new task based on the new competition phase.
475 *this.task = match *this.phase {
476 CompetitionRuntimePhase::Initial => None,
477 CompetitionRuntimePhase::Disconnected => Some((this.mk_disconnected)(shared)),
478 CompetitionRuntimePhase::Connected => Some((this.mk_connected)(shared)),
479 CompetitionRuntimePhase::Mode(CompetitionMode::Disabled) => {
480 Some((this.mk_disabled)(shared))
481 }
482 CompetitionRuntimePhase::Mode(CompetitionMode::Autonomous) => {
483 Some((this.mk_autonomous)(shared))
484 }
485 CompetitionRuntimePhase::Mode(CompetitionMode::Driver) => {
486 Some((this.mk_driver)(shared))
487 }
488 };
489 }
490
491 Poll::Pending
492 }
493}
494
495impl<Shared, Return>
496 CompetitionRuntime<
497 Shared,
498 Return,
499 DefaultMk<Shared, Return>,
500 DefaultMk<Shared, Return>,
501 DefaultMk<Shared, Return>,
502 DefaultMk<Shared, Return>,
503 DefaultMk<Shared, Return>,
504 >
505{
506 /// Create a typed builder for a competition runtime with the given `shared` data.
507 ///
508 /// The default tasks simply do nothing, so you do not need to supply them if you don't want to.
509 pub const fn builder(shared: Shared) -> CompetitionBuilder<Shared, Return> {
510 fn default_mk<Shared, Return>(
511 _: &mut Shared,
512 ) -> Pin<Box<dyn Future<Output = ControlFlow<Return>>>> {
513 Box::pin(async { ControlFlow::Continue(()) })
514 }
515
516 CompetitionBuilder {
517 shared,
518 mk_connected: default_mk,
519 mk_disconnected: default_mk,
520 mk_disabled: default_mk,
521 mk_autonomous: default_mk,
522 mk_driver: default_mk,
523 _return: PhantomData,
524 }
525 }
526}
527
528type DefaultMk<Shared, Return> =
529 for<'t> fn(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>;
530
531/// A typed builder for [`CompetitionRuntime`] instances.
532pub struct CompetitionBuilder<
533 Shared,
534 Return,
535 MkConnected = DefaultMk<Shared, Return>,
536 MkDisconnected = DefaultMk<Shared, Return>,
537 MkDisabled = DefaultMk<Shared, Return>,
538 MkAutonomous = DefaultMk<Shared, Return>,
539 MkDriver = DefaultMk<Shared, Return>,
540> {
541 shared: Shared,
542
543 mk_connected: MkConnected,
544 mk_disconnected: MkDisconnected,
545 mk_disabled: MkDisabled,
546 mk_autonomous: MkAutonomous,
547 mk_driver: MkDriver,
548
549 // We're invariant in the return type.
550 _return: PhantomData<fn(Return) -> Return>,
551}
552
553impl<Shared, Return, MkConnected, MkDisconnected, MkDisabled, MkAutonomous, MkDriver>
554 CompetitionBuilder<
555 Shared,
556 Return,
557 MkConnected,
558 MkDisconnected,
559 MkDisabled,
560 MkAutonomous,
561 MkDriver,
562 >
563where
564 MkConnected:
565 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
566 MkDisconnected:
567 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
568 MkDisabled:
569 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
570 MkAutonomous:
571 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
572 MkDriver:
573 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
574{
575 /// Finish the builder, returning a [`CompetitionRuntime`] instance.
576 pub fn finish(
577 self,
578 ) -> CompetitionRuntime<
579 Shared,
580 Return,
581 MkConnected,
582 MkDisconnected,
583 MkDisabled,
584 MkAutonomous,
585 MkDriver,
586 > {
587 CompetitionRuntime {
588 mk_connected: self.mk_connected,
589 mk_disconnected: self.mk_disconnected,
590 mk_disabled: self.mk_disabled,
591 mk_autonomous: self.mk_autonomous,
592 mk_driver: self.mk_driver,
593 status: status(),
594 updates: updates(),
595 phase: CompetitionRuntimePhase::Initial,
596 task: None,
597 shared: UnsafeCell::new(self.shared),
598 _pin: PhantomPinned,
599 }
600 }
601}
602
603impl<Shared: 'static, Return, MkConnected, MkDisconnected, MkDisabled, MkAutonomous, MkDriver>
604 IntoFuture
605 for CompetitionBuilder<
606 Shared,
607 Return,
608 MkConnected,
609 MkDisconnected,
610 MkDisabled,
611 MkAutonomous,
612 MkDriver,
613 >
614where
615 MkConnected:
616 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
617 MkDisconnected:
618 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
619 MkDisabled:
620 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
621 MkAutonomous:
622 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
623 MkDriver:
624 for<'t> FnMut(&'t mut Shared) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 't>>,
625{
626 type Output = Return;
627
628 type IntoFuture = CompetitionRuntime<
629 Shared,
630 Return,
631 MkConnected,
632 MkDisconnected,
633 MkDisabled,
634 MkAutonomous,
635 MkDriver,
636 >;
637
638 fn into_future(self) -> Self::IntoFuture {
639 self.finish()
640 }
641}
642
643impl<Shared, Return, MkDisconnected, MkDisabled, MkAutonomous, MkDriver>
644 CompetitionBuilder<
645 Shared,
646 Return,
647 DefaultMk<Shared, Return>,
648 MkDisconnected,
649 MkDisabled,
650 MkAutonomous,
651 MkDriver,
652 >
653{
654 /// Use the given function to create a task that runs when the robot is connected to a
655 /// competition system. This task will run until termination before any other tasks
656 /// (disconnected, disabled, autonomous, driver) are run.
657 pub fn on_connect<Mk>(
658 self,
659 mk_connected: Mk,
660 ) -> CompetitionBuilder<Shared, Return, Mk, MkDisconnected, MkDisabled, MkAutonomous, MkDriver>
661 where
662 Mk: for<'s> FnMut(
663 &'s mut Shared,
664 ) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 's>>,
665 {
666 CompetitionBuilder {
667 shared: self.shared,
668 mk_connected,
669 mk_disconnected: self.mk_disconnected,
670 mk_disabled: self.mk_disabled,
671 mk_autonomous: self.mk_autonomous,
672 mk_driver: self.mk_driver,
673 _return: self._return,
674 }
675 }
676}
677
678impl<Shared, Return, MkConnected, MkDisabled, MkAutonomous, MkDriver>
679 CompetitionBuilder<
680 Shared,
681 Return,
682 MkConnected,
683 DefaultMk<Shared, Return>,
684 MkDisabled,
685 MkAutonomous,
686 MkDriver,
687 >
688{
689 /// Use the given function to create a task that runs when the robot is disconnected from a
690 /// competition system. This task will run until termination before any other tasks
691 /// (connected, disabled, autonomous, driver) are run.
692 pub fn on_disconnect<Mk>(
693 self,
694 mk_disconnected: Mk,
695 ) -> CompetitionBuilder<Shared, Return, MkConnected, Mk, MkDisabled, MkAutonomous, MkDriver>
696 where
697 Mk: for<'s> FnMut(
698 &'s mut Shared,
699 ) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 's>>,
700 {
701 CompetitionBuilder {
702 shared: self.shared,
703 mk_connected: self.mk_connected,
704 mk_disconnected,
705 mk_disabled: self.mk_disabled,
706 mk_autonomous: self.mk_autonomous,
707 mk_driver: self.mk_driver,
708 _return: self._return,
709 }
710 }
711}
712
713impl<Shared, Return, MkConnected, MkDisconnected, MkAutonomous, MkDriver>
714 CompetitionBuilder<
715 Shared,
716 Return,
717 MkConnected,
718 MkDisconnected,
719 DefaultMk<Shared, Return>,
720 MkAutonomous,
721 MkDriver,
722 >
723{
724 /// Use the given function to create a task that runs while the robot is disabled. If the task
725 /// terminates before the end of the disabled period, it will NOT be restarted.
726 pub fn while_disabled<Mk>(
727 self,
728 mk_disabled: Mk,
729 ) -> CompetitionBuilder<Shared, Return, MkConnected, MkDisconnected, Mk, MkAutonomous, MkDriver>
730 where
731 Mk: for<'s> FnMut(
732 &'s mut Shared,
733 ) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 's>>,
734 {
735 CompetitionBuilder {
736 shared: self.shared,
737 mk_connected: self.mk_connected,
738 mk_disconnected: self.mk_disconnected,
739 mk_disabled,
740 mk_autonomous: self.mk_autonomous,
741 mk_driver: self.mk_driver,
742 _return: self._return,
743 }
744 }
745}
746
747impl<Shared, Return, MkConnected, MkDisconnected, MkDisabled, MkDriver>
748 CompetitionBuilder<
749 Shared,
750 Return,
751 MkConnected,
752 MkDisconnected,
753 MkDisabled,
754 DefaultMk<Shared, Return>,
755 MkDriver,
756 >
757{
758 /// Use the given function to create a task that runs while the robot is autonomously
759 /// controlled. If the task terminates before the end of the autonomous period, it will NOT
760 /// be restarted.
761 pub fn while_autonomous<Mk>(
762 self,
763 mk_autonomous: Mk,
764 ) -> CompetitionBuilder<Shared, Return, MkConnected, MkDisconnected, MkDisabled, Mk, MkDriver>
765 where
766 Mk: for<'s> FnMut(
767 &'s mut Shared,
768 ) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 's>>,
769 {
770 CompetitionBuilder {
771 shared: self.shared,
772 mk_connected: self.mk_connected,
773 mk_disconnected: self.mk_disconnected,
774 mk_disabled: self.mk_disabled,
775 mk_autonomous,
776 mk_driver: self.mk_driver,
777 _return: self._return,
778 }
779 }
780}
781
782impl<Shared, Return, MkConnected, MkDisconnected, MkDisabled, MkAutonomous>
783 CompetitionBuilder<
784 Shared,
785 Return,
786 MkConnected,
787 MkDisconnected,
788 MkDisabled,
789 MkAutonomous,
790 DefaultMk<Shared, Return>,
791 >
792{
793 /// Use the given function to create a task that runs while the robot is driver controlled. If
794 /// the task terminates before the end of the driver control period, it will NOT be restarted.
795 pub fn while_driving<Mk>(
796 self,
797 mk_driver: Mk,
798 ) -> CompetitionBuilder<Shared, Return, MkConnected, MkDisconnected, MkDisabled, MkAutonomous, Mk>
799 where
800 Mk: for<'s> FnMut(
801 &'s mut Shared,
802 ) -> Pin<Box<dyn Future<Output = ControlFlow<Return>> + 's>>,
803 {
804 CompetitionBuilder {
805 shared: self.shared,
806 mk_connected: self.mk_connected,
807 mk_disconnected: self.mk_disconnected,
808 mk_disabled: self.mk_disabled,
809 mk_autonomous: self.mk_autonomous,
810 mk_driver,
811 _return: self._return,
812 }
813 }
814}
815
816/// A set of functions to run when the competition is in a particular mode.
817///
818/// This trait allows you to declare different functions on a common robot struct to be executed
819/// when competition state changes. By implementing `Compete`, your robot can respond to changes in
820/// the various phases of a competition, such as autonomous operation, driver control, or downtime
821/// between modes.
822///
823/// # Example
824///
825/// ```no_run
826/// use vexide::prelude::*;
827///
828/// struct MyRobot {}
829///
830/// impl Compete for MyRobot {
831/// async fn autonomous(&mut self) {
832/// println!("Running in autonomous mode!");
833/// }
834///
835/// async fn driver(&mut self) {
836/// println!("Running in driver mode!");
837/// }
838/// }
839///
840/// #[vexide::main]
841/// async fn main(_peripherals: Peripherals) {
842/// let my_robot = MyRobot {};
843/// my_robot.compete().await;
844/// }
845/// ```
846///
847/// By awaiting the [`compete()`] function on our robot, we are handing over execution to vexide's
848/// [`CompetitionRuntime`], which will run a different function on the [`Compete`] trait depending
849/// on what is happening in the match.
850///
851/// [`compete()`]: CompeteExt::compete
852#[allow(async_fn_in_trait, clippy::unused_async)]
853pub trait Compete: Sized {
854 /// Runs when the robot becomes connected into a competition controller.
855 ///
856 /// See [`CompetitionBuilder::on_connect`] for more information.
857 async fn connected(&mut self) {}
858
859 /// Runs when the robot disconnects from a competition controller.
860 ///
861 /// <section class="warning">
862 ///
863 /// This function does NOT run if connection to the match is lost due to a radio issue. It will
864 /// only execute if the field control wire becomes physically disconnected from the controller
865 /// (i.e.) from unplugging after a match ends.
866 ///
867 /// </section>
868 ///
869 /// See [`CompetitionBuilder::on_disconnect`] for more information.
870 async fn disconnected(&mut self) {}
871
872 /// Runs when the robot is disabled.
873 ///
874 /// When in disabled mode, voltage commands to motors are disabled. Motors are forcibly locked
875 /// to the "coast" brake mode and cannot be moved.
876 ///
877 /// Robots may be placed into disabled mode at any point in the competition after connecting,
878 /// but are typically disabled before the autonomous period, between autonomous and opcontrol
879 /// periods, and following the opcontrol period of a match.
880 async fn disabled(&mut self) {}
881
882 /// Runs when the robot is put into autonomous mode.
883 ///
884 /// When in autonomous mode, all motors and sensors may be accessed, however user input from
885 /// controller buttons and joysticks is not available to be read.
886 ///
887 /// Robots may be placed into autonomous mode at any point in the competition after connecting,
888 /// but are typically placed into this mode at the start of a match.
889 async fn autonomous(&mut self) {}
890
891 /// Runs when the robot is put into driver control mode.
892 ///
893 /// When in opcontrol mode, all device access is available including access to controller
894 /// joystick values for reading user-input from drive team members.
895 ///
896 /// Robots may be placed into opcontrol mode at any point in the competition after connecting,
897 /// but are typically placed into this mode following the autonomous period.
898 async fn driver(&mut self) {}
899}
900
901/// Extension methods for [`Compete`].
902///
903/// Automatically implemented for any type implementing [`Compete`].
904#[allow(clippy::type_complexity)]
905pub trait CompeteExt: Compete {
906 /// Build a competition runtime that competes with this robot.
907 fn compete(
908 self,
909 ) -> CompetitionRuntime<
910 Self,
911 !,
912 impl for<'s> FnMut(&'s mut Self) -> Pin<Box<dyn Future<Output = ControlFlow<!>> + 's>>,
913 impl for<'s> FnMut(&'s mut Self) -> Pin<Box<dyn Future<Output = ControlFlow<!>> + 's>>,
914 impl for<'s> FnMut(&'s mut Self) -> Pin<Box<dyn Future<Output = ControlFlow<!>> + 's>>,
915 impl for<'s> FnMut(&'s mut Self) -> Pin<Box<dyn Future<Output = ControlFlow<!>> + 's>>,
916 impl for<'s> FnMut(&'s mut Self) -> Pin<Box<dyn Future<Output = ControlFlow<!>> + 's>>,
917 > {
918 #[allow(clippy::unit_arg)]
919 CompetitionRuntime::builder(self)
920 .on_connect(|s| Box::pin(async { ControlFlow::Continue(s.connected().await) }))
921 .on_disconnect(|s| Box::pin(async { ControlFlow::Continue(s.disconnected().await) }))
922 .while_disabled(|s| Box::pin(async { ControlFlow::Continue(s.disabled().await) }))
923 .while_autonomous(|s| Box::pin(async { ControlFlow::Continue(s.autonomous().await) }))
924 .while_driving(|s| Box::pin(async { ControlFlow::Continue(s.driver().await) }))
925 .finish()
926 }
927}
928
929impl<R: Compete> CompeteExt for R {}