Skip to main content

ingame/
ingame.rs

1/* ANCHOR: use */
2use 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};
35/* ANCHOR_END: use */
36
37/* ANCHOR: main */
38fn main() -> Result<(), Box<dyn Error>> {
39    App::<Vertex>::default().run(State::default());
40
41    Ok(())
42}
43/* ANCHOR_END: main */
44
45const DELTA_TIME: f32 = 1.0 / 60.0;
46
47/* ANCHOR: state_struct */
48struct State {
49    // We store drawing context for later use in app state.
50    // Drawing context holds resources and stack-based states.
51    context: DrawContext,
52    // Timer used for fixed step frame particle system simulation.
53    timer: Instant,
54    assets: AssetDatabase,
55    image_shader: AssetRef,
56    ferris_texture: AssetRef,
57}
58/* ANCHOR_END: state_struct */
59
60/* ANCHOR: state_impl_default */
61impl Default for State {
62    fn default() -> Self {
63        Self {
64            context: Default::default(),
65            timer: Instant::now(),
66            assets: AssetDatabase::default()
67                // Text protocol for shader sources.
68                .with_protocol(TextAssetProtocol)
69                // Group protocol for loading many assets at once.
70                .with_protocol(GroupAssetProtocol)
71                // Custom shader protocol.
72                .with_protocol(BundleAssetProtocol::new("shader", ShaderAssetProcessor))
73                // Custom texture protocol.
74                .with_protocol(BundleAssetProtocol::new("texture", TextureAssetProcessor))
75                // Load all data from file system asynchronously.
76                .with_fetch(DeferredAssetFetch::new(
77                    FileAssetFetch::default().with_root("resources"),
78                )),
79            // Stored asset references for cached asset handles.
80            image_shader: AssetRef::new("shader://image.shader"),
81            ferris_texture: AssetRef::new("texture://ferris.png"),
82        }
83    }
84}
85/* ANCHOR_END: state_impl_default */
86
87/* ANCHOR: state_impl_appstate */
88impl AppState<Vertex> for State {
89    fn on_init(&mut self, graphics: &mut Graphics<Vertex>, _: &mut AppControl) {
90        // Setup scene camera.
91        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        // Load this scene group.
99        self.assets.ensure("group://ingame.txt").unwrap();
100    }
101
102    fn on_redraw(&mut self, graphics: &mut Graphics<Vertex>, _: &mut AppControl) {
103        // Process assets periotically.
104        if self.timer.elapsed().as_secs_f32() > DELTA_TIME {
105            self.timer = Instant::now();
106            self.process_assets(graphics);
107        }
108
109        // Do not render unless we have shader loaded.
110        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        // Begin drawing objects.
121        self.context.begin_frame(graphics);
122        self.context.push_shader(&image_shader);
123        self.context.push_blending(GlowBlending::Alpha);
124
125        // Draw sprite only if texture asset is loaded.
126        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        // Commit drawn objects.
137        self.context.end_frame();
138    }
139}
140/* ANCHOR_END: state_impl_appstate */
141
142/* ANCHOR: state_impl */
143impl State {
144    fn process_assets(&mut self, graphics: &mut Graphics<Vertex>) {
145        let mut commands = CommandBuffer::default();
146
147        // Process loaded shader assets into shader objects on GPU.
148        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        // Process loaded texture assets into texture objects on GPU.
160        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}
183/* ANCHOR_END: state_impl */
184
185/* ANCHOR: async_handle */
186// Workaround for GPU handles not being Send + Sync,
187// to be able to store them in assets database.
188struct 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/* ANCHOR_END: async_handle */
207
208/* ANCHOR: shader_protocol */
209// Decoded shader asset information with dependencies.
210#[derive(Debug, Serialize, Deserialize)]
211struct ShaderAssetInfo {
212    vertex: AssetRef,
213    fragment: AssetRef,
214}
215
216// Shader asset with vertex and fragment programs code.
217struct ShaderAsset {
218    vertex: String,
219    fragment: String,
220}
221
222// Shader asset processor that turns bytes -> shader info -> shader asset.
223struct 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        // We scan for decoded shader info and if dependencies are loaded,
247        // then turn them into shader asset.
248        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}
276/* ANCHOR_END: shader_protocol */
277
278/* ANCHOR: texture_protocol */
279// Decoded texture asset with its size and decoded bitmap bytes.
280struct 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        // Decode PNG image into texture size and bitmap bytes.
296        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/* ANCHOR_END: texture_protocol */