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