-
-
Notifications
You must be signed in to change notification settings - Fork 4k
Render events #18517
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Render events #18517
Changes from all commits
d5c87d6
68b8dfd
5cfc3fb
f33c99b
64dc9d2
7472baf
a0de8fd
1ed0408
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
use core::marker::PhantomData; | ||
|
||
use async_channel::{Receiver, Sender}; | ||
use bevy_app::{App, Plugin, PreUpdate}; | ||
use bevy_ecs::{ | ||
change_detection::MaybeLocation, | ||
event::{Event, Events}, | ||
resource::Resource, | ||
system::{Res, ResMut, SystemParam}, | ||
}; | ||
|
||
use crate::RenderApp; | ||
|
||
pub trait RenderEventApp { | ||
fn add_render_event<E: Event>(&mut self) -> &mut Self; | ||
} | ||
|
||
impl RenderEventApp for App { | ||
fn add_render_event<E: Event>(&mut self) -> &mut Self { | ||
self.add_plugins(RenderEventPlugin::<E>::default()) | ||
} | ||
} | ||
|
||
struct RenderEventPlugin<E: Event>(PhantomData<E>); | ||
|
||
impl<E: Event> Default for RenderEventPlugin<E> { | ||
fn default() -> Self { | ||
Self(PhantomData) | ||
} | ||
} | ||
|
||
impl<E: Event> Plugin for RenderEventPlugin<E> { | ||
fn build(&self, app: &mut App) { | ||
app.add_event::<E>() | ||
.add_systems(PreUpdate, relay_render_events::<E>); | ||
} | ||
|
||
fn finish(&self, app: &mut App) { | ||
let (sender, receiver) = async_channel::unbounded::<(E, MaybeLocation)>(); | ||
|
||
app.insert_resource(RenderEventReceiver(receiver)); | ||
|
||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else { | ||
return; | ||
}; | ||
|
||
render_app.insert_resource(RenderEventSender(sender)); | ||
} | ||
} | ||
|
||
#[derive(Resource)] | ||
struct RenderEventReceiver<E: Event>(Receiver<(E, MaybeLocation)>); | ||
#[derive(Resource)] | ||
struct RenderEventSender<E: Event>(Sender<(E, MaybeLocation)>); | ||
|
||
fn relay_render_events<E: Event>( | ||
mut events: ResMut<Events<E>>, | ||
receiver: Res<RenderEventReceiver<E>>, | ||
) { | ||
while let Ok((event, caller)) = receiver.0.try_recv() { | ||
events.send_with_caller(event, caller); | ||
} | ||
} | ||
|
||
/// An event writer for sending events from the render world to the main world. | ||
/// | ||
/// Internally, this struct writes to a channel, the contents of which are relayed | ||
/// to the main world's [`Events`] stream during [`PreUpdate`]. Note that because | ||
/// the render world is pipelined, the events may not arrive before the next frame begins. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They almost certainly will not, but they also could. I think we might want to be clearer about how this works. In practice, you'll receive events from rendering frame N-2 with respect the the main world, but that isn't guaranteed. Users should supply their own temporal correlation id if necessary. |
||
#[derive(SystemParam)] | ||
pub struct MainEventWriter<'w, E: Event> { | ||
sender: ResMut<'w, RenderEventSender<E>>, | ||
} | ||
|
||
const ERR_MSG: &str = "A render events channel has been closed. This is illegal"; | ||
|
||
impl<'w, E: Event> MainEventWriter<'w, E> { | ||
/// Writes an `event`, which can later be read by [`EventReader`](super::EventReader)s | ||
/// in the main world. | ||
/// | ||
/// See [`Events`] for details. | ||
#[doc(alias = "send")] | ||
#[track_caller] | ||
pub fn write(&mut self, event: E) { | ||
self.sender | ||
.0 | ||
.try_send((event, MaybeLocation::caller())) | ||
.expect(ERR_MSG); | ||
} | ||
|
||
/// Sends a list of `events` all at once, which can later be read | ||
/// by [`EventReader`](super::EventReader)s in the main world. | ||
/// This is more efficient than sending each event individually. | ||
/// | ||
/// See [`Events`] for details. | ||
#[doc(alias = "send_batch")] | ||
#[track_caller] | ||
pub fn write_batch(&mut self, events: impl IntoIterator<Item = E>) { | ||
events.into_iter().for_each(|event| { | ||
self.sender | ||
.0 | ||
.try_send((event, MaybeLocation::caller())) | ||
.expect(ERR_MSG); | ||
}); | ||
} | ||
|
||
/// Writes the default value of the event. Useful when the event is an empty struct. | ||
/// | ||
/// See [`Events`] for details. | ||
#[doc(alias = "send_default")] | ||
#[track_caller] | ||
pub fn write_default(&mut self) | ||
where | ||
E: Default, | ||
{ | ||
self.sender | ||
.0 | ||
.try_send((Default::default(), MaybeLocation::caller())) | ||
.expect(ERR_MSG); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
//! Simple example demonstrating the use of [`App::init_render_event`] to send events from the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be expanded to say, in effect, "you probably don't want to do this." |
||
//! render world to the main world | ||
|
||
use bevy::{ | ||
diagnostic::FrameCount, | ||
ecs::system::{LocalBuilder, ParamBuilder}, | ||
prelude::*, | ||
render::{ | ||
render_event::{MainEventWriter, RenderEventApp}, | ||
Extract, Render, RenderApp, | ||
}, | ||
}; | ||
|
||
fn main() -> AppExit { | ||
App::new() | ||
.add_plugins((DefaultPlugins, RenderEventDemoPlugin)) | ||
.run() | ||
} | ||
|
||
// We need a plugin to organize all the systems and render node required for this example | ||
struct RenderEventDemoPlugin; | ||
impl Plugin for RenderEventDemoPlugin { | ||
fn build(&self, app: &mut App) { | ||
app.add_render_event::<FrameRenderedEvent>() | ||
.add_systems(Update, read_render_event); | ||
} | ||
|
||
fn finish(&self, app: &mut App) { | ||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else { | ||
return; | ||
}; | ||
|
||
// Since `FrameCount` is not present in the render world by default, | ||
// we need to initialize it here. This is specific to this example, | ||
// and should be unnecessary for most users. | ||
render_app.init_resource::<FrameCount>(); | ||
|
||
let send_render_event = ( | ||
ParamBuilder, | ||
ParamBuilder, | ||
ParamBuilder, | ||
LocalBuilder(Timer::from_seconds(2.0, TimerMode::Repeating)), | ||
) | ||
.build_state(render_app.world_mut()) | ||
.build_system(send_render_event); | ||
Comment on lines
+38
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels a bit unnecessarily complex. I'd suggest using a resource instead just because that's a more common ECS api. |
||
|
||
render_app | ||
.add_systems(ExtractSchedule, extract_frame_count) | ||
.add_systems(Render, send_render_event); | ||
} | ||
} | ||
|
||
// Since `FrameCount` is not present in the render world by default, | ||
// we need to extract it here. This is specific to this example, and should | ||
// be unnecessary for most users. | ||
fn extract_frame_count( | ||
main_frame_count: Extract<Res<FrameCount>>, | ||
mut render_frame_count: ResMut<FrameCount>, | ||
) { | ||
//since the frame count gets updated before extraction, it'll appear | ||
//as one greater than it should be. | ||
render_frame_count.0 = main_frame_count.0 - 1; | ||
} | ||
|
||
#[derive(Event)] | ||
struct FrameRenderedEvent(u32); | ||
|
||
fn send_render_event( | ||
frame_count: Res<FrameCount>, | ||
mut render_events: MainEventWriter<FrameRenderedEvent>, | ||
time: Res<Time>, | ||
mut timer: Local<Timer>, | ||
) { | ||
timer.tick(time.delta()); | ||
if timer.finished() { | ||
render_events.write(FrameRenderedEvent(frame_count.0)); | ||
} | ||
} | ||
|
||
fn read_render_event( | ||
mut render_events: EventReader<FrameRenderedEvent>, | ||
frame_count: Res<FrameCount>, | ||
) { | ||
for render_event in render_events.read() { | ||
println!( | ||
"Render event from frame {} received in frame {}", | ||
render_event.0, frame_count.0 | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what our convention is here -- should this be in the prelude so it doesn't have to be imported?