diff --git a/Cargo.lock b/Cargo.lock index 9eae93751..38926b1d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2272,15 +2272,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "generational-arena" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877e94aff08e743b651baaea359664321055749b398adff8740a7399af7796e7" -dependencies = [ - "cfg-if", -] - [[package]] name = "generator" version = "0.7.5" @@ -4464,7 +4455,6 @@ dependencies = [ "flv-rs", "fnv", "futures", - "generational-arena", "hashbrown 0.14.3", "image", "indexmap", @@ -4489,6 +4479,7 @@ dependencies = [ "scopeguard", "serde", "serde_json", + "slotmap", "smallvec", "swf", "symphonia", @@ -4525,7 +4516,6 @@ dependencies = [ "fontdb", "futures", "futures-lite 2.2.0", - "generational-arena", "gilrs", "image", "isahc", @@ -4536,6 +4526,7 @@ dependencies = [ "ruffle_render", "ruffle_render_wgpu", "ruffle_video_software", + "slotmap", "sys-locale", "tokio", "tracing", @@ -4717,8 +4708,8 @@ dependencies = [ name = "ruffle_video" version = "0.1.0" dependencies = [ - "generational-arena", "ruffle_render", + "slotmap", "swf", "thiserror", ] @@ -4728,7 +4719,6 @@ name = "ruffle_video_software" version = "0.1.0" dependencies = [ "flate2", - "generational-arena", "h263-rs", "h263-rs-deblock", "log", @@ -4737,6 +4727,7 @@ dependencies = [ "nihav_duck", "ruffle_render", "ruffle_video", + "slotmap", "swf", "thiserror", ] @@ -4751,7 +4742,6 @@ dependencies = [ "console_error_panic_hook", "futures", "futures-util", - "generational-arena", "getrandom", "gloo-net", "js-sys", @@ -4765,6 +4755,7 @@ dependencies = [ "ruffle_web_common", "serde", "serde-wasm-bindgen", + "slotmap", "thiserror", "tracing", "tracing-log", diff --git a/Cargo.toml b/Cargo.toml index 60bd0a243..a6c015fa3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ wgpu = "0.19.3" egui = "0.26.2" clap = { version = "4.5.1", features = ["derive"] } anyhow = "1.0" +slotmap = "1.0.7" [workspace.lints.rust] # Clippy nightly often adds new/buggy lints that we want to ignore. diff --git a/core/Cargo.toml b/core/Cargo.toml index db6fc90fb..04724aed8 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -17,7 +17,7 @@ bitstream-io = "2.2.0" flate2 = "1.0.28" fnv = "1.0.7" gc-arena = { package = "ruffle_gc_arena", path = "../ruffle_gc_arena" } -generational-arena = "0.2.9" +slotmap = { workspace = true } indexmap = "2.2.5" tracing = { workspace = true } ruffle_render = { path = "../render", features = ["tessellator"] } diff --git a/core/src/backend/audio.rs b/core/src/backend/audio.rs index 8a913887f..ba5364cf5 100644 --- a/core/src/backend/audio.rs +++ b/core/src/backend/audio.rs @@ -7,7 +7,7 @@ use crate::{ }; use downcast_rs::Downcast; use gc_arena::Collect; -use generational_arena::{Arena, Index}; +use slotmap::{DefaultKey, Key, SlotMap}; #[cfg(feature = "audio")] pub mod decoders; @@ -35,8 +35,8 @@ mod decoders { use thiserror::Error; use web_time::Duration; -pub type SoundHandle = Index; -pub type SoundInstanceHandle = Index; +pub type SoundHandle = DefaultKey; +pub type SoundInstanceHandle = DefaultKey; pub type DecodeError = decoders::Error; #[derive(Eq, PartialEq, Clone, Copy, Debug)] @@ -205,14 +205,14 @@ struct NullSound { /// Audio backend that ignores all audio. pub struct NullAudioBackend { - sounds: Arena, + sounds: SlotMap, volume: f32, } impl NullAudioBackend { pub fn new() -> NullAudioBackend { NullAudioBackend { - sounds: Arena::new(), + sounds: SlotMap::new(), volume: 1.0, } } @@ -259,7 +259,7 @@ impl AudioBackend for NullAudioBackend { _sound: SoundHandle, _sound_info: &swf::SoundInfo, ) -> Result { - Ok(SoundInstanceHandle::from_raw_parts(0, 0)) + Ok(SoundInstanceHandle::null()) } fn start_stream( @@ -267,7 +267,7 @@ impl AudioBackend for NullAudioBackend { _clip_data: crate::tag_utils::SwfSlice, _handle: &swf::SoundStreamHead, ) -> Result { - Ok(SoundInstanceHandle::from_raw_parts(0, 0)) + Ok(SoundInstanceHandle::null()) } fn start_substream( @@ -275,7 +275,7 @@ impl AudioBackend for NullAudioBackend { _stream_data: Substream, _handle: &SoundStreamInfo, ) -> Result { - Ok(SoundInstanceHandle::from_raw_parts(0, 0)) + Ok(SoundInstanceHandle::null()) } fn stop_sound(&mut self, _sound: SoundInstanceHandle) {} diff --git a/core/src/backend/audio/mixer.rs b/core/src/backend/audio/mixer.rs index 081865fe5..ab6d548c6 100644 --- a/core/src/backend/audio/mixer.rs +++ b/core/src/backend/audio/mixer.rs @@ -3,7 +3,7 @@ use super::{SoundHandle, SoundInstanceHandle, SoundStreamInfo, SoundTransform}; use crate::backend::audio::{DecodeError, RegisterError}; use crate::buffer::Substream; use crate::tag_utils::SwfSlice; -use generational_arena::Arena; +use slotmap::SlotMap; use std::io::Cursor; use std::sync::{Arc, Mutex, RwLock}; use swf::AudioCompression; @@ -54,10 +54,10 @@ impl CircBuf { // all sounds and mix the audio into an output buffer audio stream. pub struct AudioMixer { /// The currently registered sounds. - sounds: Arena, + sounds: SlotMap, /// The list of actively playing sound instances. - sound_instances: Arc>>, + sound_instances: Arc>>, /// The master volume of the audio from [0.0, 1.0]. volume: Arc>, @@ -239,8 +239,8 @@ impl AudioMixer { /// Creates a new `AudioMixer` with the given number of channels and sample rate. pub fn new(num_output_channels: u8, output_sample_rate: u32) -> Self { Self { - sounds: Arena::new(), - sound_instances: Arc::new(Mutex::new(Arena::new())), + sounds: SlotMap::new(), + sound_instances: Arc::new(Mutex::new(SlotMap::new())), volume: Arc::new(RwLock::new(1.0)), num_output_channels, output_sample_rate, @@ -428,7 +428,7 @@ impl AudioMixer { /// Refill the output buffer by stepping through all active sounds /// and mixing in their output. fn mix_audio<'a, T>( - sound_instances: &mut Arena, + sound_instances: &mut SlotMap, volume: f32, num_channels: u8, mut output_buffer: &mut [T], @@ -724,7 +724,7 @@ impl AudioMixer { /// to perform audio mixing on a different thread. pub struct AudioMixerProxy { /// The list of actively playing sound instances. - sound_instances: Arc>>, + sound_instances: Arc>>, /// The master volume of the audio from [0.0, 1.0]. volume: Arc>, diff --git a/core/src/loader.rs b/core/src/loader.rs index 9e92f0579..13bb04f47 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -32,8 +32,8 @@ use crate::vminterface::Instantiator; use crate::{avm2_stub_method, avm2_stub_method_context}; use encoding_rs::UTF_8; use gc_arena::{Collect, GcCell}; -use generational_arena::{Arena, Index}; use ruffle_render::utils::{determine_jpeg_tag_format, JpegTagFormat}; +use slotmap::{DefaultKey, SlotMap}; use std::borrow::Borrow; use std::fmt; use std::sync::{Arc, Mutex, Weak}; @@ -42,7 +42,7 @@ use swf::read::{extract_swz, read_compression_type}; use thiserror::Error; use url::{form_urlencoded, ParseError, Url}; -pub type Handle = Index; +pub type Handle = DefaultKey; /// The depth of AVM1 movies that AVM2 loads. const LOADER_INSERTED_AVM1_DEPTH: i32 = -0xF000; @@ -224,7 +224,7 @@ impl From> for Error { } /// Holds all in-progress loads for the player. -pub struct LoadManager<'gc>(Arena>); +pub struct LoadManager<'gc>(SlotMap>); unsafe impl<'gc> Collect for LoadManager<'gc> { fn trace(&self, cc: &gc_arena::Collection) { @@ -237,7 +237,7 @@ unsafe impl<'gc> Collect for LoadManager<'gc> { impl<'gc> LoadManager<'gc> { /// Construct a new `LoadManager`. pub fn new() -> Self { - Self(Arena::new()) + Self(SlotMap::new()) } /// Add a new loader to the `LoadManager`. @@ -355,15 +355,24 @@ impl<'gc> LoadManager<'gc> { /// /// This also removes all movie loaders that have completed. pub fn movie_clip_on_load(&mut self, queue: &mut ActionQueue<'gc>) { - let mut invalidated_loaders = vec![]; + // FIXME: This relies on the iteration order of the slotmap, which + // is not defined. The container should be replaced with something + // that preserves insertion order, such as `LinkedHashMap` - + // unfortunately that doesn't provide automatic key generation. + let mut loaders: Vec<_> = self.0.keys().collect(); + // `SlotMap` doesn't provide reverse iteration, so reversing afterwards. + loaders.reverse(); - for (index, loader) in self.0.iter_mut().rev() { - if loader.movie_clip_loaded(queue) { - invalidated_loaders.push(index); - } - } + // Removing the keys from `loaders` whose movie hasn't loaded yet. + loaders.retain(|handle| { + self.0 + .get_mut(*handle) + .expect("valid key") + .movie_clip_loaded(queue) + }); - for index in invalidated_loaders { + // Cleaning up the loaders that are done. + for index in loaders { self.0.remove(index); } } @@ -1692,7 +1701,7 @@ impl<'gc> Loader<'gc> { } /// Report a movie loader start event to script code. - fn movie_loader_start(handle: Index, uc: &mut UpdateContext<'_, 'gc>) -> Result<(), Error> { + fn movie_loader_start(handle: Handle, uc: &mut UpdateContext<'_, 'gc>) -> Result<(), Error> { let me = uc.load_manager.get_loader_mut(handle); if me.is_none() { return Err(Error::Cancelled); @@ -2080,7 +2089,7 @@ impl<'gc> Loader<'gc> { /// /// The current and total length are always reported as compressed lengths. fn movie_loader_progress( - handle: Index, + handle: Handle, uc: &mut UpdateContext<'_, 'gc>, cur_len: usize, total_len: usize, @@ -2146,7 +2155,7 @@ impl<'gc> Loader<'gc> { /// Report a movie loader completion to script code. fn movie_loader_complete( - handle: Index, + handle: Handle, uc: &mut UpdateContext<'_, 'gc>, dobj: Option>, status: u16, @@ -2315,7 +2324,7 @@ impl<'gc> Loader<'gc> { /// This is an associated function because we cannot borrow both the update /// context and one of it's loaders. fn movie_loader_error( - handle: Index, + handle: Handle, uc: &mut UpdateContext<'_, 'gc>, msg: AvmString<'gc>, status: u16, diff --git a/core/src/local_connection.rs b/core/src/local_connection.rs index 1f936b85b..b1bffb7f4 100644 --- a/core/src/local_connection.rs +++ b/core/src/local_connection.rs @@ -2,9 +2,9 @@ use crate::avm1::Object as Avm1Object; use crate::avm2::object::LocalConnectionObject; use crate::string::AvmString; use gc_arena::Collect; -use generational_arena::{Arena, Index}; +use slotmap::{DefaultKey, SlotMap}; -pub type LocalConnectionHandle = Index; +pub type LocalConnectionHandle = DefaultKey; #[derive(Collect)] #[collect(no_drop)] @@ -41,7 +41,7 @@ impl<'gc> LocalConnection<'gc> { /// Manages the collection of local connections. pub struct LocalConnections<'gc> { - connections: Arena>, + connections: SlotMap>, } unsafe impl<'gc> Collect for LocalConnections<'gc> { @@ -55,7 +55,7 @@ unsafe impl<'gc> Collect for LocalConnections<'gc> { impl<'gc> LocalConnections<'gc> { pub fn empty() -> Self { Self { - connections: Arena::new(), + connections: SlotMap::new(), } } diff --git a/core/src/net_connection.rs b/core/src/net_connection.rs index 735175158..e2c648bc6 100644 --- a/core/src/net_connection.rs +++ b/core/src/net_connection.rs @@ -10,12 +10,12 @@ use crate::Player; use flash_lso::packet::{Header, Message, Packet}; use flash_lso::types::{AMFVersion, Value as AmfValue}; use gc_arena::{Collect, DynamicRoot, Rootable}; -use generational_arena::{Arena, Index}; +use slotmap::{DefaultKey, SlotMap}; use std::fmt::{Debug, Formatter}; use std::rc::Rc; use std::sync::{Mutex, Weak}; -pub type NetConnectionHandle = Index; +pub type NetConnectionHandle = DefaultKey; #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum ResponderCallback { @@ -76,7 +76,7 @@ impl<'gc> From> for NetConnectionObject<'gc> { /// Manages the collection of NetConnections. pub struct NetConnections<'gc> { - connections: Arena>, + connections: SlotMap>, } unsafe impl<'gc> Collect for NetConnections<'gc> { @@ -90,7 +90,7 @@ unsafe impl<'gc> Collect for NetConnections<'gc> { impl<'gc> Default for NetConnections<'gc> { fn default() -> Self { Self { - connections: Arena::new(), + connections: SlotMap::new(), } } } diff --git a/core/src/socket.rs b/core/src/socket.rs index 3ba328fbc..1a91446a2 100644 --- a/core/src/socket.rs +++ b/core/src/socket.rs @@ -13,13 +13,13 @@ use crate::{ }; use async_channel::{unbounded, Receiver, Sender as AsyncSender, Sender}; use gc_arena::Collect; -use generational_arena::{Arena, Index}; +use slotmap::{DefaultKey, SlotMap}; use std::{ cell::{Cell, RefCell}, time::Duration, }; -pub type SocketHandle = Index; +pub type SocketHandle = DefaultKey; #[derive(Copy, Clone, Collect)] #[collect(no_drop)] @@ -62,7 +62,7 @@ pub enum SocketAction { /// Manages the collection of Sockets. pub struct Sockets<'gc> { - sockets: Arena>, + sockets: SlotMap>, receiver: Receiver, sender: Sender, @@ -81,7 +81,7 @@ impl<'gc> Sockets<'gc> { let (sender, receiver) = unbounded(); Self { - sockets: Arena::new(), + sockets: SlotMap::new(), receiver, sender, } diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index e9000e83b..3e0fb2a0c 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -25,7 +25,7 @@ ruffle_render_wgpu = { path = "../render/wgpu", features = ["clap"] } ruffle_video_software = { path = "../video/software", optional = true } tracing = { workspace = true} tracing-subscriber = { workspace = true } -generational-arena = "0.2.9" +slotmap = { workspace = true } winit = "0.29.13" webbrowser = "0.8.12" url = "2.5.0" diff --git a/desktop/src/backends/navigator.rs b/desktop/src/backends/navigator.rs index 64a3d0e50..bf099c498 100644 --- a/desktop/src/backends/navigator.rs +++ b/desktop/src/backends/navigator.rs @@ -677,7 +677,7 @@ mod tests { macro_rules! dummy_handle { () => { - SocketHandle::from_raw_parts(4, 2) + SocketHandle::default() }; } diff --git a/desktop/src/executor.rs b/desktop/src/executor.rs index 2b3d62ce1..f8c892d6f 100644 --- a/desktop/src/executor.rs +++ b/desktop/src/executor.rs @@ -3,9 +3,9 @@ use crate::custom_event::RuffleEvent; use crate::task::Task; use async_channel::{unbounded, Receiver, Sender}; -use generational_arena::{Arena, Index}; use ruffle_core::backend::navigator::OwnedFuture; use ruffle_core::loader::Error; +use slotmap::{DefaultKey, SlotMap}; use std::sync::{Arc, Mutex, Weak}; use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; use winit::event_loop::EventLoopProxy; @@ -17,7 +17,7 @@ use winit::event_loop::EventLoopProxy; #[derive(Clone)] struct TaskHandle { /// The arena handle for a given task. - handle: Index, + handle: DefaultKey, /// The executor the task belongs to. /// @@ -28,7 +28,7 @@ struct TaskHandle { impl TaskHandle { /// Construct a handle to a given task. - fn for_task(task: Index, executor: Weak>) -> Self { + fn for_task(task: DefaultKey, executor: Weak>) -> Self { Self { handle: task, executor, @@ -128,7 +128,7 @@ impl TaskHandle { pub struct WinitAsyncExecutor { /// List of all spawned tasks. - task_queue: Arena, + task_queue: SlotMap, /// Source of tasks sent to us by the `NavigatorBackend`. channel: Receiver>, @@ -152,7 +152,7 @@ impl WinitAsyncExecutor { let (send, recv) = unbounded(); let new_self = Arc::new_cyclic(|self_ref| { Mutex::new(Self { - task_queue: Arena::new(), + task_queue: SlotMap::new(), channel: recv, self_ref: self_ref.clone(), event_loop: event_loop.clone(), @@ -200,7 +200,7 @@ impl WinitAsyncExecutor { } /// Mark a task as ready to proceed. - fn wake(&mut self, task: Index) { + fn wake(&mut self, task: DefaultKey) { if let Some(task) = self.task_queue.get_mut(task) { if !task.is_completed() { task.set_ready(); diff --git a/video/Cargo.toml b/video/Cargo.toml index ba0e0ab23..8020af5c6 100644 --- a/video/Cargo.toml +++ b/video/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] swf = { path = "../swf" } ruffle_render = { path = "../render" } -generational-arena = "0.2.9" +slotmap = { workspace = true } thiserror = "1.0" [features] diff --git a/video/software/Cargo.toml b/video/software/Cargo.toml index f698b9985..e0c2d65da 100644 --- a/video/software/Cargo.toml +++ b/video/software/Cargo.toml @@ -14,7 +14,7 @@ workspace = true ruffle_render = { path = "../../render" } ruffle_video = { path = ".." } swf = { path = "../../swf" } -generational-arena = "0.2.9" +slotmap = { workspace = true } thiserror = "1.0" flate2 = "1.0.28" log = "0.4" diff --git a/video/software/src/backend.rs b/video/software/src/backend.rs index 3f59bcdf3..d5d8b3ba3 100644 --- a/video/software/src/backend.rs +++ b/video/software/src/backend.rs @@ -1,17 +1,17 @@ use crate::decoder::VideoDecoder; -use generational_arena::Arena; use ruffle_render::backend::RenderBackend; use ruffle_render::bitmap::{BitmapHandle, BitmapInfo, PixelRegion}; use ruffle_video::backend::VideoBackend; use ruffle_video::error::Error; use ruffle_video::frame::{EncodedFrame, FrameDependency}; use ruffle_video::VideoStreamHandle; +use slotmap::SlotMap; use swf::{VideoCodec, VideoDeblocking}; /// Software video backend that proxies to CPU-only codec implementations that /// ship with Ruffle. pub struct SoftwareVideoBackend { - streams: Arena, + streams: SlotMap, } impl Default for SoftwareVideoBackend { @@ -23,7 +23,7 @@ impl Default for SoftwareVideoBackend { impl SoftwareVideoBackend { pub fn new() -> Self { Self { - streams: Arena::new(), + streams: SlotMap::new(), } } } diff --git a/video/src/lib.rs b/video/src/lib.rs index 3c7152fc5..fb7885fa5 100644 --- a/video/src/lib.rs +++ b/video/src/lib.rs @@ -1,10 +1,10 @@ #![deny(clippy::unwrap_used)] -use generational_arena::Index; +use slotmap::DefaultKey; pub mod backend; pub mod error; pub mod frame; pub mod null; -pub type VideoStreamHandle = Index; +pub type VideoStreamHandle = DefaultKey; diff --git a/video/src/null.rs b/video/src/null.rs index 6d537f583..f51d3faed 100644 --- a/video/src/null.rs +++ b/video/src/null.rs @@ -2,13 +2,13 @@ use crate::backend::VideoBackend; use crate::error::Error; use crate::frame::{EncodedFrame, FrameDependency}; use crate::VideoStreamHandle; -use generational_arena::Arena; use ruffle_render::backend::RenderBackend; use ruffle_render::bitmap::BitmapInfo; +use slotmap::SlotMap; use swf::{VideoCodec, VideoDeblocking}; pub struct NullVideoBackend { - streams: Arena<()>, + streams: SlotMap, } /// Implementation of video that does not decode any video. @@ -22,7 +22,7 @@ pub struct NullVideoBackend { impl NullVideoBackend { pub fn new() -> Self { Self { - streams: Arena::new(), + streams: SlotMap::new(), } } } diff --git a/web/Cargo.toml b/web/Cargo.toml index 2345548bd..25a6f6393 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -32,7 +32,7 @@ profiling = [] [dependencies] console_error_panic_hook = { version = "0.1.7", optional = true } -generational-arena = "0.2.9" +slotmap = { workspace = true } js-sys = "0.3.68" tracing = { workspace = true, features = ["log"] } tracing-subscriber = { version = "0.3.18", default-features = false, features = ["registry"] } diff --git a/web/src/lib.rs b/web/src/lib.rs index 4c86b7e52..42d28983b 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -7,7 +7,6 @@ mod navigator; mod storage; mod ui; -use generational_arena::{Arena, Index}; use js_sys::{Array, Error as JsError, Function, Object, Promise, Uint8Array}; use ruffle_core::backend::navigator::OpenURLMode; use ruffle_core::backend::ui::FontDefinition; @@ -29,6 +28,7 @@ use ruffle_render::quality::StageQuality; use ruffle_video_software::backend::SoftwareVideoBackend; use ruffle_web_common::JsResult; use serde::{Deserialize, Serialize}; +use slotmap::{DefaultKey, SlotMap}; use std::collections::BTreeMap; use std::rc::Rc; use std::str::FromStr; @@ -52,7 +52,7 @@ thread_local! { /// We store the actual instances of the ruffle core in a static pool. /// This gives us a clear boundary between the JS side and Rust side, avoiding /// issues with lifetimes and type parameters (which cannot be exported with wasm-bindgen). - static INSTANCES: RefCell>> = RefCell::new(Arena::new()); + static INSTANCES: RefCell>> = RefCell::new(SlotMap::new()); static CURRENT_CONTEXT: RefCell>> = const { RefCell::new(None) }; } @@ -339,7 +339,7 @@ struct MovieMetadata { /// This type is exported to JS, and is used to interact with the library. #[wasm_bindgen] #[derive(Clone, Copy)] -pub struct Ruffle(Index); +pub struct Ruffle(DefaultKey); #[wasm_bindgen] impl Ruffle {