core: Close sockets and timers when replacing the root movie

Turns out that the comment from `set_root_movie` was right all along!
`set_root_movie` should not be used when replacing the root movie,
because it will leave out unclosed resources.
This patch introduces `replace_root_movie`, which is dedicated to be
used when replacing (instead of just setting) the root movie.

The best solution would be to use RAII, e.g. destroy and recreate
the whole Player instance, but this patch is a first step towards
proper resource control.
This commit is contained in:
Kamil Jarosz 2024-03-04 00:13:02 +01:00 committed by Nathan Adams
parent 3d04971ae4
commit c260a45603
5 changed files with 47 additions and 22 deletions

View File

@ -518,11 +518,11 @@ impl<'gc> AudioManager<'gc> {
audio.stop_all_sounds();
}
pub fn is_sound_playing(&mut self, sound: SoundInstanceHandle) -> bool {
pub fn is_sound_playing(&self, sound: SoundInstanceHandle) -> bool {
self.sounds.iter().any(|other| other.instance == sound)
}
pub fn is_sound_playing_with_handle(&mut self, sound: SoundHandle) -> bool {
pub fn is_sound_playing_with_handle(&self, sound: SoundHandle) -> bool {
self.sounds.iter().any(|other| other.sound == Some(sound))
}

View File

@ -321,11 +321,11 @@ impl<'a, 'gc> UpdateContext<'a, 'gc> {
self.audio_manager.stop_all_sounds(self.audio)
}
pub fn is_sound_playing(&mut self, sound: SoundInstanceHandle) -> bool {
pub fn is_sound_playing(&self, sound: SoundInstanceHandle) -> bool {
self.audio_manager.is_sound_playing(sound)
}
pub fn is_sound_playing_with_handle(&mut self, sound: SoundHandle) -> bool {
pub fn is_sound_playing_with_handle(&self, sound: SoundHandle) -> bool {
self.audio_manager.is_sound_playing_with_handle(sound)
}
@ -348,7 +348,7 @@ impl<'a, 'gc> UpdateContext<'a, 'gc> {
///
/// This should only be called once, as it makes no attempt at removing
/// previous stage contents. If you need to load a new root movie, you
/// should destroy and recreate the player instance.
/// should use `replace_root_movie`.
pub fn set_root_movie(&mut self, movie: SwfMovie) {
if !self.forced_frame_rate {
*self.frame_rate = movie.frame_rate().into();
@ -453,6 +453,17 @@ impl<'a, 'gc> UpdateContext<'a, 'gc> {
self.audio.set_frame_rate(*self.frame_rate);
}
pub fn replace_root_movie(&mut self, movie: SwfMovie) {
// FIXME Use RAII here, e.g. destroy and recreate
// the player instance instead of cleaning up.
// Clean up the stage before loading another root movie.
self.sockets.close_all();
self.timers.remove_all();
self.set_root_movie(movie);
}
}
impl<'a, 'gc> UpdateContext<'a, 'gc> {

View File

@ -1019,7 +1019,7 @@ impl<'gc> Loader<'gc> {
let movie = SwfMovie::from_data(&body, url.to_string(), loader_url)?;
player.lock().unwrap().mutate_with_update_context(|uc| {
uc.set_root_movie(movie);
uc.replace_root_movie(movie);
});
return Ok(());
}
@ -1102,7 +1102,7 @@ impl<'gc> Loader<'gc> {
"loadBytes",
"replacing root movie"
);
uc.set_root_movie(movie);
uc.replace_root_movie(movie);
return Ok(());
}

View File

@ -170,27 +170,37 @@ impl<'gc> Sockets<'gc> {
}
}
pub fn close_all(&mut self) {
for (_, socket) in self.sockets.drain() {
Self::close_internal(socket);
}
}
pub fn close(&mut self, handle: SocketHandle) {
if let Some(Socket {
if let Some(socket) = self.sockets.remove(handle) {
Self::close_internal(socket);
}
}
fn close_internal(socket: Socket) {
let Socket {
sender,
target,
connected: _,
}) = self.sockets.remove(handle)
{
drop(sender); // NOTE: By dropping the sender, the reading task will close automatically.
} = socket;
// Clear the buffers if the connection was closed.
match target {
SocketKind::Avm1(target) => {
let target =
XmlSocket::cast(target.into()).expect("target should be XmlSocket");
drop(sender); // NOTE: By dropping the sender, the reading task will close automatically.
target.read_buffer().clear();
}
SocketKind::Avm2(target) => {
target.read_buffer().clear();
target.write_buffer().clear();
}
// Clear the buffers if the connection was closed.
match target {
SocketKind::Avm1(target) => {
let target = XmlSocket::cast(target.into()).expect("target should be XmlSocket");
target.read_buffer().clear();
}
SocketKind::Avm2(target) => {
target.read_buffer().clear();
target.write_buffer().clear();
}
}
}

View File

@ -258,6 +258,10 @@ impl<'gc> Timers<'gc> {
len < old_len
}
pub fn remove_all(&mut self) {
self.timers.clear()
}
/// Changes the delay of a timer.
pub fn set_delay(&mut self, id: i32, interval: i32) {
// SANITY: Set a minimum interval so we don't spam too much.