vexide_core/
competition.rs

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