use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use bevy::prelude::*;
use crate::image::ImageRegistry;
use crate::render::{NoesisRenderState, NoesisSet};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ImageBitmap {
pub uri: String,
pub width: u32,
pub height: u32,
pub bytes: Arc<Vec<u8>>,
}
#[derive(Component, Clone, Default, Debug)]
pub struct NoesisImaging {
pub images: HashMap<String, ImageBitmap>,
}
impl NoesisImaging {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn set(
mut self,
name: impl Into<String>,
uri: impl Into<String>,
width: u32,
height: u32,
bytes: Arc<Vec<u8>>,
) -> Self {
self.images.insert(
name.into(),
ImageBitmap {
uri: uri.into(),
width,
height,
bytes,
},
);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ImageReadback {
pub has_source: bool,
pub actual_size: [f32; 2],
}
#[derive(Message, Debug, Clone)]
pub struct NoesisImageChanged {
pub view: Entity,
pub name: String,
pub readback: ImageReadback,
}
#[derive(Resource, Default)]
pub(crate) struct StagedImagingUris(HashMap<Entity, HashSet<String>>);
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn stage_imaging_bitmaps(
views: Query<(Entity, Ref<NoesisImaging>)>,
mut registry: ResMut<ImageRegistry>,
mut staged: ResMut<StagedImagingUris>,
) {
for (entity, imaging) in &views {
if !imaging.is_changed() {
continue;
}
let mut uris = HashSet::with_capacity(imaging.images.len());
for bitmap in imaging.images.values() {
registry.insert(
bitmap.uri.clone(),
bitmap.width,
bitmap.height,
Arc::clone(&bitmap.bytes),
);
uris.insert(bitmap.uri.clone());
}
staged.0.insert(entity, uris);
}
}
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn reap_removed_imaging(
mut removed: RemovedComponents<NoesisImaging>,
mut staged: ResMut<StagedImagingUris>,
mut registry: ResMut<ImageRegistry>,
mut state: Option<NonSendMut<NoesisRenderState>>,
) {
for entity in removed.read() {
if let Some(uris) = staged.0.remove(&entity) {
for uri in uris {
if !staged.0.values().any(|s| s.contains(&uri)) {
registry.remove(&uri);
}
}
}
if let Some(state) = state.as_deref_mut() {
state.reap_imaging_snapshots_for(entity);
}
}
}
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn poll_imaging_reads(
views: Query<(Entity, &NoesisImaging)>,
state: Option<NonSendMut<NoesisRenderState>>,
mut changed: MessageWriter<NoesisImageChanged>,
) {
let Some(mut state) = state else {
return;
};
for (entity, imaging) in &views {
for (name, readback) in state.poll_image_reads_for(entity, &imaging.images) {
changed.write(NoesisImageChanged {
view: entity,
name,
readback,
});
}
}
}
pub struct NoesisImagingPlugin;
impl Plugin for NoesisImagingPlugin {
fn build(&self, app: &mut App) {
app.add_message::<NoesisImageChanged>()
.init_resource::<StagedImagingUris>()
.add_systems(
PostUpdate,
(
stage_imaging_bitmaps.before(NoesisSet::Sync),
reap_removed_imaging
.after(stage_imaging_bitmaps)
.before(NoesisSet::Sync),
poll_imaging_reads.in_set(NoesisSet::Apply),
),
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builder_collects_images() {
let bytes = Arc::new(vec![255u8; 13 * 7 * 4]);
let i = NoesisImaging::new().set("Pic", "dm-bitmap://logo", 13, 7, Arc::clone(&bytes));
let got = i.images.get("Pic").expect("Pic entry");
assert_eq!(got.uri, "dm-bitmap://logo");
assert_eq!((got.width, got.height), (13, 7));
assert_eq!(got.bytes.len(), 13 * 7 * 4);
}
}