screeps_async/
lib.rs

1#![warn(missing_docs)]
2//! A tick-aware async runtime for Screeps
3//!
4//! # Getting Started
5//!
6//! Add `screeps-async` to your `Cargo.toml`
7//! ```toml
8//! [dependencies]
9//! screeps-async = "0.3.1"
10//! ```
11//!
12//! # The [`#[screeps_async::main]`](main) macro
13//! ```
14//! #[screeps_async::main]
15//! pub fn game_loop() {
16//!     // Tick logic that spawns some tasks
17//!     screeps_async::spawn(async {
18//!         println!("Hello!");
19//!     });
20//! }
21//! ```
22//!
23//! This expands to roughly the following:
24//!
25//! ```
26//! pub fn game_loop() {
27//!     // Tick logic that spawns some tasks
28//!     screeps_async::spawn(async {
29//!         println!("Hello!");
30//!     });
31//!
32//!     screeps_async::run().unwrap();
33//! }
34//! ```
35
36pub mod macros;
37pub use macros::*;
38
39use std::cell::RefCell;
40pub mod error;
41pub mod job;
42pub mod runtime;
43pub mod sync;
44pub mod time;
45
46use crate::error::RuntimeError;
47use crate::job::JobHandle;
48use crate::runtime::{Builder, ScreepsRuntime};
49use std::future::Future;
50
51thread_local! {
52    /// The current runtime
53    pub static CURRENT: RefCell<Option<ScreepsRuntime>> =
54        const { RefCell::new(None) };
55}
56
57/// Configures the runtime with default settings. Must be called only once
58///
59/// To use custom settings, create a [Builder] with [Builder::new], customize as needed,
60/// then call [Builder::apply]
61///
62/// # Panics
63///
64/// This function panics if there is already a runtime initialized
65pub fn initialize() {
66    Builder::new().apply()
67}
68
69/// Run the task executor for one tick
70///
71/// This is just shorthand for:
72/// ```no_run
73/// # let _ = // avoid warnings from not capturing returned value
74/// screeps_async::with_runtime(|runtime| {
75///     runtime.run()
76/// });
77/// ```
78///
79/// # Panics
80///
81/// This function panics if the current runtime is not set
82pub fn run() -> Result<(), RuntimeError> {
83    with_runtime(|runtime| runtime.run())
84}
85
86/// The main entrypoint for the async runtime. Runs a future to completion.
87///
88/// See [ScreepsRuntime::block_on] for details
89pub fn block_on<F>(future: F) -> Result<F::Output, RuntimeError>
90where
91    F: Future + 'static,
92{
93    with_runtime(|runtime| runtime.block_on(future))
94}
95
96/// Spawn a new async task
97///
98/// # Panics
99///
100/// This function panics if the current runtime is not set
101pub fn spawn<F>(future: F) -> JobHandle<F::Output>
102where
103    F: Future + 'static,
104{
105    with_runtime(|runtime| runtime.spawn(future))
106}
107
108/// Acquire a reference to the [ScreepsRuntime].
109///
110/// # Panics
111///
112/// This function panics if the current runtime is not set
113pub fn with_runtime<F, R>(f: F) -> R
114where
115    F: FnOnce(&ScreepsRuntime) -> R,
116{
117    CURRENT.with_borrow(|runtime| {
118        let runtime = runtime
119            .as_ref()
120            .expect("No screeps_async runtime configured");
121        f(runtime)
122    })
123}
124
125#[cfg(not(test))]
126mod utils {
127    use screeps::game;
128
129    pub(super) fn game_time() -> u32 {
130        game::time()
131    }
132
133    /// Returns the percentage of tick time used so far
134    pub(super) fn time_used() -> f64 {
135        game::cpu::get_used() / game::cpu::tick_limit()
136    }
137}
138
139#[cfg(test)]
140mod utils {
141    pub(super) use super::tests::*;
142}
143
144#[cfg(test)]
145mod tests {
146    use crate::error::RuntimeError;
147    use crate::runtime::Builder;
148    use std::cell::RefCell;
149
150    thread_local! {
151        pub(crate) static GAME_TIME: RefCell<u32> = const { RefCell::new(0) };
152        pub(crate) static TIME_USED: RefCell<f64> = const { RefCell::new(0.0) };
153    }
154
155    pub(super) fn game_time() -> u32 {
156        GAME_TIME.with_borrow(|t| *t)
157    }
158
159    pub(super) fn time_used() -> f64 {
160        TIME_USED.with_borrow(|t| *t)
161    }
162
163    pub(crate) fn init_test() {
164        GAME_TIME.with_borrow_mut(|t| *t = 0);
165        TIME_USED.with_borrow_mut(|t| *t = 0.0);
166
167        Builder::new().apply()
168    }
169
170    /// Calls [crate::run] and increments [GAME_TIME] if [crate::run] succeeded
171    pub(crate) fn tick() -> Result<(), RuntimeError> {
172        crate::run()?;
173        GAME_TIME.with_borrow_mut(|t| *t += 1);
174        Ok(())
175    }
176}