Skip to main content

goud_engine/ffi/window/
state.rs

1//! # Window State
2//!
3//! Defines [`WindowState`], which composes a [`GlfwPlatform`] and an
4//! [`OpenGLBackend`] into a single per-context object, and the thread-local
5//! storage helpers used to access it from FFI functions.
6
7use crate::core::error::GoudError;
8use crate::ecs::InputManager;
9use crate::ffi::context::GoudContextId;
10use crate::libs::graphics::backend::opengl::OpenGLBackend;
11use crate::libs::graphics::backend::StateOps;
12use crate::libs::platform::glfw_platform::GlfwPlatform;
13use crate::libs::platform::PlatformBackend;
14use crate::sdk::debug_overlay::DebugOverlay;
15use std::cell::RefCell;
16
17// ============================================================================
18// Window State
19// ============================================================================
20
21/// Window state attached to a context.
22///
23/// Composes a [`GlfwPlatform`] (windowing + input) with an [`OpenGLBackend`]
24/// (rendering). The platform backend owns the window handle and event loop;
25/// the render backend is stored alongside it.
26pub struct WindowState {
27    pub(crate) platform: GlfwPlatform,
28
29    /// OpenGL rendering backend
30    pub(crate) backend: OpenGLBackend,
31
32    /// Delta time from last frame
33    delta_time: f32,
34
35    /// Debug overlay for FPS stats tracking.
36    pub(crate) debug_overlay: DebugOverlay,
37}
38
39impl WindowState {
40    /// Creates a new [`WindowState`] from the given platform and backend.
41    pub fn new(platform: GlfwPlatform, backend: OpenGLBackend) -> Self {
42        Self {
43            platform,
44            backend,
45            delta_time: 0.0,
46            debug_overlay: DebugOverlay::new(0.5),
47        }
48    }
49
50    /// Returns true if the window should close.
51    pub fn should_close(&self) -> bool {
52        self.platform.should_close()
53    }
54
55    /// Sets whether the window should close.
56    pub fn set_should_close(&mut self, should_close: bool) {
57        self.platform.set_should_close(should_close);
58    }
59
60    /// Polls events, updates input state, and syncs the viewport on resize.
61    pub fn poll_events(&mut self, input: &mut InputManager) -> f32 {
62        let old_size = self.platform.get_size();
63        self.delta_time = self.platform.poll_events(input);
64        let new_size = self.platform.get_size();
65
66        if old_size != new_size {
67            self.backend.set_viewport(0, 0, new_size.0, new_size.1);
68        }
69
70        self.debug_overlay.update(self.delta_time);
71
72        self.delta_time
73    }
74
75    /// Swaps the front and back buffers.
76    pub fn swap_buffers(&mut self) {
77        self.platform.swap_buffers();
78    }
79
80    /// Gets window size (logical).
81    pub fn get_size(&self) -> (u32, u32) {
82        self.platform.get_size()
83    }
84
85    /// Gets framebuffer size (physical - may differ on HiDPI/Retina displays).
86    pub fn get_framebuffer_size(&self) -> (u32, u32) {
87        self.platform.get_framebuffer_size()
88    }
89
90    /// Gets a mutable reference to the backend.
91    pub fn backend_mut(&mut self) -> &mut OpenGLBackend {
92        &mut self.backend
93    }
94
95    /// Gets the delta time.
96    pub fn delta_time(&self) -> f32 {
97        self.delta_time
98    }
99}
100
101// ============================================================================
102// Window State Storage (Thread-Local)
103// ============================================================================
104
105thread_local! {
106    pub(super) static WINDOW_STATES: RefCell<Vec<Option<WindowState>>> = const { RefCell::new(Vec::new()) };
107}
108
109/// Stores the given [`WindowState`] for the specified context.
110pub fn set_window_state(context_id: GoudContextId, state: WindowState) -> Result<(), GoudError> {
111    WINDOW_STATES.with(|cell| {
112        let mut states = cell.borrow_mut();
113        let index = context_id.index() as usize;
114
115        while states.len() <= index {
116            states.push(None);
117        }
118
119        states[index] = Some(state);
120        Ok(())
121    })
122}
123
124/// Removes the [`WindowState`] associated with the specified context.
125pub fn remove_window_state(context_id: GoudContextId) {
126    WINDOW_STATES.with(|cell| {
127        let mut states = cell.borrow_mut();
128        let index = context_id.index() as usize;
129        if index < states.len() {
130            states[index] = None;
131        }
132    });
133}
134
135/// Provides access to window state for a given context.
136///
137/// # Safety
138///
139/// The closure must not store references to the WindowState beyond the call.
140pub fn with_window_state<F, R>(context_id: GoudContextId, f: F) -> Option<R>
141where
142    F: FnOnce(&mut WindowState) -> R,
143{
144    WINDOW_STATES.with(|cell| {
145        let mut states = cell.borrow_mut();
146        let index = context_id.index() as usize;
147        states.get_mut(index).and_then(|opt| opt.as_mut()).map(f)
148    })
149}