# UNDF: UNDF-2026-000001156 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -1619,10 +1619,14 @@ impl<'gc> MovieClip<'gc> { // 4) We want to avoid creating objects just to destroy them if they aren't on // the goto frame, so we should instead aggregate the deltas into a final list // of commands, and THEN modify the children as necessary. - - // This map will maintain a map of depth -> placement commands. - // TODO: Move this to UpdateContext to avoid allocations. - let mut goto_commands: Vec> = vec![]; + + // This map will maintain a map of depth -> placement commands. + // TODO: Move this to UpdateContext to avoid allocations. + let mut goto_commands: Vec> = vec![]; + // O(1) depth lookup index: depth -> index in goto_commands Vec. + // Previously each goto_place_object/goto_remove_object call did + // iter().position() O(D) per SWF tag; a gotoAndPlay spanning F frames + // with D display objects per frame costs O(F*D^2). HashMap brings it to O(F*D). + let mut goto_depth_index: std::collections::HashMap = + std::collections::HashMap::new(); @@ -1688,8 +1692,12 @@ impl<'gc> MovieClip<'gc> { Action::Place(version) => { index += 1; self.0.goto_place_object( + &mut goto_depth_index, reader, version, &mut goto_commands, is_rewind, index, ) } Action::Remove(version) => self.goto_remove_object( + &mut goto_depth_index, reader, version, context, @@ -2143,6 +2151,7 @@ impl<'gc> MovieClip<'gc> { fn goto_remove_object<'a>( mut self, reader: &mut SwfStream<'a>, version: u8, context: &mut UpdateContext<'gc>, + goto_depth_index: &mut std::collections::HashMap, goto_commands: &mut Vec>, is_rewind: bool, from_frame: FrameNumber, @@ -2158,8 +2167,14 @@ impl<'gc> MovieClip<'gc> { }?; let depth: Depth = remove_object.depth.into(); - if let Some(i) = goto_commands.iter().position(|o| o.depth() == depth) { - goto_commands.swap_remove(i); + if let Some(&i) = goto_depth_index.get(&depth) { + let last_depth = goto_commands.last().map(|o| o.depth()); + goto_commands.swap_remove(i); + goto_depth_index.remove(&depth); + // swap_remove moves the last element to position i; update its index entry. + if let Some(d) = last_depth { + if d != depth { + goto_depth_index.insert(d, i); + } + } } @@ -3301,6 +3316,7 @@ impl<'gc> MovieClip<'gc> { fn goto_place_object<'a>( &self, reader: &mut SwfStream<'a>, version: u8, + goto_depth_index: &mut std::collections::HashMap, goto_commands: &mut Vec>, is_rewind: bool, index: usize, @@ -3325,10 +3341,15 @@ impl<'gc> MovieClip<'gc> { let depth: Depth = place_object.depth.into(); let mut goto_place = GotoPlaceObject::new( self.current_frame(), place_object, is_rewind, index, tag_start, version, ); - if let Some(i) = goto_commands.iter().position(|o| o.depth() == depth) { - goto_commands[i].merge(&mut goto_place); + if let Some(&i) = goto_depth_index.get(&depth) { + goto_commands[i].merge(&mut goto_place); } else { + goto_depth_index.insert(depth, goto_commands.len()); goto_commands.push(goto_place); }