Add the possibility to late initialize the renderer

This commit is contained in:
Maximilian Ammann 2022-11-02 20:44:35 +01:00
parent d0cefe69de
commit fdd09e0b19
10 changed files with 233 additions and 164 deletions

View File

@ -18,7 +18,7 @@ use maplibre::{
kernel::{Kernel, KernelBuilder}, kernel::{Kernel, KernelBuilder},
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler}, platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
render::{ render::{
builder::{InitializedRenderer, RenderBuilder}, builder::{InitializedRenderer, RendererBuilder},
settings::{RendererSettings, TextureFormat}, settings::{RendererSettings, TextureFormat},
}, },
style::Style, style::Style,

View File

@ -6,7 +6,7 @@ use maplibre::{
kernel::KernelBuilder, kernel::KernelBuilder,
platform::{http_client::ReqwestHttpClient, scheduler::TokioScheduler}, platform::{http_client::ReqwestHttpClient, scheduler::TokioScheduler},
render::{ render::{
builder::RenderBuilder, builder::RendererBuilder,
settings::{RendererSettings, TextureFormat}, settings::{RendererSettings, TextureFormat},
}, },
style::Style, style::Style,

View File

@ -1,8 +1,9 @@
pub mod input; pub mod input;
use std::{cell::RefCell, marker::PhantomData, ops::Deref, rc::Rc}; use std::{cell::RefCell, fmt::Debug, marker::PhantomData, ops::Deref, rc::Rc};
use instant::Instant; use instant::Instant;
use log::info;
use maplibre::{ use maplibre::{
environment::Environment, environment::Environment,
error::Error, error::Error,
@ -14,6 +15,10 @@ use maplibre::{
transferables::{DefaultTransferables, Transferables}, transferables::{DefaultTransferables, Transferables},
}, },
map::Map, map::Map,
render::{
builder::RendererBuilder,
settings::{Backends, WgpuSettings},
},
window::{HeadedMapWindow, MapWindowConfig}, window::{HeadedMapWindow, MapWindowConfig},
}; };
use winit::{ use winit::{
@ -69,15 +74,11 @@ pub struct WinitEventLoop<ET: 'static> {
event_loop: RawWinitEventLoop<ET>, event_loop: RawWinitEventLoop<ET>,
} }
impl<ET: 'static> EventLoop<ET> for WinitEventLoop<ET> { impl<ET: 'static + PartialEq + Debug> EventLoop<ET> for WinitEventLoop<ET> {
type EventLoopProxy = WinitEventLoopProxy<ET>; type EventLoopProxy = WinitEventLoopProxy<ET>;
fn run<E>( fn run<E>(mut self, mut map: Map<E>, max_frames: Option<u64>)
mut self, where
mut window: <E::MapWindowConfig as MapWindowConfig>::MapWindow,
mut map: Map<E>,
max_frames: Option<u64>,
) where
E: Environment, E: Environment,
<E::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow, <E::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow,
{ {
@ -89,12 +90,16 @@ impl<ET: 'static> EventLoop<ET> for WinitEventLoop<ET> {
self.event_loop self.event_loop
.run(move |event, window_target, control_flow| { .run(move |event, window_target, control_flow| {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
if !map.is_initialized() && event == Event::Resumed { if !map.has_renderer() && event == Event::Resumed {
use tokio::{runtime::Handle, task}; use tokio::{runtime::Handle, task};
task::block_in_place(|| { task::block_in_place(|| {
Handle::current().block_on(async { Handle::current().block_on(async {
map.late_init().await; map.initialize_renderer(RendererBuilder::new()
.with_wgpu_settings(WgpuSettings {
backends: Some(Backends::VULKAN),
..WgpuSettings::default()
})).await.unwrap();
}) })
}); });
return; return;
@ -111,7 +116,7 @@ impl<ET: 'static> EventLoop<ET> for WinitEventLoop<ET> {
Event::WindowEvent { Event::WindowEvent {
ref event, ref event,
window_id, window_id,
} if window_id == window.id().into() => { } if window_id == map.window().id().into() => {
if !input_controller.window_input(event) { if !input_controller.window_input(event) {
match event { match event {
WindowEvent::CloseRequested WindowEvent::CloseRequested
@ -125,10 +130,14 @@ impl<ET: 'static> EventLoop<ET> for WinitEventLoop<ET> {
.. ..
} => *control_flow = ControlFlow::Exit, } => *control_flow = ControlFlow::Exit,
WindowEvent::Resized(physical_size) => { WindowEvent::Resized(physical_size) => {
map.context_mut().resize(physical_size.width, physical_size.height); if let Ok(map_context) = map.context_mut() {
map_context.resize(physical_size.width, physical_size.height);
}
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
map.context_mut().resize(new_inner_size.width, new_inner_size.height); if let Ok(map_context) = map.context_mut() {
map_context.resize(new_inner_size.width, new_inner_size.height);
}
} }
_ => {} _ => {}
} }
@ -139,7 +148,9 @@ impl<ET: 'static> EventLoop<ET> for WinitEventLoop<ET> {
let dt = now - last_render_time; let dt = now - last_render_time;
last_render_time = now; last_render_time = now;
input_controller.update_state(map.context_mut().world.view_state_mut(), dt); if let Ok(map_context) = map.context_mut() {
input_controller.update_state(map_context.world.view_state_mut(), dt);
}
match map.run_schedule() { match map.run_schedule() {
Ok(_) => {} Ok(_) => {}
@ -170,7 +181,7 @@ impl<ET: 'static> EventLoop<ET> for WinitEventLoop<ET> {
Event::MainEventsCleared => { Event::MainEventsCleared => {
// RedrawRequested will only trigger once, unless we manually // RedrawRequested will only trigger once, unless we manually
// request it. // request it.
window.request_redraw(); map.window().request_redraw();
} }
_ => {} _ => {}
} }

View File

@ -11,7 +11,10 @@ use maplibre::{
kernel::{Kernel, KernelBuilder}, kernel::{Kernel, KernelBuilder},
map::Map, map::Map,
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler}, platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
render::builder::{InitializedRenderer, RenderBuilder}, render::{
builder::{InitializationResult, InitializedRenderer, RendererBuilder},
settings::{Backends, RendererSettings, WgpuSettings},
},
style::Style, style::Style,
window::{HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize}, window::{HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize},
}; };
@ -83,23 +86,21 @@ pub fn run_headed_map(cache_path: Option<String>) {
.with_scheduler(TokioScheduler::new()) .with_scheduler(TokioScheduler::new())
.build(); .build();
let InitializedRenderer { let mut map = Map::new(Style::default(), kernel).unwrap();
mut window,
renderer,
} = RenderBuilder::new()
.build()
.initialize_with(&kernel)
.await
.expect("Failed to initialize renderer")
.unwarp();
window #[cfg(not(target_os = "android"))]
{
map.initialize_renderer(RendererBuilder::new().with_wgpu_settings(WgpuSettings {
backends: Some(Backends::VULKAN),
..WgpuSettings::default()
}))
.await
.unwrap();
}
map.window_mut()
.take_event_loop() .take_event_loop()
.expect("Event loop is not available") .expect("Event loop is not available")
.run( .run(map, None)
window,
Map::new(Style::default(), kernel, renderer).unwrap(),
None,
)
}) })
} }

View File

@ -1,6 +1,6 @@
//! Errors which can happen in various parts of the library. //! Errors which can happen in various parts of the library.
use std::{fmt, fmt::Formatter, sync::mpsc::SendError}; use std::{borrow::Cow, fmt, fmt::Formatter, sync::mpsc::SendError};
use lyon::tessellation::TessellationError; use lyon::tessellation::TessellationError;
@ -14,6 +14,7 @@ pub enum Error {
Network(String), Network(String),
Tesselation(TessellationError), Tesselation(TessellationError),
Render(RenderError), Render(RenderError),
Generic(Cow<'static, str>),
} }
impl<E> From<E> for Error impl<E> From<E> for Error

View File

@ -15,15 +15,11 @@ pub trait EventLoopProxy<T: 'static> {
fn send_event(&self, event: T); fn send_event(&self, event: T);
} }
pub trait EventLoop<T: 'static> { pub trait EventLoop<ET: 'static + PartialEq> {
type EventLoopProxy: EventLoopProxy<T>; type EventLoopProxy: EventLoopProxy<ET>;
fn run<E>( fn run<E>(self, map: Map<E>, max_frames: Option<u64>)
self, where
window: <E::MapWindowConfig as MapWindowConfig>::MapWindow,
map: Map<E>,
max_frames: Option<u64>,
) where
E: Environment, E: Environment,
<E::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow; <E::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow;

View File

@ -1,10 +1,16 @@
use crate::{ use crate::{
headless::{environment::HeadlessEnvironment, window::HeadlessMapWindowConfig}, headless::{
environment::HeadlessEnvironment,
window::{HeadlessMapWindow, HeadlessMapWindowConfig},
},
io::apc::SchedulerAsyncProcedureCall, io::apc::SchedulerAsyncProcedureCall,
kernel::{Kernel, KernelBuilder}, kernel::{Kernel, KernelBuilder},
platform::{http_client::ReqwestHttpClient, scheduler::TokioScheduler}, platform::{http_client::ReqwestHttpClient, scheduler::TokioScheduler},
render::{builder::RenderBuilder, Renderer}, render::{
window::WindowSize, builder::{InitializedRenderer, RendererBuilder},
Renderer,
},
window::{MapWindowConfig, WindowSize},
}; };
mod graph_node; mod graph_node;
@ -31,9 +37,12 @@ pub async fn create_headless_renderer(
.with_scheduler(TokioScheduler::new()) .with_scheduler(TokioScheduler::new())
.build(); .build();
let renderer = RenderBuilder::new() let mwc: &HeadlessMapWindowConfig = kernel.map_window_config();
let window: HeadlessMapWindow = mwc.create();
let renderer = RendererBuilder::new()
.build() .build()
.initialize_headless_with(&kernel) .initialize_headless::<HeadlessMapWindowConfig>(&window)
.await .await
.expect("Failed to initialize renderer"); .expect("Failed to initialize renderer");

View File

@ -6,31 +6,38 @@ use crate::{
environment::Environment, environment::Environment,
error::Error, error::Error,
kernel::Kernel, kernel::Kernel,
render::{create_default_render_graph, register_default_render_stages, Renderer}, render::{
builder::{
InitializationResult, InitializedRenderer, RendererBuilder, UninitializedRenderer,
},
create_default_render_graph, register_default_render_stages,
settings::{RendererSettings, WgpuSettings},
Renderer,
},
schedule::{Schedule, Stage}, schedule::{Schedule, Stage},
stages::register_stages, stages::register_stages,
style::Style, style::Style,
window::{HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize},
world::World, world::World,
}; };
pub enum MapContextState {
Ready(MapContext),
Pending { style: Style },
}
pub struct Map<E: Environment> { pub struct Map<E: Environment> {
kernel: Rc<Kernel<E>>, kernel: Rc<Kernel<E>>,
schedule: Schedule, schedule: Schedule,
map_context: MapContext, map_context: MapContextState,
window: <E::MapWindowConfig as MapWindowConfig>::MapWindow,
} }
impl<E: Environment> Map<E> { impl<E: Environment> Map<E>
pub fn new(style: Style, kernel: Kernel<E>, renderer: Renderer) -> Result<Self, Error> { where
let window_size = renderer.state().surface().size(); <<E as Environment>::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow,
{
let center = style.center.unwrap_or_default(); pub fn new(style: Style, kernel: Kernel<E>) -> Result<Self, Error> {
let world = World::new_at(
window_size,
LatLon::new(center[0], center[1]),
style.zoom.map(|zoom| Zoom::new(zoom)).unwrap_or_default(),
cgmath::Deg::<f64>(style.pitch.unwrap_or_default()),
);
let mut schedule = Schedule::default(); let mut schedule = Schedule::default();
let graph = create_default_render_graph().unwrap(); // TODO: Remove unwrap let graph = create_default_render_graph().unwrap(); // TODO: Remove unwrap
@ -40,29 +47,100 @@ impl<E: Environment> Map<E> {
register_stages::<E>(&mut schedule, kernel.clone()); register_stages::<E>(&mut schedule, kernel.clone());
Ok(Self { let mut window = kernel.map_window_config().create();
let map = Self {
kernel, kernel,
map_context: MapContext { map_context: MapContextState::Pending { style },
style,
world,
renderer,
},
schedule, schedule,
}) window,
};
Ok(map)
}
pub async fn initialize_renderer(
&mut self,
render_builder: RendererBuilder,
) -> Result<(), Error> {
let result = render_builder
.build()
.initialize_renderer::<E::MapWindowConfig>(&self.window)
.await
.expect("Failed to initialize renderer");
match &mut self.map_context {
MapContextState::Ready(_) => Err(Error::Generic("Renderer is already set".into())),
MapContextState::Pending { style } => {
let window_size = self.window.size();
let center = style.center.unwrap_or_default();
let world = World::new_at(
window_size,
LatLon::new(center[0], center[1]),
style.zoom.map(|zoom| Zoom::new(zoom)).unwrap_or_default(),
cgmath::Deg::<f64>(style.pitch.unwrap_or_default()),
);
match result {
InitializationResult::Initialized(InitializedRenderer { renderer, .. }) => {
*&mut self.map_context = MapContextState::Ready(MapContext {
world,
style: std::mem::take(style),
renderer,
});
}
InitializationResult::Uninizalized(UninitializedRenderer { .. }) => {}
_ => panic!("Rendering context gone"),
};
Ok(())
}
}
}
pub fn window_mut(&mut self) -> &mut <E::MapWindowConfig as MapWindowConfig>::MapWindow {
&mut self.window
}
pub fn window(&self) -> &<E::MapWindowConfig as MapWindowConfig>::MapWindow {
&self.window
}
pub fn has_renderer(&self) -> bool {
match &self.map_context {
MapContextState::Ready(_) => true,
MapContextState::Pending { .. } => false,
}
} }
#[tracing::instrument(name = "update_and_redraw", skip_all)] #[tracing::instrument(name = "update_and_redraw", skip_all)]
pub fn run_schedule(&mut self) -> Result<(), Error> { pub fn run_schedule(&mut self) -> Result<(), Error> {
self.schedule.run(&mut self.map_context); match &mut self.map_context {
Ok(()) MapContextState::Ready(map_context) => {
self.schedule.run(map_context);
Ok(())
}
MapContextState::Pending { .. } => {
Err(Error::Generic("Renderer is already set".into()))
}
}
} }
pub fn context(&self) -> &MapContext { pub fn context(&self) -> Result<&MapContext, Error> {
&self.map_context match &self.map_context {
MapContextState::Ready(map_context) => Ok(map_context),
MapContextState::Pending { .. } => {
Err(Error::Generic("Renderer is already set".into()))
}
}
} }
pub fn context_mut(&mut self) -> &mut MapContext { pub fn context_mut(&mut self) -> Result<&mut MapContext, Error> {
&mut self.map_context match &mut self.map_context {
MapContextState::Ready(map_context) => Ok(map_context),
MapContextState::Pending { .. } => {
Err(Error::Generic("Renderer is already set".into()))
}
}
} }
pub fn kernel(&self) -> &Rc<Kernel<E>> { pub fn kernel(&self) -> &Rc<Kernel<E>> {

View File

@ -12,18 +12,16 @@ use crate::{
window::{HeadedMapWindow, MapWindow, MapWindowConfig}, window::{HeadedMapWindow, MapWindow, MapWindowConfig},
}; };
pub struct RenderBuilder<MWC: MapWindowConfig> { pub struct RendererBuilder {
wgpu_settings: Option<WgpuSettings>, wgpu_settings: Option<WgpuSettings>,
renderer_settings: Option<RendererSettings>, renderer_settings: Option<RendererSettings>,
phatom_mwc: PhantomData<MWC>,
} }
impl<MWC: MapWindowConfig> RenderBuilder<MWC> { impl RendererBuilder {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
wgpu_settings: None, wgpu_settings: None,
renderer_settings: None, renderer_settings: None,
phatom_mwc: Default::default(),
} }
} }
@ -37,97 +35,92 @@ impl<MWC: MapWindowConfig> RenderBuilder<MWC> {
self self
} }
pub fn build(self) -> UninitializedRenderer<MWC> { pub fn build(self) -> UninitializedRenderer {
UninitializedRenderer { UninitializedRenderer {
window: None,
wgpu_settings: self.wgpu_settings.unwrap_or_default(), wgpu_settings: self.wgpu_settings.unwrap_or_default(),
renderer_settings: self.renderer_settings.unwrap_or_default(), renderer_settings: self.renderer_settings.unwrap_or_default(),
} }
} }
} }
pub enum InitializationResult<MWC: MapWindowConfig> { pub enum InitializationResult {
Initialized(InitializedRenderer<MWC>), Initialized(InitializedRenderer),
Uninizalized(UninitializedRenderer<MWC>), Uninizalized(UninitializedRenderer),
Gone,
} }
impl<MWC: MapWindowConfig> InitializationResult<MWC> { impl Default for InitializationResult {
pub fn unwarp(self) -> InitializedRenderer<MWC> { fn default() -> Self {
Self::Gone
}
}
impl InitializationResult {
pub fn unwarp_renderer(self) -> InitializedRenderer {
match self { match self {
InitializationResult::Initialized(renderer) => renderer, InitializationResult::Initialized(renderer) => renderer,
InitializationResult::Uninizalized(_) => panic!("Renderer is not initialized"), InitializationResult::Uninizalized(_) => panic!("Renderer is not initialized"),
InitializationResult::Gone => panic!("Initialization context is gone"),
}
}
pub fn into_option(self) -> Option<Renderer> {
match self {
InitializationResult::Initialized(InitializedRenderer { renderer, .. }) => {
Some(renderer)
}
InitializationResult::Uninizalized(_) => None,
InitializationResult::Gone => panic!("Initialization context is gone"),
} }
} }
} }
pub struct UninitializedRenderer<MWC: MapWindowConfig> { pub struct UninitializedRenderer {
window: Option<MWC::MapWindow>, pub wgpu_settings: WgpuSettings,
wgpu_settings: WgpuSettings, pub renderer_settings: RendererSettings,
renderer_settings: RendererSettings,
} }
impl<MWC: MapWindowConfig> UninitializedRenderer<MWC> impl UninitializedRenderer {
where
MWC::MapWindow: MapWindow + HeadedMapWindow,
{
/// Initializes the whole rendering pipeline for the given configuration. /// Initializes the whole rendering pipeline for the given configuration.
/// Returns the initialized map, ready to be run. /// Returns the initialized map, ready to be run.
async fn initialize(self, map_window_config: &MWC) -> Result<InitializationResult<MWC>, Error> { pub async fn initialize_renderer<MWC>(
let window = map_window_config.create();
#[cfg(target_os = "android")]
{
Ok(InitializationResult::Uninizalized(self))
}
#[cfg(not(target_os = "android"))]
{
let renderer = Renderer::initialize(
&window,
self.wgpu_settings.clone(),
self.renderer_settings.clone(),
)
.await?;
Ok(InitializationResult::Initialized(InitializedRenderer {
window,
renderer,
}))
}
}
pub async fn initialize_with<E>(
self, self,
kernel: &Kernel<E>, existing_window: &MWC::MapWindow,
) -> Result<InitializationResult<MWC>, Error> ) -> Result<InitializationResult, Error>
where where
E: Environment<MapWindowConfig = MWC>, MWC: MapWindowConfig,
<MWC as MapWindowConfig>::MapWindow: HeadedMapWindow,
{ {
self.initialize(kernel.map_window_config()).await let renderer = Renderer::initialize(
existing_window,
self.wgpu_settings.clone(),
self.renderer_settings.clone(),
)
.await?;
Ok(InitializationResult::Initialized(InitializedRenderer {
renderer,
}))
} }
} }
#[cfg(feature = "headless")] #[cfg(feature = "headless")]
impl<MWC: MapWindowConfig> UninitializedRenderer<MWC> { impl UninitializedRenderer {
async fn initialize_headless(self, map_window_config: &MWC) -> Result<Renderer, Error> { pub(crate) async fn initialize_headless<MWC>(
let window = map_window_config.create(); self,
existing_window: &MWC::MapWindow,
) -> Result<Renderer, Error>
where
MWC: MapWindowConfig,
{
Ok(Renderer::initialize_headless( Ok(Renderer::initialize_headless(
&window, existing_window,
self.wgpu_settings.clone(), self.wgpu_settings.clone(),
self.renderer_settings.clone(), self.renderer_settings.clone(),
) )
.await?) .await?)
} }
pub async fn initialize_headless_with<E>(self, kernel: &Kernel<E>) -> Result<Renderer, Error>
where
E: Environment<MapWindowConfig = MWC>,
{
self.initialize_headless(kernel.map_window_config()).await
}
} }
pub struct InitializedRenderer<MWC: MapWindowConfig> { pub struct InitializedRenderer {
pub window: MWC::MapWindow,
pub renderer: Renderer, pub renderer: Renderer,
} }

View File

@ -7,8 +7,9 @@ use maplibre::{
io::{apc::SchedulerAsyncProcedureCall, scheduler::NopScheduler}, io::{apc::SchedulerAsyncProcedureCall, scheduler::NopScheduler},
kernel::{Kernel, KernelBuilder}, kernel::{Kernel, KernelBuilder},
map::Map, map::Map,
render::builder::{InitializedRenderer, RenderBuilder}, render::builder::{InitializedRenderer, RendererBuilder},
style::Style, style::Style,
window::MapWindowConfig,
}; };
use maplibre_winit::{WinitEnvironment, WinitMapWindowConfig}; use maplibre_winit::{WinitEnvironment, WinitMapWindowConfig};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@ -66,13 +67,8 @@ type CurrentEnvironment = WinitEnvironment<
pub type MapType = Map<CurrentEnvironment>; pub type MapType = Map<CurrentEnvironment>;
pub struct InitResult {
initialized: InitializedRenderer<WinitMapWindowConfig<()>>,
kernel: Kernel<CurrentEnvironment>,
}
#[wasm_bindgen] #[wasm_bindgen]
pub async fn init_maplibre(new_worker: js_sys::Function) -> u32 { pub async fn run_maplibre(new_worker: js_sys::Function) {
let mut kernel_builder = KernelBuilder::new() let mut kernel_builder = KernelBuilder::new()
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string())) .with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
.with_http_client(WHATWGFetchHttpClient::new()); .with_http_client(WHATWGFetchHttpClient::new());
@ -100,31 +96,15 @@ pub async fn init_maplibre(new_worker: js_sys::Function) -> u32 {
let kernel: Kernel<WinitEnvironment<_, _, _, ()>> = kernel_builder.build(); let kernel: Kernel<WinitEnvironment<_, _, _, ()>> = kernel_builder.build();
Box::into_raw(Box::new(InitResult { let mut map: MapType = Map::new(Style::default(), kernel).unwrap();
initialized: RenderBuilder::new() map.initialize_renderer(RendererBuilder::new())
.build() .await
.initialize_with(&kernel) .unwrap();
.await
.expect("Failed to initialize renderer")
.unwarp(),
kernel,
})) as u32
}
#[wasm_bindgen] map.window_mut()
pub unsafe fn run(init_ptr: *mut InitResult) {
let mut init_result = Box::from_raw(init_ptr);
let InitializedRenderer {
mut window,
renderer,
} = init_result.initialized;
let map: MapType = Map::new(Style::default(), init_result.kernel, renderer).unwrap();
window
.take_event_loop() .take_event_loop()
.expect("Event loop is not available") .expect("Event loop is not available")
.run(window, map, None) .run(map, None)
} }
#[cfg(test)] #[cfg(test)]