Skip to main content

gc/
gc.rs

1use quaso::{
2    GameLauncher,
3    assets::{make_directory_database, shader::ShaderAsset},
4    config::Config,
5    context::GameContext,
6    game::{GameInstance, GameObject, GameState, GameStateChange},
7    gc::Gc,
8    third_party::{
9        spitfire_draw::{
10            sprite::Sprite,
11            utils::{Drawable, ShaderRef},
12        },
13        spitfire_glow::graphics::{CameraScaling, Shader},
14        spitfire_input::{
15            CardinalInputCombinator, InputActionRef, InputConsume, InputMapping, VirtualAction,
16            VirtualKeyCode,
17        },
18        vek::{Rgba, Vec2},
19    },
20};
21use std::error::Error;
22
23// Example demonstrating basic usage of garbage-collected objects.
24// It's important to note that `Gc` type is not a typical garbage-collected
25// pointer. Think of it as GC with ownership flavor - newly created `Gc`
26// instance owns the object memory and all other instances created via
27// `reference` method are just weak references. That means, when owning `Gc`
28// instance is dropped, the object memory is freed, and all other weak
29// references become invalid. This approach allows to have cyclic references
30// as well as self-references between `Gc` objects without memory leaks.
31fn main() -> Result<(), Box<dyn Error>> {
32    GameLauncher::new(GameInstance::new(Preloader).setup_assets(|assets| {
33        *assets = make_directory_database("./resources/").unwrap();
34    }))
35    .title("GC")
36    .config(Config::load_from_file("./resources/GameConfig.toml")?)
37    .run();
38    Ok(())
39}
40
41#[derive(Default)]
42struct Preloader;
43
44impl GameState for Preloader {
45    fn enter(&mut self, context: GameContext) {
46        context.graphics.state.color = [0.2, 0.2, 0.2, 1.0];
47        context.graphics.state.main_camera.screen_alignment = 0.5.into();
48        context.graphics.state.main_camera.scaling = CameraScaling::FitVertical(500.0);
49
50        context
51            .assets
52            .spawn(
53                "shader://color",
54                (ShaderAsset::new(
55                    Shader::COLORED_VERTEX_2D,
56                    Shader::PASS_FRAGMENT,
57                ),),
58            )
59            .unwrap();
60        context
61            .assets
62            .spawn(
63                "shader://image",
64                (ShaderAsset::new(
65                    Shader::TEXTURED_VERTEX_2D,
66                    Shader::TEXTURED_FRAGMENT,
67                ),),
68            )
69            .unwrap();
70        context
71            .assets
72            .spawn(
73                "shader://text",
74                (ShaderAsset::new(Shader::TEXT_VERTEX, Shader::TEXT_FRAGMENT),),
75            )
76            .unwrap();
77    }
78
79    fn update(&mut self, context: GameContext, _: f32) {
80        if !context.assets.is_busy() {
81            *context.state_change = GameStateChange::Swap(Box::new(State::default()));
82        }
83    }
84}
85
86#[derive(Default)]
87struct State {
88    // Player controller is owned by the state.
89    player_controller: Option<Gc<PlayerController>>,
90    // Actors are also owned by the state.
91    actors: Vec<Gc<Actor>>,
92    switch: InputActionRef,
93    exit: InputActionRef,
94    spawn: InputActionRef,
95    destroy: InputActionRef,
96}
97
98impl GameState for State {
99    fn enter(&mut self, context: GameContext) {
100        let move_left = InputActionRef::default();
101        let move_right = InputActionRef::default();
102        let move_up = InputActionRef::default();
103        let move_down = InputActionRef::default();
104
105        context.input.push_mapping(
106            InputMapping::default()
107                .consume(InputConsume::Hit)
108                .action(
109                    VirtualAction::KeyButton(VirtualKeyCode::A),
110                    move_left.clone(),
111                )
112                .action(
113                    VirtualAction::KeyButton(VirtualKeyCode::D),
114                    move_right.clone(),
115                )
116                .action(VirtualAction::KeyButton(VirtualKeyCode::W), move_up.clone())
117                .action(
118                    VirtualAction::KeyButton(VirtualKeyCode::S),
119                    move_down.clone(),
120                )
121                .action(
122                    VirtualAction::KeyButton(VirtualKeyCode::Left),
123                    move_left.clone(),
124                )
125                .action(
126                    VirtualAction::KeyButton(VirtualKeyCode::Right),
127                    move_right.clone(),
128                )
129                .action(
130                    VirtualAction::KeyButton(VirtualKeyCode::Up),
131                    move_up.clone(),
132                )
133                .action(
134                    VirtualAction::KeyButton(VirtualKeyCode::Down),
135                    move_down.clone(),
136                )
137                .action(
138                    VirtualAction::KeyButton(VirtualKeyCode::Space),
139                    self.switch.clone(),
140                )
141                .action(
142                    VirtualAction::KeyButton(VirtualKeyCode::Insert),
143                    self.spawn.clone(),
144                )
145                .action(
146                    VirtualAction::KeyButton(VirtualKeyCode::Delete),
147                    self.destroy.clone(),
148                )
149                .action(
150                    VirtualAction::KeyButton(VirtualKeyCode::Escape),
151                    self.exit.clone(),
152                ),
153        );
154
155        // Create the player controller pointer owned by the state.
156        self.player_controller = Some(Gc::new(PlayerController {
157            movement_input: CardinalInputCombinator::new(move_left, move_right, move_up, move_down),
158            controls: None,
159        }));
160
161        self.spawn_actor(Vec2::new(-100.0, 0.0), 150.0);
162        self.spawn_actor(Vec2::new(100.0, 0.0), 100.0);
163    }
164
165    fn exit(&mut self, context: GameContext) {
166        context.input.pop_mapping();
167    }
168
169    fn fixed_update(&mut self, mut context: GameContext, delta_time: f32) {
170        if let Some(controller) = &mut self.player_controller {
171            // Process the player controller using writable access to GC pointer.
172            controller.write().process(&mut context, delta_time);
173        }
174
175        for actor in &mut self.actors {
176            actor.write().process(&mut context, delta_time);
177        }
178
179        if self.switch.get().is_pressed() {
180            self.switch_to_next();
181        } else if self.spawn.get().is_pressed() {
182            self.spawn_actor(Default::default(), 100.0);
183        } else if self.destroy.get().is_pressed() {
184            self.destroy_current();
185        }
186
187        if self.exit.get().is_pressed() {
188            *context.state_change = GameStateChange::Pop;
189        }
190    }
191
192    fn draw(&mut self, mut context: GameContext) {
193        for actor in &mut self.actors {
194            actor.write().draw(&mut context);
195        }
196
197        if let Some(controller) = &mut self.player_controller {
198            controller.write().draw(&mut context);
199        }
200    }
201}
202
203impl State {
204    fn switch_to_next(&mut self) {
205        if !self.actors.is_empty()
206            && let Some(controller) = &mut self.player_controller
207        {
208            let controller = &mut *controller.write();
209            if let Some(current) = &controller.controls {
210                // We can tell if two GC pointers are pointing to same object.
211                if let Some(index) = self.actors.iter().position(|a| Gc::ptr_eq(a, current)) {
212                    let next = self.actors[(index + 1) % self.actors.len()].reference();
213                    controller.controls = Some(next);
214                } else {
215                    controller.controls = Some(self.actors[0].reference());
216                }
217            }
218        }
219    }
220
221    fn spawn_actor(&mut self, position: Vec2<f32>, speed: f32) {
222        let actor = Gc::new(Actor {
223            sprite: Sprite::default()
224                .shader(ShaderRef::name("color"))
225                .position(position)
226                .size(50.0.into())
227                .pivot(0.5.into())
228                .tint(Rgba::red()),
229            speed,
230        });
231        if let Some(controller) = &mut self.player_controller {
232            controller.write().controls = Some(actor.reference());
233        }
234        self.actors.push(actor);
235    }
236
237    fn destroy_current(&mut self) {
238        let switch = if let Some(controller) = &mut self.player_controller {
239            let controller = &mut *controller.write();
240            if let Some(current) = &controller.controls {
241                // Remove the current actor from the list, dropping the owning
242                // GC pointer, which will free the object memory and invalidate
243                // all other weak references pointing to it.
244                self.actors.retain(|actor| !Gc::ptr_eq(actor, current));
245                true
246            } else {
247                false
248            }
249        } else {
250            false
251        };
252        if switch {
253            self.switch_to_next();
254        }
255    }
256}
257
258struct Actor {
259    sprite: Sprite,
260    speed: f32,
261}
262
263impl GameObject for Actor {
264    fn draw(&mut self, context: &mut GameContext) {
265        self.sprite.draw(context.draw, context.graphics);
266    }
267}
268
269struct PlayerController {
270    pub movement_input: CardinalInputCombinator,
271    pub controls: Option<Gc<Actor>>,
272}
273
274impl GameObject for PlayerController {
275    fn process(&mut self, _: &mut GameContext, delta_time: f32) {
276        if let Some(actor) = &mut self.controls {
277            let mut actor = actor.write();
278            let movement = Vec2::from(self.movement_input.get()) * actor.speed * delta_time;
279            actor.sprite.transform.position += movement;
280        }
281    }
282
283    fn draw(&mut self, context: &mut GameContext) {
284        if let Some(actor) = &mut self.controls {
285            Sprite::default()
286                .shader(ShaderRef::name("color"))
287                .position(actor.read().sprite.transform.position.into())
288                .size(10.0.into())
289                .pivot(0.5.into())
290                .tint(Rgba::blue())
291                .draw(context.draw, context.graphics);
292        }
293    }
294}