use crate::error::RuntimeError;
use crate::job::JobHandle;
use crate::utils::{game_time, time_used};
use crate::CURRENT;
use async_task::Runnable;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::future::Future;
use std::rc::Rc;
use std::sync::Mutex;
use std::task::Waker;
pub struct Builder {
config: Config,
}
impl Builder {
pub fn new() -> Self {
Self {
config: Config::default(),
}
}
pub fn tick_time_allocation(mut self, dur: f64) -> Self {
self.config.tick_time_allocation = dur;
self
}
pub fn apply(self) {
CURRENT.with_borrow_mut(|runtime| {
*runtime = Some(ScreepsRuntime::new(self.config));
})
}
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
pub struct Config {
tick_time_allocation: f64,
}
impl Default for Config {
fn default() -> Self {
Self {
tick_time_allocation: 0.9,
}
}
}
pub struct ScreepsRuntime {
scheduled: flume::Receiver<Runnable>,
sender: flume::Sender<Runnable>,
pub(crate) timers: Rc<Mutex<TimerMap>>,
config: Config,
is_blocking: Mutex<()>,
}
impl ScreepsRuntime {
pub(crate) fn new(config: Config) -> Self {
let (sender, scheduled) = flume::unbounded();
let timers = Rc::new(Mutex::new(BTreeMap::new()));
Self {
scheduled,
sender,
timers,
config,
is_blocking: Mutex::new(()),
}
}
pub fn spawn<F>(&self, future: F) -> JobHandle<F::Output>
where
F: Future + 'static,
{
let fut_res = Rc::new(RefCell::new(None));
let future = {
let fut_res = fut_res.clone();
async move {
let res = future.await;
let mut fut_res = fut_res.borrow_mut();
*fut_res = Some(res);
}
};
let sender = self.sender.clone();
let (runnable, task) = async_task::spawn_local(future, move |runnable| {
if !sender.is_disconnected() {
sender.send(runnable).unwrap();
}
});
runnable.schedule();
JobHandle::new(fut_res, task)
}
pub fn block_on<F>(&self, future: F) -> Result<F::Output, RuntimeError>
where
F: Future + 'static,
{
let _guard = self
.is_blocking
.try_lock()
.expect("Cannot block_on multiple futures at once. Please .await on the inner future");
let handle = self.spawn(future);
while !handle.is_complete() {
if !self.try_poll_scheduled()? {
return Err(RuntimeError::DeadlockDetected);
}
}
Ok(handle.fut_res.take().unwrap())
}
pub fn run(&self) -> Result<(), RuntimeError> {
self.wake_timers();
while self.try_poll_scheduled()? {}
Ok(())
}
pub(crate) fn try_poll_scheduled(&self) -> Result<bool, RuntimeError> {
if time_used() > self.config.tick_time_allocation {
return Err(RuntimeError::OutOfTime);
}
if let Ok(runnable) = self.scheduled.try_recv() {
runnable.run();
Ok(true)
} else {
Ok(false)
}
}
fn wake_timers(&self) {
let game_time = game_time();
let mut timers = self.timers.try_lock().unwrap();
let to_fire = {
let mut pending = timers.split_off(&(game_time + 1));
std::mem::swap(&mut pending, &mut timers);
pending
};
to_fire
.into_values()
.flatten()
.flatten()
.for_each(Waker::wake);
}
}
type TimerMap = BTreeMap<u32, Vec<Option<Waker>>>;
#[cfg(test)]
mod tests {
use super::*;
use crate::error::RuntimeError::OutOfTime;
use crate::tests::*;
use crate::time::yield_now;
use crate::{spawn, with_runtime};
use std::cell::OnceCell;
#[test]
fn test_block_on() {
init_test();
let res = crate::block_on(async move {
yield_now().await;
1 + 2
})
.unwrap();
assert_eq!(3, res);
}
#[test]
fn test_spawn() {
init_test();
drop(spawn(async move {}));
with_runtime(|runtime| {
runtime
.scheduled
.try_recv()
.expect("Failed to schedule task");
})
}
#[test]
fn test_run() {
init_test();
let has_run = Rc::new(OnceCell::new());
{
let has_run = has_run.clone();
spawn(async move {
has_run.set(()).unwrap();
})
.detach();
}
assert!(has_run.get().is_none());
crate::run().unwrap();
assert!(has_run.get().is_some());
}
#[test]
fn test_nested_spawn() {
init_test();
let has_run = Rc::new(OnceCell::new());
{
let has_run = has_run.clone();
spawn(async move {
let result = spawn(async move { 1 + 2 }).await;
assert_eq!(3, result);
has_run.set(()).unwrap();
})
.detach();
}
assert!(has_run.get().is_none());
crate::run().unwrap();
assert!(has_run.get().is_some());
}
#[test]
fn test_respects_time_remaining() {
init_test();
let has_run = Rc::new(OnceCell::new());
{
let has_run = has_run.clone();
spawn(async move {
has_run.set(()).unwrap();
})
.detach();
}
TIME_USED.with_borrow_mut(|t| *t = 0.95);
assert!(has_run.get().is_none());
assert_eq!(Err(OutOfTime), crate::run());
assert!(has_run.get().is_none());
}
}