game_toolkit_core/
context.rs1use std::path::Path;
2use std::sync::Arc;
3
4use anyhow::Result;
5use game_toolkit_audio::SoundId;
6use game_toolkit_gfx::TextureId;
7use winit::event_loop::ActiveEventLoop;
8use winit::window::Window;
9
10use crate::Rng;
11use crate::app::AppConfig;
12use crate::time::Time;
13
14pub enum GameEvent {
15 Resized { width: u32, height: u32 },
16 FocusChanged(bool),
17 CloseRequested,
18}
19
20pub struct Context {
21 pub gfx: game_toolkit_gfx::Graphics,
22 pub audio: Option<game_toolkit_audio::Audio>,
23 pub input: game_toolkit_input::Input,
24 pub assets: game_toolkit_assets::Assets,
25 pub time: Time,
26 pub rng: Rng,
27 pub window: Arc<Window>,
28 pub(crate) quit_requested: bool,
29}
30
31pub(crate) struct Parts {
34 gfx: game_toolkit_gfx::Graphics,
35 assets: game_toolkit_assets::Assets,
36}
37
38pub(crate) fn create_window(
41 event_loop: &ActiveEventLoop,
42 config: &AppConfig,
43) -> Result<Arc<Window>> {
44 let attrs = Window::default_attributes()
45 .with_title(&config.title)
46 .with_inner_size(winit::dpi::LogicalSize::new(config.width, config.height));
47 #[cfg(target_arch = "wasm32")]
48 let attrs = crate::web::canvas_attributes(attrs, config.canvas_id.as_deref())?;
49 Ok(Arc::new(event_loop.create_window(attrs)?))
50}
51
52pub(crate) async fn init_parts(window: Arc<Window>, config: AppConfig) -> Result<Parts> {
55 let gfx = game_toolkit_gfx::Graphics::new(
56 window,
57 config.vsync,
58 config.depth_format,
59 config.msaa_samples,
60 )
61 .await?;
62 let assets = open_assets(&config).await?;
63 Ok(Parts { gfx, assets })
64}
65
66#[cfg(not(target_arch = "wasm32"))]
67#[allow(clippy::unused_async)]
68async fn open_assets(config: &AppConfig) -> Result<game_toolkit_assets::Assets> {
69 use game_toolkit_assets::Assets;
70 Assets::new(&config.asset_root).or_else(|e| {
71 log::warn!(
72 "asset root {} unusable ({e}); falling back to cwd",
73 config.asset_root.display()
74 );
75 Assets::new(".")
76 })
77}
78
79#[cfg(target_arch = "wasm32")]
80async fn open_assets(config: &AppConfig) -> Result<game_toolkit_assets::Assets> {
81 let mut assets = game_toolkit_assets::Assets::new(&config.asset_root)?;
82 let paths = match &config.web_preload {
83 Some(paths) => paths.clone(),
84 None => assets.fetch_manifest().await?,
85 };
86 log::info!("preloading {} asset(s)", paths.len());
87 assets.preload(paths).await?;
88 Ok(assets)
89}
90
91impl Context {
92 pub(crate) fn from_parts(window: Arc<Window>, parts: Parts, config: &AppConfig) -> Self {
93 let audio = match game_toolkit_audio::Audio::new() {
94 Ok(a) => Some(a),
95 Err(e) => {
96 log::warn!("audio init failed, continuing muted: {e}");
97 None
98 }
99 };
100 Self {
101 gfx: parts.gfx,
102 audio,
103 input: game_toolkit_input::Input::new(),
104 assets: parts.assets,
105 time: Time::new(),
106 rng: Rng::new(config.random_seed),
107 window,
108 quit_requested: false,
109 }
110 }
111
112 pub fn quit(&mut self) {
113 self.quit_requested = true;
114 }
115
116 pub fn load_texture(&mut self, rel: impl AsRef<Path>) -> Result<TextureId> {
120 let rel = rel.as_ref();
121 #[cfg(not(target_arch = "wasm32"))]
122 if !self.assets.is_loaded(rel) {
123 return self.gfx.load_texture(self.assets.resolve(rel));
124 }
125 let bytes = self.assets.read(rel)?;
126 self.gfx
127 .load_texture_bytes(&bytes, Some(&rel.to_string_lossy()))
128 }
129
130 pub fn load_sound(&mut self, rel: impl AsRef<Path>) -> Result<Option<SoundId>> {
133 let Some(audio) = self.audio.as_mut() else {
134 return Ok(None);
135 };
136 let bytes = self.assets.read(rel)?;
137 audio.load_sound_bytes(bytes).map(Some)
138 }
139
140 pub(crate) fn start_audio(&mut self) {
143 if let Some(audio) = self.audio.as_mut()
144 && !audio.is_started()
145 && let Err(e) = audio.start()
146 {
147 log::warn!("audio start failed, continuing muted: {e}");
148 self.audio = None;
149 }
150 }
151}