1use std::collections::HashMap;
5use std::time::Instant;
6
7use crate::assets::Assets;
8use crate::audio::AudioOut;
9use crate::angle::Angle;
10use crate::gfx::Gfx;
11use crate::host::{Game, Host, LoadOptions, Stop};
12use crate::net::Net;
13use crate::storage::Storage;
14
15pub struct Session {
16 pub name: String,
18 pub wasm: Vec<u8>,
20 pub assets: Assets,
21 pub params: HashMap<String, String>,
22 pub allow_net: bool,
23 pub allow_hosts: Vec<String>,
25 pub fetch: crate::fetch::FetchMode,
27 pub app_id: Option<String>,
29 pub save: crate::files::SaveTarget,
31 pub consent: Option<crate::consent::Consent>,
33 pub manifest: Option<crate::manifest::Manifest>,
35 pub mod_requests: Vec<(String, Vec<String>)>,
38 pub storage: Storage,
39 pub load: LoadOptions,
40 pub gl_lib: Option<std::path::PathBuf>,
42 pub gl_software: bool,
44 pub gl_stats: bool,
46 pub profile: Option<std::path::PathBuf>,
48 pub media: crate::media::MediaPolicy,
51 pub fullscreen: Option<bool>,
54 pub watch_assets: Vec<crate::assets::AssetWatch>,
57}
58
59impl Session {
60 pub fn uses_gl(&self) -> bool {
62 crate::host::imports_module(&self.wasm, "gasm:gl", self.load.allow_precompiled)
63 }
64
65 pub fn open_gl(&self, window: Option<crate::angle::NativeWindow>, size: (u32, u32)) -> Result<Angle, String> {
67 let dir = crate::angle::find(self.gl_lib.as_deref())?;
68 let open = |software| match window {
69 Some(w) => Angle::window(&dir, w, software),
70 None => Angle::offscreen(&dir, size.0, size.1, software),
71 };
72 match open(self.gl_software) {
73 Err(e) if !self.gl_software => {
75 eprintln!("[gasm] gl: {e}; trying SwiftShader (software)");
76 open(true)
77 }
78 r => r,
79 }
80 }
81
82 pub fn compile_in_background(&self) -> std::thread::JoinHandle<Result<wasmtime::Module, Stop>> {
88 let (wasm, allow) = (self.wasm.clone(), self.load.allow_precompiled);
89 std::thread::spawn(move || Game::compile(&wasm, allow))
90 }
91
92 pub fn start(self, audio: Option<Box<dyn AudioOut>>, gfx: Gfx, gl: Option<Angle>, reproducible: bool, hashing: bool) -> Result<Game, Stop> {
93 self.start_with(None, audio, gfx, gl, reproducible, hashing)
94 }
95
96 fn decide_mods(&mut self, policy: &crate::net::NetPolicy) {
99 for (file, hosts) in std::mem::take(&mut self.mod_requests) {
100 let covered = policy.allowed && hosts.iter().all(|h| policy.permits(h));
101 let said = || self.consent.as_ref().and_then(|c| c.lock().unwrap().check_mod(&file, &hosts));
102 if covered || said() == Some(true) {
103 eprintln!("[gasm] mods: {file} may connect to {}", hosts.join(", "));
104 continue;
105 }
106 let why = match &self.consent {
107 Some(_) => format!("it connects to {} and the player said no", hosts.join(", ")),
108 None => format!("it connects to {}, which --allow-net doesn't cover", hosts.join(", ")),
109 };
110 eprintln!("[gasm] mods: refused {file} ({why})");
111 self.assets.refuse_mod(&file, &why);
112 }
113 }
114
115 pub fn start_compiled(self, module: wasmtime::Module, audio: Option<Box<dyn AudioOut>>, gfx: Gfx, gl: Option<Angle>) -> Result<Game, Stop> {
117 self.start_with(Some(module), audio, gfx, gl, false, false)
118 }
119
120 fn start_with(mut self, module: Option<wasmtime::Module>, audio: Option<Box<dyn AudioOut>>, gfx: Gfx, gl: Option<Angle>, reproducible: bool, hashing: bool) -> Result<Game, Stop> {
121 let policy = crate::net::NetPolicy::new(self.allow_net, self.allow_hosts.clone()).with_consent(self.consent.clone());
122 self.decide_mods(&policy);
123 let bus = audio.as_ref().and_then(|a| a.media_bus());
125 let mut media = self.media.clone();
126 media.declared = self.manifest.as_ref().is_some_and(|m| m.media);
127 media.consent = self.consent.clone();
128 let mut host = Host::new(self.assets, self.params, audio, gfx, Net::with_policy(policy.clone()), self.storage);
129 host.media = crate::media::Media::new(media);
130 host.media.set_output(bus);
131 host.requires = self.manifest.map(|m| m.requires).unwrap_or_default();
132 host.fetch = crate::fetch::Fetch::new(policy, self.fetch);
133 host.fetch.set_app_id(self.app_id.as_deref());
134 host.files = crate::files::Files::new(self.save).with_consent(self.consent.clone());
135 if self.gl_stats {
136 host.gl.stats = Some(Default::default());
137 }
138 if let Some(out) = self.profile.clone() {
139 host.profile = Some(crate::host::Profile::new(out));
140 }
141 if let Some(on) = self.fullscreen {
142 host.fullscreen_capable = true;
143 host.fullscreen = on;
144 }
145 if let Some(a) = gl {
146 eprintln!("[gasm] gl: {}", a.renderer);
147 host.gl.attach(a);
148 }
149 if reproducible {
150 host.set_reproducible();
151 }
152 host.hashing = hashing;
153 let t0 = Instant::now();
154 let module = match module {
155 Some(m) => m,
156 None => Game::compile(&self.wasm, self.load.allow_precompiled)?,
157 };
158 let mut game = Game::load_module(module, host, self.load)?;
159 for w in self.watch_assets {
160 game.add_watch(w);
161 }
162 eprintln!("[gasm] loaded {} in {:.0} ms", self.name, t0.elapsed().as_secs_f64() * 1000.0);
163 Ok(game)
164 }
165}