gizmo_engine/systems/
streaming.rs1use gizmo_core::system::{AccessInfo, System};
17use gizmo_core::World;
18use gizmo_physics_core::Transform;
19use gizmo_renderer::components::{Camera, Material};
20
21const MAX_REQUESTS_PER_FRAME: usize = 3;
23const STREAM_IN_DISTANCE: f32 = 50.0;
25
26pub struct TextureStreamingSystem;
30
31impl System for TextureStreamingSystem {
32 fn access_info(&self) -> AccessInfo {
33 let mut info = AccessInfo::new();
34 info.is_exclusive = true;
35 info
36 }
37
38 fn run(&mut self, world: &World, _dt: f32) {
39 apply_completed_textures(world);
40 request_nearby_textures(world);
41 }
42}
43
44fn apply_completed_textures(world: &World) {
47 let completions = {
49 let Some(mut server) = world.get_resource_mut::<crate::asset_server::AssetServer>() else {
50 return;
51 };
52 if server.completed_textures.is_empty() {
53 return;
54 }
55 std::mem::take(&mut server.completed_textures)
56 };
57
58 let installed: Vec<(Vec<usize>, std::sync::Arc<wgpu::BindGroup>)> = {
60 let Some(renderer) = world.get_resource::<gizmo_renderer::Renderer>() else {
61 return;
63 };
64 let mut am = match renderer.asset_manager.write() {
65 Ok(am) => am,
66 Err(poisoned) => poisoned.into_inner(),
67 };
68 completions
69 .into_iter()
70 .filter_map(|c| {
71 match am.install_decoded_material_texture(
72 &renderer.device,
73 &renderer.queue,
74 &renderer.scene.texture_bind_group_layout,
75 &c.cache_key,
76 &c.rgba,
77 c.width,
78 c.height,
79 ) {
80 Ok(bg) => Some((c.entity_ids, bg)),
81 Err(e) => {
82 tracing::warn!(
83 "[streaming] texture install failed ({}): {:?}",
84 c.cache_key,
85 e
86 );
87 None
88 }
89 }
90 })
91 .collect()
92 };
93
94 if installed.is_empty() {
96 return;
97 }
98 let mut materials = unsafe { world.borrow_mut_unchecked::<Material>() };
100 for (entity_ids, bind_group) in installed {
101 for eid in entity_ids {
102 if let Some(mut mat) = materials.get_mut(eid as u32) {
103 mat.bind_group = bind_group.clone();
104 }
105 }
106 }
107}
108
109fn request_nearby_textures(world: &World) {
111 let cam_pos = {
113 let Some(q) = world.query::<(&Camera, &Transform)>() else {
114 return;
115 };
116 let mut fallback = None;
117 let mut primary = None;
118 for (_id, (cam, t)) in q.iter() {
119 if cam.primary {
120 primary = Some(t.position);
121 break;
122 }
123 if fallback.is_none() {
124 fallback = Some(t.position);
125 }
126 }
127 match primary.or(fallback) {
128 Some(p) => p,
129 None => return,
130 }
131 };
132
133 if world
135 .get_resource::<crate::asset_server::AssetServer>()
136 .is_none()
137 {
138 return;
139 }
140
141 let entities: Vec<u32> = world.borrow::<Material>().entities().collect();
143 let transforms = world.borrow::<Transform>();
144 let hidden = world.borrow::<gizmo_core::component::IsHidden>();
145 let server = world
146 .get_resource::<crate::asset_server::AssetServer>()
147 .expect("just checked present");
148 let mut materials = unsafe { world.borrow_mut_unchecked::<Material>() };
151
152 let mut requests = 0usize;
153 for e in entities {
154 if requests >= MAX_REQUESTS_PER_FRAME {
155 break;
156 }
157 if hidden.get(e).is_some() {
158 continue; }
160 let Some(mut mat) = materials.get_mut(e) else {
161 continue;
162 };
163 let Some(path) = mat.texture_source.clone() else {
164 continue;
165 };
166 let Some(t) = transforms.get(e) else {
167 continue;
168 };
169 if cam_pos.distance_squared(t.position) < STREAM_IN_DISTANCE * STREAM_IN_DISTANCE {
170 server.loader.request_texture_reload(path, e as usize);
171 mat.texture_source = None;
173 requests += 1;
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use crate::asset_server::AssetServer;
182 use gizmo_math::Vec3;
183 use gizmo_renderer::async_assets::TextureReloadCompletion;
184 use gizmo_renderer::Renderer;
185
186 fn gpu_available() -> bool {
189 pollster::block_on(Renderer::headless_adapter_available())
190 }
191
192 fn setup() -> (World, u32) {
195 let renderer = pollster::block_on(Renderer::new_headless(64, 64, None));
196 let mut world = World::new();
197
198 let cam = world.spawn();
199 world.add_component(cam, Camera::new(1.0, 0.1, 100.0, 0.0, 0.0, true));
200 world.add_component(cam, Transform::new(Vec3::ZERO));
201
202 let ent = world.spawn();
203 let mut mat = Material::new(renderer.create_white_texture());
204 mat.texture_source = Some("dummy.png".to_string());
205 world.add_component(ent, mat);
206 world.add_component(ent, Transform::new(Vec3::new(1.0, 0.0, 0.0)));
207
208 world.insert_resource(renderer);
209 world.insert_resource(AssetServer::new());
210 (world, ent.id())
211 }
212
213 #[test]
221 fn texture_streaming_requests_nearby_and_applies_completed() {
222 if !gpu_available() {
223 eprintln!("skip: GPU adapter yok (headless render GPU ister)");
224 return;
225 }
226 let (world, mat_id) = setup();
227
228 request_nearby_textures(&world);
230 assert!(
231 world
232 .borrow::<Material>()
233 .get(mat_id)
234 .is_some_and(|m| m.texture_source.is_none()),
235 "yakın materyal için streaming isteği atılıp texture_source None olmalı"
236 );
237
238 let before = world
240 .borrow::<Material>()
241 .get(mat_id)
242 .expect("material var")
243 .bind_group
244 .clone();
245 world
246 .get_resource_mut::<AssetServer>()
247 .expect("AssetServer resource")
248 .completed_textures
249 .push(TextureReloadCompletion {
250 cache_key: "test-red-2x2".to_string(),
251 rgba: vec![
252 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255,
253 ],
254 width: 2,
255 height: 2,
256 entity_ids: vec![mat_id as usize],
257 });
258 apply_completed_textures(&world);
259 let after = world
260 .borrow::<Material>()
261 .get(mat_id)
262 .expect("material var")
263 .bind_group
264 .clone();
265 assert!(
266 !std::sync::Arc::ptr_eq(&before, &after),
267 "apply, bind_group'u yeni yüklenen texture ile DEĞİŞTİRMELİ"
268 );
269
270 apply_completed_textures(&world);
272 let after2 = world
273 .borrow::<Material>()
274 .get(mat_id)
275 .expect("material var")
276 .bind_group
277 .clone();
278 assert!(
279 std::sync::Arc::ptr_eq(&after, &after2),
280 "kuyruk boşken apply materyali değiştirmemeli"
281 );
282
283 std::mem::forget(world);
285 }
286}