Skip to main content

engine/
context.rs

1/**----------------------------------------------------
2*!  Game context providing access to engine systems.
3*----------------------------------------------------**/
4use crate::audio::{AudioManager, UiAudioEvent};
5use crate::input::{GameAction, InputState};
6use crate::sprite::{BlendMode, Rect, Sprite};
7use glam::Vec2;
8
9//? This struct is passed to `GameApp` methods and provides:
10//? - Input state (keyboard, mouse, gamepad)
11//? - Delta time for frame-rate independent updates
12//? - Sprite drawing API
13//? - Screen dimensions
14//? Interpolation alpha for render-time smoothing between physics frames.
15//? - Sprite batch (current frame, internal use, cleared each frame)
16pub struct Context<A: GameAction> {
17    pub input: InputState<A>,
18    pub delta_time: f32,
19    pub screen_width: f32,
20    pub screen_height: f32,
21    pub camera_offset_x: f32,
22    pub camera_offset_y: f32,
23    pub fps: f32,
24    pub frame_time_ms: f32,
25    pub fixed_tick_rate: u32,
26    pub target_fps: u32,
27    pub interpolation_alpha: f32,
28    pub freeze_frames: u16,
29    pub pending_shakes: Vec<(f32, f32)>,
30    pub request_exit: bool,
31    pub fullscreen_enabled: bool,
32    pub request_fullscreen: Option<bool>,
33    pub hdr_enabled: bool,
34    pub request_hdr: Option<bool>,
35    pub audio: AudioManager,
36    pub pending_ui_audio: Vec<UiAudioEvent>,
37
38    //* pub(crate) - public only within the current crate
39    //* Vec<T> - growable, heap-allocated array
40    pub(crate) sprite_batch: Vec<Sprite>,
41    pub(crate) pending_textures: Vec<PendingTexture>,
42}
43
44//? A texture load request queued by the game during init.
45pub(crate) struct PendingTexture {
46    pub bytes: &'static [u8],
47    pub label: String,
48}
49
50impl<A: GameAction> Context<A> {
51    //? Create a new context with given screen dimensions
52    //? and default values for other fields.
53    pub fn new(screen_width: f32, screen_height: f32) -> Self {
54        Self {
55            input: InputState::new(),
56            delta_time: 0.0,
57            screen_width,
58            screen_height,
59            sprite_batch: Vec::new(),
60            camera_offset_x: 0.0,
61            camera_offset_y: 0.0,
62            fps: 0.0,
63            frame_time_ms: 0.0,
64            fixed_tick_rate: crate::time::DEFAULT_FIXED_HZ,
65            target_fps: 60, //* Default to 60 FPS target. can be changed by the game
66            interpolation_alpha: 0.0,
67            freeze_frames: 0,
68            pending_shakes: Vec::new(),
69            pending_textures: Vec::new(),
70            request_exit: false,
71            fullscreen_enabled: true,
72            request_fullscreen: None,
73            hdr_enabled: false,
74            request_hdr: None,
75            audio: AudioManager::new(),
76            pending_ui_audio: Vec::new(),
77        }
78    }
79
80    //? Queue a texture for loading. Called during `GameApp::init()`.
81    //* Returns a 1-based texture ID for use with `draw_sprite_from_sheet()`.
82    //* The engine processes these after init completes.
83    pub fn load_texture(&mut self, bytes: &'static [u8], label: &str) -> usize {
84        let id = self.pending_textures.len() + 1; //*1-based (0 = white pixel)
85        self.pending_textures.push(PendingTexture {
86            bytes,
87            label: label.to_string(),
88        });
89        id
90    }
91
92    //? Trigger frame-perfect freeze for N render frames. FSM and physics pause.
93    pub fn trigger_freeze(&mut self, frames: u16) {
94        self.freeze_frames = self.freeze_frames.max(frames);
95    }
96
97    //? Trigger a screen shake effect with given intensity and duration (in seconds).
98    pub fn trigger_shake(&mut self, intensity: f32, duration: f32) {
99        self.pending_shakes.push((intensity, duration));
100    }
101
102    //? Draw a sprite (colored rectangle or textured).
103    //? # Arguments:
104    //? - position: Top-left corner in screen space (0,0)
105    //? - size: Width and height in pixels
106    //? - color: RGBA color (each component 0.0 to 1.0)
107    //? - flip_x: Horizontally flip the sprite
108    pub fn draw_sprite(&mut self, position: Vec2, size: Vec2, color: [f32; 4], flip_x: bool) {
109        self.sprite_batch
110            .push(Sprite::new(position, size, color).with_flip(flip_x));
111        //* push to the array just like in C++ std::vector or python list.
112    }
113
114    //* Same as draw_sprite, without flip
115    pub fn draw_rect(&mut self, position: Vec2, size: Vec2, color: [f32; 4]) {
116        self.sprite_batch.push(Sprite::new(position, size, color));
117    }
118
119    //? Draw a sprite from a sprite sheet.
120    //? # Arguments:
121    //? - position: Top-left corner in screen space (0,0)
122    //? - size: Width and height in pixels
123    //? - color: RGBA color (each component 0.0 to 1.0)
124    //? - source_rect: Rectangle defining the sprite sheet region (pixel coordinates)
125    //? - flip_x: Horizontally flip the sprite
126    //? - texture_id: Which texture to use (1+ for game textures, 0 for white pixel)
127    pub fn draw_sprite_from_sheet(
128        &mut self,
129        position: Vec2,
130        size: Vec2,
131        color: [f32; 4],
132        source_rect: Rect,
133        flip_x: bool,
134        texture_id: usize,
135    ) {
136        self.sprite_batch.push(
137            Sprite::new(position, size, color)
138                .with_source(source_rect)
139                .with_flip(flip_x)
140                .with_texture_id(texture_id),
141        );
142    }
143
144    //? Draw a sprite from a sprite sheet with additive blending.
145    pub fn draw_sprite_from_sheet_additive(
146        &mut self,
147        position: Vec2,
148        size: Vec2,
149        color: [f32; 4],
150        source_rect: Rect,
151        flip_x: bool,
152        texture_id: usize,
153    ) {
154        self.sprite_batch.push(
155            Sprite::new(position, size, color)
156                .with_source(source_rect)
157                .with_flip(flip_x)
158                .with_texture_id(texture_id)
159                .with_blend_mode(BlendMode::Additive),
160        );
161    }
162
163    pub fn screen_center(&self) -> Vec2 {
164        Vec2::new(self.screen_width / 2.0, self.screen_height / 2.0)
165    }
166
167    //? Deduplicate UI audio events queued during this frame.
168    pub(crate) fn drain_ui_audio_events(&mut self) {
169        if self.pending_ui_audio.is_empty() {
170            return;
171        }
172        self.pending_ui_audio.sort_unstable();
173        self.pending_ui_audio.dedup();
174    }
175
176    //? Clear sprite batch (called internally between frames).
177    //* Method is public within the crate, not outside.
178    pub(crate) fn clear_sprites(&mut self) {
179        self.sprite_batch.clear();
180    }
181
182    //? Update screen dimensions on window resize.
183    #[allow(dead_code)] //* needed if dynamic resolution or windowed mode is added
184    pub(crate) fn resize(&mut self, width: f32, height: f32) {
185        self.screen_width = width;
186        self.screen_height = height;
187    }
188
189    pub fn set_fullscreen_enabled(&mut self, enabled: bool) {
190        self.request_fullscreen = Some(enabled);
191    }
192
193    pub fn set_hdr_enabled(&mut self, enabled: bool) {
194        self.request_hdr = Some(enabled);
195    }
196}