use std::time::Duration;
use bevy::ecs::system::SystemParam;
use bevy::prelude::*;
use bevy::render::gpu_readback::{ReadbackComplete, ReadbackOnce};
use bevy::render::storage::ShaderBuffer;
use crate::plugin::ElementsSystems;
use crate::query::EnvironmentSnapshot;
#[derive(Resource, Clone, Debug, Reflect)]
pub struct ElementsReadback {
pub interval: Duration,
}
impl Default for ElementsReadback {
fn default() -> Self {
Self {
interval: Duration::from_millis(250),
}
}
}
#[derive(Resource, Default)]
pub(crate) struct ReadbackTick {
since: Duration,
due: bool,
}
impl ReadbackTick {
pub(crate) fn is_due(&self) -> bool {
self.due
}
}
#[derive(Component)]
pub(crate) struct ReadbackField {
key: &'static str,
issued: Duration,
}
pub(crate) struct ReadbackPlugin;
impl Plugin for ReadbackPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<ElementsReadback>()
.init_resource::<ReadbackTick>()
.add_systems(
Update,
(
tick.in_set(ElementsSystems::Clock),
drop_stale.in_set(ElementsSystems::Readback),
),
);
}
}
fn tick(time: Res<Time<Real>>, settings: Res<ElementsReadback>, mut tick: ResMut<ReadbackTick>) {
tick.since += time.delta();
tick.due = tick.since >= settings.interval;
if tick.due {
tick.since = Duration::ZERO;
}
}
fn drop_stale(
mut commands: Commands,
time: Res<Time<Real>>,
settings: Res<ElementsReadback>,
fields: Query<(Entity, &ReadbackField)>,
) {
let limit = settings.interval * 4 + Duration::from_secs(1);
for (entity, field) in &fields {
if time.elapsed().saturating_sub(field.issued) > limit {
commands.entity(entity).despawn();
}
}
}
#[derive(SystemParam)]
pub(crate) struct Readbacks<'w, 's> {
commands: Commands<'w, 's>,
outstanding: Query<'w, 's, &'static ReadbackField>,
tick: Res<'w, ReadbackTick>,
time: Res<'w, Time<Real>>,
}
impl Readbacks<'_, '_> {
pub fn request(
&mut self,
key: &'static str,
buffer: Handle<ShaderBuffer>,
decode: impl Fn(&[u8], &mut EnvironmentSnapshot) + Send + Sync + 'static,
) {
if !self.tick.due || self.outstanding.iter().any(|f| f.key == key) {
return;
}
self.commands
.spawn((
ReadbackField {
key,
issued: self.time.elapsed(),
},
ReadbackOnce::buffer(buffer),
))
.observe(
move |done: On<ReadbackComplete>,
mut snapshot: ResMut<EnvironmentSnapshot>,
time: Res<Time<Real>>,
mut commands: Commands| {
decode(&done.data, &mut snapshot);
snapshot.updated_at = Some(time.elapsed());
if let Ok(mut entity) = commands.get_entity(done.entity) {
entity.despawn();
}
},
);
}
}
pub(crate) fn decode<T: bytemuck::Pod>(bytes: &[u8]) -> Option<Vec<T>> {
bytes
.len()
.is_multiple_of(size_of::<T>())
.then(|| bytemuck::pod_collect_to_vec(bytes))
}