use crate::ecs::resources::Resources;
use crate::ecs::system::{RequiredResource, Res, SystemParam};
use crate::threading::{BackgroundTasks, SpawnableFuture};
pub struct Events<T> {
current: Vec<(usize, T)>,
previous: Vec<(usize, T)>,
next_id: usize,
}
impl<T> Default for Events<T> {
fn default() -> Self {
Self {
current: Vec::new(),
previous: Vec::new(),
next_id: 0,
}
}
}
impl<T> Events<T> {
pub fn send(&mut self, event: T) {
self.current.push((self.next_id, event));
self.next_id += 1;
}
pub(crate) fn update(&mut self) {
self.previous = std::mem::take(&mut self.current);
}
}
pub struct EventReader<'a, T: 'static> {
events: hecs::Ref<'a, Events<T>>,
last_seen: &'a mut usize,
}
impl<'a, T: 'static> EventReader<'a, T> {
pub fn iter(&mut self) -> impl Iterator<Item = &T> + '_ {
let seen = *self.last_seen;
let unread: Vec<&T> = self
.events
.previous
.iter()
.chain(self.events.current.iter())
.filter(|(id, _)| *id >= seen)
.map(|(_, event)| event)
.collect();
*self.last_seen = self.events.next_id;
unread.into_iter()
}
pub fn is_empty(&self) -> bool {
let seen = *self.last_seen;
!self
.events
.previous
.iter()
.chain(self.events.current.iter())
.any(|(id, _)| *id >= seen)
}
}
impl<T> SystemParam for EventReader<'static, T>
where
T: Send + Sync + 'static,
{
type Item<'a> = EventReader<'a, T>;
type State = usize;
fn fetch<'a>(
state: &'a mut Self::State,
world: &'a hecs::World,
resources: &'a Resources,
) -> Self::Item<'a> {
EventReader {
events: resources.get_resource(world),
last_seen: state,
}
}
fn requires() -> Vec<RequiredResource> {
vec![RequiredResource {
name: std::any::type_name::<Events<T>>(),
type_id: std::any::TypeId::of::<Events<T>>(),
present: |world, resources| resources.has_resource::<Events<T>>(world),
hint: Some(
"Register this event type with `app.add_event::<T>()` (or \
`app.add_async_event::<T>()` if it's delivered by a background task) \
before this system runs.",
),
}]
}
}
impl<T> SystemParam for Option<EventReader<'static, T>>
where
T: Send + Sync + 'static,
{
type Item<'a> = Option<EventReader<'a, T>>;
type State = usize;
fn fetch<'a>(
state: &'a mut Self::State,
world: &'a hecs::World,
resources: &'a Resources,
) -> Self::Item<'a> {
if resources.has_resource::<Events<T>>(world) {
return Some(EventReader {
events: resources.get_resource(world),
last_seen: state,
});
}
None
}
}
pub struct EventWriter<'a, T: 'static> {
events: hecs::RefMut<'a, Events<T>>,
}
impl<'a, T: 'static> EventWriter<'a, T> {
pub fn send(&mut self, event: T) {
self.events.send(event);
}
}
impl<T> SystemParam for EventWriter<'static, T>
where
T: Send + Sync + 'static,
{
type Item<'a> = EventWriter<'a, T>;
type State = ();
fn fetch<'a>(
_state: &'a mut Self::State,
world: &'a hecs::World,
resources: &'a Resources,
) -> Self::Item<'a> {
EventWriter {
events: resources.get_resource_mut(world),
}
}
fn requires() -> Vec<RequiredResource> {
vec![RequiredResource {
name: std::any::type_name::<Events<T>>(),
type_id: std::any::TypeId::of::<Events<T>>(),
present: |world, resources| resources.has_resource::<Events<T>>(world),
hint: Some(
"Register this event type with `app.add_event::<T>()` (or \
`app.add_async_event::<T>()` if it's delivered by a background task) \
before this system runs.",
),
}]
}
}
impl<T> SystemParam for Option<EventWriter<'static, T>>
where
T: Send + Sync + 'static,
{
type Item<'a> = Option<EventWriter<'a, T>>;
type State = ();
fn fetch<'a>(
_state: &'a mut Self::State,
world: &'a hecs::World,
resources: &'a Resources,
) -> Self::Item<'a> {
if resources.has_resource::<Events<T>>(world) {
return Some(EventWriter {
events: resources.get_resource_mut(world),
});
}
None
}
}
pub(crate) struct AsyncEventChannel<T> {
tx: crossbeam_channel::Sender<T>,
rx: crossbeam_channel::Receiver<T>,
}
impl<T> AsyncEventChannel<T> {
pub(crate) fn new() -> Self {
let (tx, rx) = crossbeam_channel::unbounded();
Self { tx, rx }
}
}
pub(crate) fn drain_async_events<T: Send + Sync + 'static>(
channel: Res<AsyncEventChannel<T>>,
mut writer: EventWriter<T>,
) {
while let Ok(event) = channel.rx.try_recv() {
writer.send(event);
}
}
pub struct AsyncEventWriter<'a, T: Send + 'static> {
tasks: hecs::Ref<'a, BackgroundTasks>,
channel: hecs::Ref<'a, AsyncEventChannel<T>>,
}
impl<'a, T: Send + 'static> AsyncEventWriter<'a, T> {
pub fn spawn(&self, future: impl SpawnableFuture<T>) {
let tx = self.channel.tx.clone();
let _ = self.tasks.spawn_async(async move {
let _ = tx.send(future.await);
});
}
}
impl<T> SystemParam for AsyncEventWriter<'static, T>
where
T: Send + Sync + 'static,
{
type Item<'a> = AsyncEventWriter<'a, T>;
type State = ();
fn fetch<'a>(
_state: &'a mut Self::State,
world: &'a hecs::World,
resources: &'a Resources,
) -> Self::Item<'a> {
AsyncEventWriter {
tasks: resources.get_resource(world),
channel: resources.get_resource(world),
}
}
fn requires() -> Vec<RequiredResource> {
vec![
RequiredResource {
name: std::any::type_name::<BackgroundTasks>(),
type_id: std::any::TypeId::of::<BackgroundTasks>(),
present: |world, resources| resources.has_resource::<BackgroundTasks>(world),
hint: Some(
"`AsyncEventWriter` drives its future through `BackgroundTasks` — register \
`app.add_plugin(BackgroundTasksPlugin::new(worker_count))` before this system runs.",
),
},
RequiredResource {
name: std::any::type_name::<AsyncEventChannel<T>>(),
type_id: std::any::TypeId::of::<AsyncEventChannel<T>>(),
present: |world, resources| resources.has_resource::<AsyncEventChannel<T>>(world),
hint: Some(
"Register this event type with `app.add_async_event::<T>()` (not \
`app.add_event`) before this system runs.",
),
},
]
}
}
impl<T> SystemParam for Option<AsyncEventWriter<'static, T>>
where
T: Send + Sync + 'static,
{
type Item<'a> = Option<AsyncEventWriter<'a, T>>;
type State = ();
fn fetch<'a>(
_state: &'a mut Self::State,
world: &'a hecs::World,
resources: &'a Resources,
) -> Self::Item<'a> {
if resources.has_resource::<BackgroundTasks>(world)
&& resources.has_resource::<AsyncEventChannel<T>>(world)
{
return Some(AsyncEventWriter {
tasks: resources.get_resource(world),
channel: resources.get_resource(world),
});
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::app::{App, SystemStage};
use crate::ecs::system::{Local, ResMut};
struct Damage(u32);
struct Seen(Vec<u32>);
fn send_once(mut writer: EventWriter<Damage>, mut sent: Local<bool>) {
if !*sent {
writer.send(Damage(5));
*sent = true;
}
}
fn record_damage(mut reader: EventReader<Damage>, mut seen: ResMut<Seen>) {
for event in reader.iter() {
seen.0.push(event.0);
}
}
#[test]
fn reader_in_an_earlier_stage_still_catches_a_same_tick_send_one_tick_later() {
let mut app = App::new();
app.add_event::<Damage>();
app.add_resource(Seen(Vec::new()));
app.add_system(SystemStage::PreUpdate, record_damage);
app.add_system(SystemStage::Update, send_once);
app.build();
app.update(); assert_eq!(app.get_resource::<Seen>().0, Vec::<u32>::new());
app.update(); assert_eq!(app.get_resource::<Seen>().0, vec![5]);
app.update(); assert_eq!(app.get_resource::<Seen>().0, vec![5]);
}
struct Loaded(u32);
fn kick_off(events: AsyncEventWriter<Loaded>, mut sent: Local<bool>) {
if !*sent {
events.spawn(async { Loaded(42) });
*sent = true;
}
}
fn record_loaded(mut reader: EventReader<Loaded>, mut seen: ResMut<Seen>) {
for event in reader.iter() {
seen.0.push(event.0);
}
}
#[test]
fn async_events_deliver_a_background_tasks_result_as_an_event() {
use crate::ecs::plugin::Plugin;
use crate::threading::BackgroundTasksPlugin;
let mut app = App::new();
BackgroundTasksPlugin::new(1).build(&mut app);
app.add_async_event::<Loaded>();
app.add_resource(Seen(Vec::new()));
app.add_system(SystemStage::Update, kick_off);
app.add_system(SystemStage::PostRender, record_loaded);
app.build();
for _ in 0..200 {
app.update();
if !app.get_resource::<Seen>().0.is_empty() {
break;
}
std::thread::sleep(std::time::Duration::from_millis(5));
}
assert_eq!(app.get_resource::<Seen>().0, vec![42]);
}
}