use std::{
sync::{
Arc, Condvar, Mutex,
atomic::{AtomicBool, AtomicU64, Ordering},
},
time::Duration,
};
use bevy_app::{App, AppExit, MainScheduleOrder, Plugin, PluginsState, Update};
use bevy_ecs::{
prelude::*,
schedule::{LogLevel, ScheduleBuildSettings, ScheduleLabel},
};
use bevy_tasks::{IoTaskPool, TaskPool};
use rig_core::serve::ServingPolicy;
use super::{
collect::{collect_streams, collect_tasks, settle},
dispatch::dispatch,
effect::{IdCounter, SeqCounter, WorldOutcomeCounter},
handlers::{HandlerIndex, WorldKinds},
record::{DeliveryBatch, begin_delivery_pass, record_bound, record_cancelled, record_outcome},
};
#[derive(ScheduleLabel, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RigSchedule;
#[derive(ScheduleLabel, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RigEnd;
#[derive(SystemSet, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BusSet {
Gate,
Dispatch,
Collect,
Judge,
}
#[derive(Resource, Debug, Clone, Copy, PartialEq, Eq)]
pub struct Policy(pub ServingPolicy);
#[derive(Resource, Clone, Default)]
pub struct Wake(Arc<WakeInner>);
#[derive(Default)]
struct WakeInner {
raised: AtomicBool,
generation: AtomicU64,
waker: futures::task::AtomicWaker,
lock: Mutex<()>,
condvar: Condvar,
}
impl Wake {
pub fn signal(&self) {
self.0.generation.fetch_add(1, Ordering::AcqRel);
self.0.raised.store(true, Ordering::Release);
self.0.waker.wake();
let guard = self
.0
.lock
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
self.0.condvar.notify_all();
drop(guard);
}
pub fn generation(&self) -> u64 {
self.0.generation.load(Ordering::Acquire)
}
pub fn take(&self) -> bool {
self.0.raised.swap(false, Ordering::AcqRel)
}
pub fn wait(&self, timeout: Duration) -> bool {
let deadline = std::time::Instant::now() + timeout;
let mut guard = self
.0
.lock
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
while !self.0.raised.load(Ordering::Acquire) {
let now = std::time::Instant::now();
if now >= deadline {
break;
}
let (next, _) = self
.0
.condvar
.wait_timeout(guard, deadline - now)
.unwrap_or_else(|poisoned| poisoned.into_inner());
guard = next;
}
drop(guard);
self.take()
}
pub fn woken(&self) -> impl Future<Output = ()> + Send + '_ {
std::future::poll_fn(move |cx| {
if self.take() {
return std::task::Poll::Ready(());
}
self.0.waker.register(cx.waker());
if self.take() {
std::task::Poll::Ready(())
} else {
std::task::Poll::Pending
}
})
}
}
pub fn woken_runner(idle: Duration) -> impl FnOnce(App) -> AppExit {
move |mut app: App| {
if app.plugins_state() == PluginsState::Ready {
app.finish();
app.cleanup();
}
let wake = app.world().resource::<Wake>().clone();
loop {
app.update();
if let Some(exit) = app.should_exit() {
return exit;
}
wake.wait(idle);
}
}
}
#[derive(Debug, Clone)]
pub struct BusPlugin {
pub policy: ServingPolicy,
pub ambiguity: LogLevel,
pub idle: Duration,
}
impl Default for BusPlugin {
fn default() -> Self {
Self {
policy: ServingPolicy::default(),
ambiguity: LogLevel::Warn,
idle: Duration::from_millis(100),
}
}
}
impl BusPlugin {
pub fn with_policy(policy: ServingPolicy) -> Self {
Self {
policy,
..Self::default()
}
}
#[must_use = "the setting applies to the returned value"]
pub fn ambiguity_detection(mut self, level: LogLevel) -> Self {
self.ambiguity = level;
self
}
pub fn install(&self, world: &mut World) {
assert!(
!world.contains_resource::<Policy>(),
"the bus is already installed in this world"
);
IoTaskPool::get_or_init(TaskPool::default);
world.insert_resource(Policy(ServingPolicy {
command_capacity: self.policy.command_capacity.max(1),
..self.policy
}));
world.init_resource::<Wake>();
world.init_resource::<SeqCounter>();
world.init_resource::<IdCounter>();
world.init_resource::<DeliveryBatch>();
world.init_resource::<WorldOutcomeCounter>();
world.init_resource::<HandlerIndex>();
world.init_resource::<WorldKinds>();
world.init_non_send::<super::effect::Executions>();
world.init_non_send::<super::handlers::HandlerTable>();
world.add_observer(super::effect::drop_execution);
world.add_observer(super::handlers::unbound);
world.add_observer(record_outcome);
world.add_observer(record_cancelled);
world.add_observer(super::record::witness_cancelled);
world.add_observer(record_bound);
let mut schedule = Schedule::new(RigSchedule);
schedule.set_build_settings(ScheduleBuildSettings {
ambiguity_detection: self.ambiguity,
..Default::default()
});
schedule.configure_sets(
(
BusSet::Gate,
BusSet::Dispatch,
BusSet::Collect,
BusSet::Judge,
)
.chain(),
);
schedule.add_systems(dispatch.in_set(BusSet::Dispatch));
schedule.add_systems(begin_delivery_pass.before(BusSet::Gate));
schedule.add_systems(
(
collect_tasks,
collect_streams,
super::collect::collect_world,
settle,
)
.chain()
.in_set(BusSet::Collect),
);
world.init_resource::<Schedules>();
world.resource_mut::<Schedules>().insert(schedule);
world
.resource_mut::<Schedules>()
.insert(Schedule::new(RigEnd));
}
}
impl Plugin for BusPlugin {
fn build(&self, app: &mut App) {
self.install(app.world_mut());
let mut order = app.world_mut().resource_mut::<MainScheduleOrder>();
order.insert_after(Update, RigSchedule);
order.insert_after(RigSchedule, RigEnd);
app.set_runner(woken_runner(self.idle));
}
}
#[cfg(test)]
mod tests;