desktop: Fetch root movie asynchronously

Use `Player::fetch_root_movie` for loading the root movie, as an
early preparation for chunked loading.
This commit is contained in:
relrelb 2022-05-14 08:49:07 +03:00 committed by relrelb
parent 0097a75703
commit 102fc46ca2
2 changed files with 93 additions and 69 deletions

View File

@ -4,4 +4,7 @@
pub enum RuffleEvent { pub enum RuffleEvent {
/// Indicates that one or more tasks are ready to poll on our executor. /// Indicates that one or more tasks are ready to poll on our executor.
TaskPoll, TaskPoll,
/// Indicates that an asynchronous SWF metadata load has been completed.
OnMetadata(ruffle_core::swf::HeaderExt),
} }

View File

@ -124,6 +124,17 @@ fn parse_url(path: &Path) -> Result<Url, Box<dyn std::error::Error>> {
}) })
} }
fn parse_parameters(opt: &Opt) -> impl '_ + Iterator<Item = (String, String)> {
opt.parameters.iter().map(|parameter| {
let mut split = parameter.splitn(2, '=');
if let (Some(key), Some(value)) = (split.next(), split.next()) {
(key.to_owned(), value.to_owned())
} else {
(parameter.clone(), "".to_string())
}
})
}
fn pick_file() -> Option<PathBuf> { fn pick_file() -> Option<PathBuf> {
FileDialog::new() FileDialog::new()
.add_filter(".swf", &["swf"]) .add_filter(".swf", &["swf"])
@ -147,41 +158,27 @@ fn load_movie(url: &Url, opt: &Opt) -> Result<SwfMovie, Box<dyn std::error::Erro
SwfMovie::from_data(&buffer, Some(url.to_string()), None)? SwfMovie::from_data(&buffer, Some(url.to_string()), None)?
}; };
let parameters = opt.parameters.iter().map(|parameter| { movie.append_parameters(parse_parameters(opt));
let mut split = parameter.splitn(2, '=');
if let (Some(key), Some(value)) = (split.next(), split.next()) {
(key.to_owned(), value.to_owned())
} else {
(parameter.clone(), "".to_string())
}
});
movie.append_parameters(parameters);
Ok(movie) Ok(movie)
} }
struct App { struct App {
#[allow(dead_code)]
opt: Opt, opt: Opt,
window: Rc<Window>, window: Rc<Window>,
event_loop: EventLoop<RuffleEvent>, event_loop: EventLoop<RuffleEvent>,
executor: Arc<Mutex<GlutinAsyncExecutor>>, executor: Arc<Mutex<GlutinAsyncExecutor>>,
player: Arc<Mutex<Player>>, player: Arc<Mutex<Player>>,
loaded: bool,
} }
impl App { impl App {
const DEFAULT_WINDOW_SIZE: LogicalSize<f64> = LogicalSize::new(1280.0, 720.0);
fn new(opt: Opt) -> Result<Self, Box<dyn std::error::Error>> { fn new(opt: Opt) -> Result<Self, Box<dyn std::error::Error>> {
let path = match opt.input_path.as_ref() { let path = match opt.input_path.as_ref() {
Some(path) => Some(std::borrow::Cow::Borrowed(path)), Some(path) => Some(std::borrow::Cow::Borrowed(path)),
None => pick_file().map(std::borrow::Cow::Owned), None => pick_file().map(std::borrow::Cow::Owned),
}; };
let (movie, movie_url) = if let Some(path) = path { let movie_url = if let Some(path) = path {
let movie_url = parse_url(&path)?; Some(parse_url(&path)?)
let movie = load_movie(&movie_url, &opt)?;
(Some(movie), Some(movie_url))
} else { } else {
shutdown(&Ok(())); shutdown(&Ok(()));
std::process::exit(0); std::process::exit(0);
@ -192,78 +189,53 @@ impl App {
let event_loop: EventLoop<RuffleEvent> = EventLoop::with_user_event(); let event_loop: EventLoop<RuffleEvent> = EventLoop::with_user_event();
let (title, movie_size) = if let (Some(movie), Some(movie_url)) = (&movie, &movie_url) { let title = if let Some(movie_url) = &movie_url {
let filename = movie_url let filename = movie_url
.path_segments() .path_segments()
.and_then(|segments| segments.last()) .and_then(|segments| segments.last())
.unwrap_or_else(|| movie_url.as_str()); .unwrap_or_else(|| movie_url.as_str());
( format!("Ruffle - {}", filename)
format!("Ruffle - {}", filename),
LogicalSize::new(movie.width().to_pixels(), movie.height().to_pixels()),
)
} else { } else {
("Ruffle".into(), Self::DEFAULT_WINDOW_SIZE) "Ruffle".into()
};
let window_size: Size = if opt.width.is_none() && opt.height.is_none() {
movie_size.into()
} else {
let window_width = opt
.width
.unwrap_or(
movie_size.width
* (opt.height.unwrap_or(movie_size.height) / movie_size.height),
)
.max(1.0);
let window_height = opt
.height
.unwrap_or(
movie_size.height * (opt.width.unwrap_or(movie_size.width) / movie_size.width),
)
.max(1.0);
PhysicalSize::new(window_width, window_height).into()
}; };
let window = WindowBuilder::new() let window = WindowBuilder::new()
.with_visible(false)
.with_title(title) .with_title(title)
.with_window_icon(Some(icon)) .with_window_icon(Some(icon))
.with_inner_size(window_size)
.with_max_inner_size(LogicalSize::new(i16::MAX, i16::MAX)) .with_max_inner_size(LogicalSize::new(i16::MAX, i16::MAX))
.with_fullscreen(if opt.fullscreen {
Some(Fullscreen::Borderless(None))
} else {
None
})
.build(&event_loop)?; .build(&event_loop)?;
let viewport_size = window.inner_size();
let viewport_scale_factor = window.scale_factor();
let window = Rc::new(window);
let mut builder = PlayerBuilder::new(); let mut builder = PlayerBuilder::new();
match audio::CpalAudioBackend::new() { match audio::CpalAudioBackend::new() {
Ok(audio) => builder = builder.with_audio(audio), Ok(audio) => builder = builder.with_audio(audio),
Err(e) => { Err(e) => {
log::error!("Unable to create audio device: {}", e); log::error!("Unable to create audio device: {}", e);
} }
}; };
let (executor, channel) = GlutinAsyncExecutor::new(event_loop.create_proxy()); let (executor, channel) = GlutinAsyncExecutor::new(event_loop.create_proxy());
let navigator = navigator::ExternalNavigatorBackend::new( let navigator = navigator::ExternalNavigatorBackend::new(
movie_url.unwrap(), movie_url.as_ref().unwrap().to_owned(),
channel, channel,
event_loop.create_proxy(), event_loop.create_proxy(),
opt.proxy.clone(), opt.proxy.clone(),
opt.upgrade_to_https, opt.upgrade_to_https,
); );
let viewport_size = window.inner_size();
let renderer = WgpuRenderBackend::for_window( let renderer = WgpuRenderBackend::for_window(
window.as_ref(), &window,
(viewport_size.width, viewport_size.height), (viewport_size.width, viewport_size.height),
opt.graphics.into(), opt.graphics.into(),
opt.power.into(), opt.power.into(),
trace_path(&opt), trace_path(&opt),
)?; )?;
let window = Rc::new(window);
builder = builder builder = builder
.with_navigator(navigator) .with_navigator(navigator)
.with_renderer(renderer) .with_renderer(renderer)
@ -273,32 +245,34 @@ impl App {
.with_autoplay(true) .with_autoplay(true)
.with_letterbox(Letterbox::On) .with_letterbox(Letterbox::On)
.with_warn_on_unsupported_content(!opt.dont_warn_on_unsupported_content) .with_warn_on_unsupported_content(!opt.dont_warn_on_unsupported_content)
.with_viewport_dimensions(
viewport_size.width,
viewport_size.height,
viewport_scale_factor,
)
.with_fullscreen(opt.fullscreen); .with_fullscreen(opt.fullscreen);
let loaded = if let Some(movie) = movie {
builder = builder.with_movie(movie);
true
} else {
false
};
let player = builder.build(); let player = builder.build();
if let Some(movie_url) = &movie_url {
let event_loop_proxy = event_loop.create_proxy();
let on_metadata = move |swf_header: &ruffle_core::swf::HeaderExt| {
let _ = event_loop_proxy.send_event(RuffleEvent::OnMetadata(swf_header.clone()));
};
player.lock().unwrap().fetch_root_movie(
movie_url.as_str(),
parse_parameters(&opt).collect(),
Box::new(on_metadata),
);
}
Ok(Self { Ok(Self {
opt, opt,
window, window,
event_loop, event_loop,
executor, executor,
player, player,
loaded,
}) })
} }
fn run(self) -> ! { fn run(self) -> ! {
let mut loaded = false;
let mut mouse_pos = PhysicalPosition::new(0.0, 0.0); let mut mouse_pos = PhysicalPosition::new(0.0, 0.0);
let mut time = Instant::now(); let mut time = Instant::now();
let mut next_frame_time = Instant::now(); let mut next_frame_time = Instant::now();
@ -357,7 +331,7 @@ impl App {
} }
// Core loop // Core loop
winit::event::Event::MainEventsCleared if self.loaded => { winit::event::Event::MainEventsCleared if loaded => {
let new_time = Instant::now(); let new_time = Instant::now();
let dt = new_time.duration_since(time).as_micros(); let dt = new_time.duration_since(time).as_micros();
if dt > 0 { if dt > 0 {
@ -493,11 +467,58 @@ impl App {
.lock() .lock()
.expect("active executor reference") .expect("active executor reference")
.poll_all(), .poll_all(),
winit::event::Event::UserEvent(RuffleEvent::OnMetadata(swf_header)) => {
// TODO: Re-use `SwfMovie::width` and `SwfMovie::height`.
let movie_width = (swf_header.stage_size().x_max
- swf_header.stage_size().x_min)
.to_pixels();
let movie_height = (swf_header.stage_size().y_max
- swf_header.stage_size().y_min)
.to_pixels();
let window_size: Size = match (self.opt.width, self.opt.height) {
(None, None) => LogicalSize::new(movie_width, movie_height).into(),
(Some(width), None) => {
let scale = width / movie_width;
let height = movie_height * scale;
PhysicalSize::new(width.max(1.0), height.max(1.0)).into()
}
(None, Some(height)) => {
let scale = height / movie_height;
let width = movie_width * scale;
PhysicalSize::new(width.max(1.0), height.max(1.0)).into()
}
(Some(width), Some(height)) => {
PhysicalSize::new(width.max(1.0), height.max(1.0)).into()
}
};
self.window.set_inner_size(window_size);
self.window.set_fullscreen(if self.opt.fullscreen {
Some(Fullscreen::Borderless(None))
} else {
None
});
self.window.set_visible(true);
let viewport_size = self.window.inner_size();
let viewport_scale_factor = self.window.scale_factor();
let mut player_lock = self.player.lock().unwrap();
player_lock.set_viewport_dimensions(
viewport_size.width,
viewport_size.height,
viewport_scale_factor,
);
player_lock
.renderer_mut()
.set_viewport_dimensions(viewport_size.width, viewport_size.height);
loaded = true;
}
_ => (), _ => (),
} }
// After polling events, sleep the event loop until the next event or the next frame. // After polling events, sleep the event loop until the next event or the next frame.
*control_flow = if self.loaded { *control_flow = if loaded {
ControlFlow::WaitUntil(next_frame_time) ControlFlow::WaitUntil(next_frame_time)
} else { } else {
ControlFlow::Wait ControlFlow::Wait