bevy_yoleck/
knobs.rs

1use std::any::{Any, TypeId};
2use std::hash::{BuildHasher, Hash};
3
4use bevy::ecs::system::{EntityCommands, SystemParam};
5use bevy::platform::collections::HashMap;
6use bevy::prelude::*;
7
8use crate::editor::YoleckPassedData;
9use crate::BoxedArc;
10
11#[doc(hidden)]
12#[derive(Default, Resource)]
13pub struct YoleckKnobsCache {
14    by_key_hash: HashMap<u64, Vec<CachedKnob>>,
15}
16
17#[doc(hidden)]
18#[derive(Component)]
19pub struct YoleckKnobMarker;
20
21struct CachedKnob {
22    key: Box<dyn Send + Sync + Any>,
23    entity: Entity,
24    keep_alive: bool,
25}
26
27impl YoleckKnobsCache {
28    pub fn access<'a, K>(&mut self, key: K, commands: &'a mut Commands) -> KnobFromCache<'a>
29    where
30        K: 'static + Send + Sync + Hash + Eq,
31    {
32        let entries = self
33            .by_key_hash
34            .entry(self.by_key_hash.hasher().hash_one(&key))
35            .or_default();
36        for entry in entries.iter_mut() {
37            if let Some(cached_key) = entry.key.downcast_ref::<K>() {
38                if key == *cached_key {
39                    entry.keep_alive = true;
40                    return KnobFromCache {
41                        cmd: commands.entity(entry.entity),
42                        is_new: false,
43                    };
44                }
45            }
46        }
47        let cmd = commands.spawn(YoleckKnobMarker);
48        entries.push(CachedKnob {
49            key: Box::new(key),
50            entity: cmd.id(),
51            keep_alive: true,
52        });
53        KnobFromCache { cmd, is_new: true }
54    }
55
56    pub fn clean_untouched(&mut self, mut clean_func: impl FnMut(Entity)) {
57        self.by_key_hash.retain(|_, entries| {
58            entries.retain_mut(|entry| {
59                if entry.keep_alive {
60                    entry.keep_alive = false;
61                    true
62                } else {
63                    clean_func(entry.entity);
64                    false
65                }
66            });
67            !entries.is_empty()
68        });
69    }
70
71    pub fn drain(&mut self) -> impl '_ + Iterator<Item = Entity> {
72        self.by_key_hash
73            .drain()
74            .flat_map(|(_, entries)| entries.into_iter().map(|entry| entry.entity))
75    }
76}
77
78pub struct KnobFromCache<'a> {
79    pub cmd: EntityCommands<'a>,
80    pub is_new: bool,
81}
82
83/// An handle for intearcing with a knob from an edit system.
84pub struct YoleckKnobHandle<'a> {
85    /// The command of the knob entity.
86    pub cmd: EntityCommands<'a>,
87    /// `true` if the knob entity is just created this frame.
88    pub is_new: bool,
89    passed: HashMap<TypeId, BoxedArc>,
90}
91
92impl YoleckKnobHandle<'_> {
93    /// Get data sent to the knob from external systems (usually interaciton from the level
94    /// editor)
95    ///
96    /// The data is sent using [a directive event](crate::YoleckDirective::pass_to_entity).
97    ///
98    /// ```no_run
99    /// # use bevy::prelude::*;
100    /// # use bevy_yoleck::prelude::*;;
101    /// # use bevy_yoleck::vpeol::YoleckKnobClick;
102    /// # #[derive(Component)]
103    /// # struct Example {
104    /// #     num_clicks_on_knob: usize,
105    /// # };
106    /// fn edit_example_with_knob(mut edit: YoleckEdit<&mut Example>, mut knobs: YoleckKnobs) {
107    ///     let Ok(mut example) = edit.single_mut() else { return };
108    ///     let mut knob = knobs.knob("click-counting");
109    ///     knob.cmd.insert((
110    ///         // setup the knobs position and graphics
111    ///     ));
112    ///     if knob.get_passed_data::<YoleckKnobClick>().is_some() {
113    ///         example.num_clicks_on_knob += 1;
114    ///     }
115    /// }
116    /// ```
117    pub fn get_passed_data<T: 'static>(&self) -> Option<&T> {
118        if let Some(dynamic) = self.passed.get(&TypeId::of::<T>()) {
119            dynamic.downcast_ref()
120        } else {
121            None
122        }
123    }
124}
125
126#[derive(SystemParam)]
127pub struct YoleckKnobs<'w, 's> {
128    knobs_cache: ResMut<'w, YoleckKnobsCache>,
129    commands: Commands<'w, 's>,
130    passed_data: Res<'w, YoleckPassedData>,
131}
132
133impl YoleckKnobs<'_, '_> {
134    pub fn knob<K>(&mut self, key: K) -> YoleckKnobHandle
135    where
136        K: 'static + Send + Sync + Hash + Eq,
137    {
138        let KnobFromCache { cmd, is_new } = self.knobs_cache.access(key, &mut self.commands);
139        let passed = self
140            .passed_data
141            .0
142            .get(&cmd.id())
143            // TODO: find a way to do this with the borrow checker, without cloning
144            .cloned()
145            .unwrap_or_default();
146        YoleckKnobHandle {
147            cmd,
148            is_new,
149            passed,
150        }
151    }
152}