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
83pub struct YoleckKnobHandle<'a> {
85 pub cmd: EntityCommands<'a>,
87 pub is_new: bool,
89 passed: HashMap<TypeId, BoxedArc>,
90}
91
92impl YoleckKnobHandle<'_> {
93 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 .cloned()
145 .unwrap_or_default();
146 YoleckKnobHandle {
147 cmd,
148 is_new,
149 passed,
150 }
151 }
152}