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 let mut assets = 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 if let Some(paths) = &config.web_preload {
79 assets.set_web_preload(paths);
80 }
81 Ok(assets)
82}
83
84#[cfg(target_arch = "wasm32")]
85async fn open_assets(config: &AppConfig) -> Result<game_toolkit_assets::Assets> {
86 let mut assets = game_toolkit_assets::Assets::new(&config.asset_root)?;
87 let paths = match &config.web_preload {
88 Some(paths) => paths.clone(),
89 None => assets.fetch_manifest().await?,
90 };
91 log::info!("preloading {} asset(s)", paths.len());
92 assets.preload(paths).await?;
93 Ok(assets)
94}
95
96impl Context {
97 pub(crate) fn from_parts(window: Arc<Window>, parts: Parts, config: &AppConfig) -> Self {
98 let audio = match game_toolkit_audio::Audio::new() {
99 Ok(a) => Some(a),
100 Err(e) => {
101 log::warn!("audio init failed, continuing muted: {e}");
102 None
103 }
104 };
105 let mut input = game_toolkit_input::Input::new();
106 input.set_touch_emulates_mouse(config.touch_emulates_mouse);
107 Self {
108 gfx: parts.gfx,
109 audio,
110 input,
111 assets: parts.assets,
112 time: Time::new(),
113 rng: Rng::new(config.random_seed),
114 window,
115 quit_requested: false,
116 }
117 }
118
119 pub fn quit(&mut self) {
120 self.quit_requested = true;
121 }
122
123 pub fn load_texture(&mut self, rel: impl AsRef<Path>) -> Result<TextureId> {
127 let rel = rel.as_ref();
128 #[cfg(not(target_arch = "wasm32"))]
129 if !self.assets.is_loaded(rel) {
130 let id = self.gfx.load_texture(self.assets.resolve(rel))?;
131 self.assets.check_web_listed(rel);
132 return Ok(id);
133 }
134 let bytes = self.assets.read(rel)?;
135 self.gfx
136 .load_texture_bytes(&bytes, Some(&rel.to_string_lossy()))
137 }
138
139 pub fn load_sound(&mut self, rel: impl AsRef<Path>) -> Result<Option<SoundId>> {
142 let Some(audio) = self.audio.as_mut() else {
143 return Ok(None);
144 };
145 let bytes = self.assets.read(rel)?;
146 audio.load_sound_bytes(bytes).map(Some)
147 }
148
149 pub(crate) fn start_audio(&mut self) {
152 if let Some(audio) = self.audio.as_mut()
153 && !audio.is_started()
154 && let Err(e) = audio.start()
155 {
156 log::warn!("audio start failed, continuing muted: {e}");
157 self.audio = None;
158 }
159 }
160}