use std::path::Path;
use std::sync::Arc;
use anyhow::Result;
use game_toolkit_audio::SoundId;
use game_toolkit_gfx::TextureId;
use winit::event_loop::ActiveEventLoop;
use winit::window::Window;
use crate::Rng;
use crate::app::AppConfig;
use crate::time::Time;
pub enum GameEvent {
Resized { width: u32, height: u32 },
FocusChanged(bool),
CloseRequested,
}
pub struct Context {
pub gfx: game_toolkit_gfx::Graphics,
pub audio: Option<game_toolkit_audio::Audio>,
pub input: game_toolkit_input::Input,
pub assets: game_toolkit_assets::Assets,
pub time: Time,
pub rng: Rng,
pub window: Arc<Window>,
pub(crate) quit_requested: bool,
}
pub(crate) struct Parts {
gfx: game_toolkit_gfx::Graphics,
assets: game_toolkit_assets::Assets,
}
pub(crate) fn create_window(
event_loop: &ActiveEventLoop,
config: &AppConfig,
) -> Result<Arc<Window>> {
let attrs = Window::default_attributes()
.with_title(&config.title)
.with_inner_size(winit::dpi::LogicalSize::new(config.width, config.height));
#[cfg(target_arch = "wasm32")]
let attrs = crate::web::canvas_attributes(attrs, config.canvas_id.as_deref())?;
Ok(Arc::new(event_loop.create_window(attrs)?))
}
pub(crate) async fn init_parts(window: Arc<Window>, config: AppConfig) -> Result<Parts> {
let gfx = game_toolkit_gfx::Graphics::new(
window,
config.vsync,
config.depth_format,
config.msaa_samples,
)
.await?;
let assets = open_assets(&config).await?;
Ok(Parts { gfx, assets })
}
#[cfg(not(target_arch = "wasm32"))]
#[allow(clippy::unused_async)]
async fn open_assets(config: &AppConfig) -> Result<game_toolkit_assets::Assets> {
use game_toolkit_assets::Assets;
Assets::new(&config.asset_root).or_else(|e| {
log::warn!(
"asset root {} unusable ({e}); falling back to cwd",
config.asset_root.display()
);
Assets::new(".")
})
}
#[cfg(target_arch = "wasm32")]
async fn open_assets(config: &AppConfig) -> Result<game_toolkit_assets::Assets> {
let mut assets = game_toolkit_assets::Assets::new(&config.asset_root)?;
let paths = match &config.web_preload {
Some(paths) => paths.clone(),
None => assets.fetch_manifest().await?,
};
log::info!("preloading {} asset(s)", paths.len());
assets.preload(paths).await?;
Ok(assets)
}
impl Context {
pub(crate) fn from_parts(window: Arc<Window>, parts: Parts, config: &AppConfig) -> Self {
let audio = match game_toolkit_audio::Audio::new() {
Ok(a) => Some(a),
Err(e) => {
log::warn!("audio init failed, continuing muted: {e}");
None
}
};
Self {
gfx: parts.gfx,
audio,
input: game_toolkit_input::Input::new(),
assets: parts.assets,
time: Time::new(),
rng: Rng::new(config.random_seed),
window,
quit_requested: false,
}
}
pub fn quit(&mut self) {
self.quit_requested = true;
}
pub fn load_texture(&mut self, rel: impl AsRef<Path>) -> Result<TextureId> {
let rel = rel.as_ref();
#[cfg(not(target_arch = "wasm32"))]
if !self.assets.is_loaded(rel) {
return self.gfx.load_texture(self.assets.resolve(rel));
}
let bytes = self.assets.read(rel)?;
self.gfx
.load_texture_bytes(&bytes, Some(&rel.to_string_lossy()))
}
pub fn load_sound(&mut self, rel: impl AsRef<Path>) -> Result<Option<SoundId>> {
let Some(audio) = self.audio.as_mut() else {
return Ok(None);
};
let bytes = self.assets.read(rel)?;
audio.load_sound_bytes(bytes).map(Some)
}
pub(crate) fn start_audio(&mut self) {
if let Some(audio) = self.audio.as_mut()
&& !audio.is_started()
&& let Err(e) = audio.start()
{
log::warn!("audio start failed, continuing muted: {e}");
self.audio = None;
}
}
}