1use keket::{
3 database::{AssetDatabase, handle::AssetDependency, reference::AssetRef},
4 fetch::{deferred::DeferredAssetFetch, file::FileAssetFetch},
5 protocol::{
6 bundle::{BundleAssetProtocol, BundleWithDependencies, BundleWithDependenciesProcessor},
7 group::GroupAssetProtocol,
8 text::TextAssetProtocol,
9 },
10 third_party::anput::{
11 commands::{CommandBuffer, InsertCommand, RemoveCommand},
12 entity::Entity,
13 world::{Relation, World},
14 },
15};
16use serde::{Deserialize, Serialize};
17use spitfire::{
18 draw::{
19 context::DrawContext,
20 sprite::{Sprite, SpriteTexture},
21 utils::{Drawable, ResourceRef, Vertex},
22 },
23 glow::{
24 app::{App, AppControl, AppState},
25 graphics::{CameraScaling, Graphics, Shader, Texture},
26 renderer::{GlowBlending, GlowTextureFormat},
27 },
28};
29use std::{
30 error::Error,
31 io::Cursor,
32 sync::{Arc, RwLock},
33 time::Instant,
34};
35fn main() -> Result<(), Box<dyn Error>> {
39 App::<Vertex>::default().run(State::default());
40
41 Ok(())
42}
43const DELTA_TIME: f32 = 1.0 / 60.0;
46
47struct State {
49 context: DrawContext,
52 timer: Instant,
54 assets: AssetDatabase,
55 image_shader: AssetRef,
56 ferris_texture: AssetRef,
57}
58impl Default for State {
62 fn default() -> Self {
63 Self {
64 context: Default::default(),
65 timer: Instant::now(),
66 assets: AssetDatabase::default()
67 .with_protocol(TextAssetProtocol)
69 .with_protocol(GroupAssetProtocol)
71 .with_protocol(BundleAssetProtocol::new("shader", ShaderAssetProcessor))
73 .with_protocol(BundleAssetProtocol::new("texture", TextureAssetProcessor))
75 .with_fetch(DeferredAssetFetch::new(
77 FileAssetFetch::default().with_root("resources"),
78 )),
79 image_shader: AssetRef::new("shader://image.shader"),
81 ferris_texture: AssetRef::new("texture://ferris.png"),
82 }
83 }
84}
85impl AppState<Vertex> for State {
89 fn on_init(&mut self, graphics: &mut Graphics<Vertex>, _: &mut AppControl) {
90 graphics.state.color = [0.25, 0.25, 0.25, 1.0];
92 graphics.state.main_camera.screen_alignment = 0.5.into();
93 graphics.state.main_camera.scaling = CameraScaling::FitToView {
94 size: 1000.0.into(),
95 inside: false,
96 };
97
98 self.assets.ensure("group://ingame.txt").unwrap();
100 }
101
102 fn on_redraw(&mut self, graphics: &mut Graphics<Vertex>, _: &mut AppControl) {
103 if self.timer.elapsed().as_secs_f32() > DELTA_TIME {
105 self.timer = Instant::now();
106 self.process_assets(graphics);
107 }
108
109 let Ok(image_shader) = self.image_shader.resolve(&self.assets) else {
111 return;
112 };
113 let Some(image_shader) = image_shader
114 .access_checked::<&AsyncHandle<Shader>>()
115 .map(|handle| handle.to_ref())
116 else {
117 return;
118 };
119
120 self.context.begin_frame(graphics);
122 self.context.push_shader(&image_shader);
123 self.context.push_blending(GlowBlending::Alpha);
124
125 if let Ok(texture) = self.ferris_texture.resolve(&self.assets)
127 && let Some(texture) = texture
128 .access_checked::<&AsyncHandle<Texture>>()
129 .map(|handle| handle.to_ref())
130 {
131 Sprite::single(SpriteTexture::new("u_image".into(), texture))
132 .pivot(0.5.into())
133 .draw(&mut self.context, graphics);
134 }
135
136 self.context.end_frame();
138 }
139}
140impl State {
144 fn process_assets(&mut self, graphics: &mut Graphics<Vertex>) {
145 let mut commands = CommandBuffer::default();
146
147 for entity in self.assets.storage.added().iter_of::<ShaderAsset>() {
149 let asset = self
150 .assets
151 .storage
152 .component::<true, ShaderAsset>(entity)
153 .unwrap();
154 let shader = graphics.shader(&asset.vertex, &asset.fragment).unwrap();
155 println!("* Shader asset turned into shader: {entity}");
156 commands.command(InsertCommand::new(entity, (AsyncHandle::new(shader),)));
157 }
158
159 for entity in self.assets.storage.added().iter_of::<TextureAsset>() {
161 let asset = self
162 .assets
163 .storage
164 .component::<true, TextureAsset>(entity)
165 .unwrap();
166 let texture = graphics
167 .texture(
168 asset.width,
169 asset.height,
170 1,
171 GlowTextureFormat::Rgba,
172 Some(&asset.bytes),
173 )
174 .unwrap();
175 println!("* Texture asset turned into texture: {entity}");
176 commands.command(InsertCommand::new(entity, (AsyncHandle::new(texture),)));
177 }
178
179 commands.execute(&mut self.assets.storage);
180 self.assets.maintain().unwrap();
181 }
182}
183struct AsyncHandle<T: Clone>(Arc<RwLock<T>>);
189
190unsafe impl<T: Clone> Send for AsyncHandle<T> {}
191unsafe impl<T: Clone> Sync for AsyncHandle<T> {}
192
193impl<T: Clone> AsyncHandle<T> {
194 fn new(data: T) -> Self {
195 Self(Arc::new(RwLock::new(data)))
196 }
197
198 fn get(&self) -> T {
199 self.0.read().unwrap().clone()
200 }
201
202 fn to_ref(&self) -> ResourceRef<T> {
203 ResourceRef::object(self.get())
204 }
205}
206#[derive(Debug, Serialize, Deserialize)]
211struct ShaderAssetInfo {
212 vertex: AssetRef,
213 fragment: AssetRef,
214}
215
216struct ShaderAsset {
218 vertex: String,
219 fragment: String,
220}
221
222struct ShaderAssetProcessor;
224
225impl BundleWithDependenciesProcessor for ShaderAssetProcessor {
226 type Bundle = (ShaderAssetInfo,);
227
228 fn process_bytes(
229 &mut self,
230 bytes: Vec<u8>,
231 ) -> Result<BundleWithDependencies<Self::Bundle>, Box<dyn Error>> {
232 let asset = serde_json::from_slice::<ShaderAssetInfo>(&bytes)?;
233 let vertex = asset.vertex.path().clone();
234 let fragment = asset.fragment.path().clone();
235
236 println!("* Shader asset processed: {asset:#?}");
237 Ok(BundleWithDependencies::new((asset,))
238 .dependency(vertex)
239 .dependency(fragment))
240 }
241
242 fn maintain(&mut self, storage: &mut World) -> Result<(), Box<dyn Error>> {
243 let mut commands = CommandBuffer::default();
244 let mut lookup = storage.lookup_access::<true, &String>();
245
246 for (entity, info, dependencies) in
249 storage.query::<true, (Entity, &ShaderAssetInfo, &Relation<AssetDependency>)>()
250 {
251 if dependencies
252 .entities()
253 .all(|entity| storage.has_entity_component::<String>(entity))
254 {
255 let vertex = lookup
256 .access(storage.find_by::<true, _>(info.vertex.path()).unwrap())
257 .unwrap()
258 .to_owned();
259 let fragment = lookup
260 .access(storage.find_by::<true, _>(info.fragment.path()).unwrap())
261 .unwrap()
262 .to_owned();
263
264 let asset = ShaderAsset { vertex, fragment };
265 commands.command(InsertCommand::new(entity, (asset,)));
266 commands.command(RemoveCommand::<(ShaderAssetInfo,)>::new(entity));
267 }
268 }
269 drop(lookup);
270
271 commands.execute(storage);
272
273 Ok(())
274 }
275}
276struct TextureAsset {
281 width: u32,
282 height: u32,
283 bytes: Vec<u8>,
284}
285
286struct TextureAssetProcessor;
287
288impl BundleWithDependenciesProcessor for TextureAssetProcessor {
289 type Bundle = (TextureAsset,);
290
291 fn process_bytes(
292 &mut self,
293 bytes: Vec<u8>,
294 ) -> Result<BundleWithDependencies<Self::Bundle>, Box<dyn Error>> {
295 let decoder = png::Decoder::new(Cursor::new(bytes));
297 let mut reader = decoder.read_info()?;
298 let mut buf = vec![0; reader.output_buffer_size().unwrap_or_default()];
299 let info = reader.next_frame(&mut buf)?;
300 let bytes = buf[..info.buffer_size()].to_vec();
301
302 println!("* Texture asset processed: {info:#?}");
303 Ok(BundleWithDependencies::new((TextureAsset {
304 width: info.width,
305 height: info.height,
306 bytes,
307 },)))
308 }
309}
310