use std::{mem, sync::{atomic::{AtomicBool, Ordering}, Arc}, thread::JoinHandle};
use parking_lot::Mutex;
use bevy::{ecs::{system::SystemState, world::WorldId}, log, prelude::*};
use crossbeam::channel::{Receiver, Sender, TryRecvError};
use web_time::{Instant, Duration};
#[derive(Component)]
pub struct SubWorld {
handle: Option<JoinHandle<AppExit>>,
started: Arc<AtomicBool>,
tx: Sender<SuperSignal>,
}
impl SubWorld {
pub fn build<'c>(parent: WorldId, cmd: &'c mut Commands) -> SubWorldBuilder<'c> {
SubWorldBuilder {
parent,
channel_cap: 16,
resources: Vec::new(),
commands: cmd.spawn_empty()
}
}
pub fn is_exited(&self) -> bool {
self.handle.as_ref().is_none_or(|handle| handle.is_finished())
}
pub fn is_started(&self) -> bool {
self.started.load(Ordering::Relaxed)
}
pub fn take_handle(&mut self) -> Option<JoinHandle<AppExit>> {
self.handle.take()
}
pub fn exit(&self) {
let _ = self.tx.send(SuperSignal::Exit);
}
pub fn run_commands<F>(&self, f: F)
where
F: FnOnce(&mut Commands) + Send + Sync + 'static
{
let _ = self.tx.send(SuperSignal::Commands(Box::new(f)));
}
pub fn extract<S, Out, M>(&self, system: S) -> ExtractHandle<Out>
where
S: IntoSystem<(), Out, M> + Send + Sync + 'static,
Out: Send + Sync + 'static,
{
let shared = Arc::new(Shared::<Out>::new());
let shared2 = shared.clone();
let _ = self.tx.send(SuperSignal::WorldFn(Box::new(
move |world: &mut World| {
match world.run_system_cached(system) {
Ok(out) => shared2.set(Ok(out)),
Err(e) => {
log::error!("Attempted to run a SubTask in a SubWorld, but the system failed to register with error: '{e}'.");
shared2.set(Err(ExtractError::RegistrationErr))
}
}
}
)));
ExtractHandle {
shared,
send_time: Instant::now(),
recv_time: None,
}
}
pub fn extract_with_input<S, In, Out, M>(&self, input: In, system: S) -> ExtractHandle<Out>
where
S: IntoSystem<In, Out, M> + Send + Sync + 'static,
In: SystemInput<Inner<'static> = In> + Send + Sync + 'static,
Out: Send + Sync + 'static,
{
let shared = Arc::new(Shared::<Out>::new());
let shared2 = shared.clone();
let _ = self.tx.send(SuperSignal::WorldFn(Box::new(
move |world: &mut World| {
match world.run_system_cached_with(system, input) {
Ok(out) => shared2.set(Ok(out)),
Err(e) => {
log::error!("Attempted to run a SubTask in a SubWorld, but the system failed to register with error: '{e}'.");
shared2.set(Err(ExtractError::RegistrationErr))
}
}
}
)));
ExtractHandle {
shared,
send_time: Instant::now(),
recv_time: None,
}
}
}
impl Drop for SubWorld {
fn drop(&mut self) {
let _ = self.tx.try_send(SuperSignal::Exit);
}
}
pub struct SubWorldBuilder<'c> {
parent: WorldId,
channel_cap: usize,
resources: Vec<Box<dyn FnOnce(&mut App) -> &mut App + Send + Sync + 'static>>,
commands: EntityCommands<'c>,
}
impl<'c> SubWorldBuilder<'c> {
pub fn with_channel_cap(mut self, cap: usize) -> Self {
self.channel_cap = cap;
self
}
pub fn with_signal_rx<T>(mut self) -> Self
where
T: Send + Sync + 'static
{
let (tx, rx) = crossbeam::channel::bounded::<T>(self.channel_cap);
self.commands.insert(SignalTx { tx });
self.resources.push(Box::new(move |app| app.insert_resource(SignalRx { rx })));
self
}
pub fn with_signal_tx<T>(mut self) -> Self
where
T: Send + Sync + 'static,
{
let (tx, rx) = crossbeam::channel::bounded::<T>(self.channel_cap);
self.commands.insert(SignalRx { rx });
self.resources.push(Box::new(move |app| app.insert_resource(SignalTx { tx })));
self
}
pub fn with_signal<T>(self) -> Self
where
T: Send + Sync + 'static
{
self.with_signal_rx::<T>()
.with_signal_tx::<T>()
}
pub fn start<F>(mut self, f: F) -> EntityCommands<'c>
where
F: FnOnce(&mut App) + Send + Sync + 'static
{
let mut resources = std::mem::take(&mut self.resources);
let (tx, rx) = crossbeam::channel::bounded::<SuperSignal>(4);
let start = Arc::new(AtomicBool::new(false));
let started = start.clone();
let handle = std::thread::spawn(move || {
let mut app = App::new();
(f)(&mut app);
while let Some(res) = resources.pop() {
(res)(&mut app);
}
app
.add_systems(PreUpdate, process_super_signals)
.insert_resource(SuperWorld {
parent: self.parent,
started,
rx,
})
.run()
});
self.commands.insert(
SubWorld {
handle: Some(handle),
started: start,
tx
}
);
self.commands
}
}
#[derive(Component, Resource)]
#[component(immutable)]
pub struct SignalTx<T>
where
T: Send + Sync + 'static
{
tx: Sender<T>,
}
impl<T> SignalTx<T>
where
T: Send + Sync + 'static,
{
pub fn send(&self, signal: T) {
let _ = self.tx.try_send(signal);
}
}
#[derive(Component, Resource)]
pub struct SignalRx<T>
where
T: Send + Sync + 'static,
{
rx: Receiver<T>,
}
impl<T> Iterator for &mut SignalRx<T>
where
T: Send + Sync + 'static
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
self.rx.try_recv().ok()
}
}
#[derive(Resource)]
pub struct SuperWorld {
parent: WorldId,
started: Arc<AtomicBool>,
rx: Receiver<SuperSignal>,
}
impl SuperWorld {
pub fn parent(&self) -> WorldId {
self.parent
}
}
enum SuperSignal {
Commands(Box<dyn FnOnce(&mut Commands) + Send + Sync + 'static>),
WorldFn(Box<dyn FnOnce(&mut World) + Send + Sync + 'static>),
Exit,
}
fn process_super_signals(
world: &mut World,
has_ran: Local<bool>,
params: &mut SystemState<(
ResMut<SuperWorld>,
Commands,
EventWriter<AppExit>,
)>,
) {
let mut systems = Vec::new();
{
let (signals, mut commands, mut exit_ev) = params.get_mut(world);
if !*has_ran {
signals.started.store(true, Ordering::Relaxed);
}
loop {
match signals.rx.try_recv() {
Ok(signal) => {
match signal {
SuperSignal::Exit => {
exit_ev.write(AppExit::Success);
},
SuperSignal::Commands(f) => {
(f)(&mut commands)
},
SuperSignal::WorldFn(f) => {
systems.push(f);
}
}
},
Err(e) => {
match e {
TryRecvError::Empty => break,
TryRecvError::Disconnected => {
exit_ev.write(AppExit::Success);
},
}
},
}
}
}
while let Some(system) = systems.pop() {
(system)(world)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExtractError {
SendFailed,
RegistrationErr,
AlreadyReceived,
NotFinished,
}
#[derive(Component)]
pub struct ExtractHandle<Out=()> {
shared: Arc<Shared<Out>>,
send_time: Instant,
recv_time: Option<Instant>,
}
impl<Out> ExtractHandle<Out> {
pub fn duration(&self) -> Option<Duration> {
self.recv_time.map(|time| {
time.duration_since(self.send_time)
})
}
pub fn send_time(&self) -> Instant {
self.send_time
}
pub fn recv_time(&self) -> Option<Instant> {
self.recv_time
}
pub fn is_received(&self) -> bool {
self.recv_time.is_some()
}
pub fn poll(&mut self) -> Result<Out, ExtractError> {
match self.shared.get() {
Ok(out) => {
self.recv_time = Some(Instant::now());
Ok(out)
},
Err(e) => Err(e)
}
}
}
struct Shared<Out> {
inner: Mutex<Result<Out, ExtractError>>,
}
impl<Out> Shared<Out> {
fn new() -> Self {
Self {
inner: Mutex::new(Err(ExtractError::NotFinished))
}
}
fn get(self: &Arc<Self>) -> Result<Out, ExtractError> {
let mut guard = self.inner.lock();
if guard.is_ok() {
mem::replace(&mut*guard, Err(ExtractError::AlreadyReceived))
} else {
let Err(e) = &*guard else { unreachable!() };
if *e == ExtractError::NotFinished && Arc::strong_count(&self) == 1 {
return Err(ExtractError::SendFailed);
}
Err(e.clone())
}
}
fn set(&self, value: Result<Out, ExtractError>) {
*self.inner.lock() = value;
}
}