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 {}