debug_ui: Add 'Show Domains' menu to view domains and classes

This opens a searchable list (similar to what we have for display
objects), which shows a tree of Domains and their associated classes.

Currently, clicking on the domain/class buttons doesn't do anything.
In a follow-up, I'm planning to add additional windows to display
information about a class.
This commit is contained in:
Aaron Hill 2024-01-08 23:00:41 -05:00 committed by TÖRÖK Attila
parent bebadb450f
commit dbcefd060f
7 changed files with 220 additions and 5 deletions

View File

@ -66,7 +66,8 @@ mod vtable;
pub use crate::avm2::activation::Activation; pub use crate::avm2::activation::Activation;
pub use crate::avm2::array::ArrayStorage; pub use crate::avm2::array::ArrayStorage;
pub use crate::avm2::call_stack::{CallNode, CallStack}; pub use crate::avm2::call_stack::{CallNode, CallStack};
pub use crate::avm2::domain::Domain; #[allow(unused)] // For debug_ui
pub use crate::avm2::domain::{Domain, DomainPtr};
pub use crate::avm2::error::Error; pub use crate::avm2::error::Error;
pub use crate::avm2::flv::FlvValueAvm2Ext; pub use crate::avm2::flv::FlvValueAvm2Ext;
pub use crate::avm2::globals::flash::ui::context_menu::make_context_menu_state; pub use crate::avm2::globals::flash::ui::context_menu::make_context_menu_state;
@ -259,6 +260,10 @@ impl<'gc> Avm2<'gc> {
globals::load_player_globals(&mut activation, globals) globals::load_player_globals(&mut activation, globals)
} }
pub fn playerglobals_domain(&self) -> Domain<'gc> {
self.playerglobals_domain
}
/// Return the current set of system classes. /// Return the current set of system classes.
/// ///
/// This function panics if the interpreter has not yet been initialized. /// This function panics if the interpreter has not yet been initialized.

View File

@ -1,5 +1,7 @@
//! Application Domains //! Application Domains
use std::cell::Ref;
use crate::avm2::activation::Activation; use crate::avm2::activation::Activation;
use crate::avm2::object::{ByteArrayObject, TObject}; use crate::avm2::object::{ByteArrayObject, TObject};
use crate::avm2::property_map::PropertyMap; use crate::avm2::property_map::PropertyMap;
@ -8,7 +10,7 @@ use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
use crate::avm2::Multiname; use crate::avm2::Multiname;
use crate::avm2::QName; use crate::avm2::QName;
use gc_arena::{Collect, GcCell, Mutation}; use gc_arena::{Collect, GcCell, GcWeakCell, Mutation};
use ruffle_wstr::WStr; use ruffle_wstr::WStr;
use super::class::Class; use super::class::Class;
@ -22,6 +24,10 @@ use super::Avm2;
#[collect(no_drop)] #[collect(no_drop)]
pub struct Domain<'gc>(GcCell<'gc, DomainData<'gc>>); pub struct Domain<'gc>(GcCell<'gc, DomainData<'gc>>);
#[derive(Copy, Clone, Collect)]
#[collect(no_drop)]
pub struct DomainWeak<'gc>(GcWeakCell<'gc, DomainData<'gc>>);
#[derive(Clone, Collect)] #[derive(Clone, Collect)]
#[collect(no_drop)] #[collect(no_drop)]
struct DomainData<'gc> { struct DomainData<'gc> {
@ -44,6 +50,10 @@ struct DomainData<'gc> {
pub domain_memory: Option<ByteArrayObject<'gc>>, pub domain_memory: Option<ByteArrayObject<'gc>>,
pub default_domain_memory: Option<ByteArrayObject<'gc>>, pub default_domain_memory: Option<ByteArrayObject<'gc>>,
/// All children of this domain. This is intended exclusively for
/// use with `debug_ui`
children: Vec<DomainWeak<'gc>>,
} }
const MIN_DOMAIN_MEMORY_LENGTH: usize = 1024; const MIN_DOMAIN_MEMORY_LENGTH: usize = 1024;
@ -58,7 +68,7 @@ impl<'gc> Domain<'gc> {
/// You must initialize domain memory later on after the ByteArray class is /// You must initialize domain memory later on after the ByteArray class is
/// instantiated but before user code runs. /// instantiated but before user code runs.
pub fn uninitialized_domain(mc: &Mutation<'gc>, parent: Option<Domain<'gc>>) -> Domain<'gc> { pub fn uninitialized_domain(mc: &Mutation<'gc>, parent: Option<Domain<'gc>>) -> Domain<'gc> {
Self(GcCell::new( let domain = Self(GcCell::new(
mc, mc,
DomainData { DomainData {
defs: PropertyMap::new(), defs: PropertyMap::new(),
@ -66,14 +76,41 @@ impl<'gc> Domain<'gc> {
parent, parent,
domain_memory: None, domain_memory: None,
default_domain_memory: None, default_domain_memory: None,
children: Vec::new(),
}, },
)) ));
if let Some(parent) = parent {
parent
.0
.write(mc)
.children
.push(DomainWeak(GcCell::downgrade(domain.0)));
}
domain
}
pub fn classes(&self) -> Ref<'_, PropertyMap<'gc, GcCell<'gc, Class<'gc>>>> {
Ref::map(self.0.read(), |r| &r.classes)
} }
pub fn is_playerglobals_domain(&self, avm2: &Avm2<'gc>) -> bool { pub fn is_playerglobals_domain(&self, avm2: &Avm2<'gc>) -> bool {
avm2.playerglobals_domain.0.as_ptr() == self.0.as_ptr() avm2.playerglobals_domain.0.as_ptr() == self.0.as_ptr()
} }
pub fn children(&self, mc: &Mutation<'gc>) -> Vec<Domain<'gc>> {
// Take this opportunity to clean up dead children.
let mut output = Vec::new();
self.0.write(mc).children.retain(|child| {
if let Some(child_cell) = GcWeakCell::upgrade(&child.0, mc) {
output.push(Domain(child_cell));
true
} else {
false
}
});
output
}
/// Create a new domain with a given parent. /// Create a new domain with a given parent.
/// ///
/// This function must not be called before the player globals have been /// This function must not be called before the player globals have been
@ -87,11 +124,18 @@ impl<'gc> Domain<'gc> {
parent: Some(parent), parent: Some(parent),
domain_memory: None, domain_memory: None,
default_domain_memory: None, default_domain_memory: None,
children: Vec::new(),
}, },
)); ));
this.init_default_domain_memory(activation).unwrap(); this.init_default_domain_memory(activation).unwrap();
parent
.0
.write(activation.context.gc_context)
.children
.push(DomainWeak(GcCell::downgrade(this.0)));
this this
} }
@ -376,8 +420,14 @@ impl<'gc> Domain<'gc> {
Ok(()) Ok(())
} }
pub fn as_ptr(self) -> *const DomainPtr {
self.0.as_ptr() as _
}
} }
pub enum DomainPtr {}
impl<'gc> PartialEq for Domain<'gc> { impl<'gc> PartialEq for Domain<'gc> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.0.as_ptr() == other.0.as_ptr() self.0.as_ptr() == other.0.as_ptr()

View File

@ -1,6 +1,7 @@
mod avm1; mod avm1;
mod avm2; mod avm2;
mod display_object; mod display_object;
mod domain;
mod handle; mod handle;
mod movie; mod movie;
@ -8,7 +9,10 @@ use crate::context::{RenderContext, UpdateContext};
use crate::debug_ui::avm1::Avm1ObjectWindow; use crate::debug_ui::avm1::Avm1ObjectWindow;
use crate::debug_ui::avm2::Avm2ObjectWindow; use crate::debug_ui::avm2::Avm2ObjectWindow;
use crate::debug_ui::display_object::{DisplayObjectSearchWindow, DisplayObjectWindow}; use crate::debug_ui::display_object::{DisplayObjectSearchWindow, DisplayObjectWindow};
use crate::debug_ui::handle::{AVM1ObjectHandle, AVM2ObjectHandle, DisplayObjectHandle}; use crate::debug_ui::domain::DomainListWindow;
use crate::debug_ui::handle::{
AVM1ObjectHandle, AVM2ObjectHandle, DisplayObjectHandle, DomainHandle,
};
use crate::debug_ui::movie::{MovieListWindow, MovieWindow}; use crate::debug_ui::movie::{MovieListWindow, MovieWindow};
use crate::display_object::TDisplayObject; use crate::display_object::TDisplayObject;
use crate::tag_utils::SwfMovie; use crate::tag_utils::SwfMovie;
@ -27,21 +31,25 @@ pub struct DebugUi {
movies: PtrWeakKeyHashMap<Weak<SwfMovie>, MovieWindow>, movies: PtrWeakKeyHashMap<Weak<SwfMovie>, MovieWindow>,
avm1_objects: HashMap<AVM1ObjectHandle, Avm1ObjectWindow>, avm1_objects: HashMap<AVM1ObjectHandle, Avm1ObjectWindow>,
avm2_objects: HashMap<AVM2ObjectHandle, Avm2ObjectWindow>, avm2_objects: HashMap<AVM2ObjectHandle, Avm2ObjectWindow>,
domains: HashMap<DomainHandle, DomainListWindow>,
queued_messages: Vec<Message>, queued_messages: Vec<Message>,
items_to_save: Vec<ItemToSave>, items_to_save: Vec<ItemToSave>,
movie_list: Option<MovieListWindow>, movie_list: Option<MovieListWindow>,
domain_list: Option<DomainListWindow>,
display_object_search: Option<DisplayObjectSearchWindow>, display_object_search: Option<DisplayObjectSearchWindow>,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum Message { pub enum Message {
TrackDisplayObject(DisplayObjectHandle), TrackDisplayObject(DisplayObjectHandle),
TrackDomain(DomainHandle),
TrackMovie(Arc<SwfMovie>), TrackMovie(Arc<SwfMovie>),
TrackAVM1Object(AVM1ObjectHandle), TrackAVM1Object(AVM1ObjectHandle),
TrackAVM2Object(AVM2ObjectHandle), TrackAVM2Object(AVM2ObjectHandle),
TrackStage, TrackStage,
TrackTopLevelMovie, TrackTopLevelMovie,
ShowKnownMovies, ShowKnownMovies,
ShowDomains,
SaveFile(ItemToSave), SaveFile(ItemToSave),
SearchForDisplayObject, SearchForDisplayObject,
} }
@ -79,6 +87,12 @@ impl DebugUi {
} }
} }
if let Some(mut domain_list) = self.domain_list.take() {
if domain_list.show(egui_ctx, context, &mut messages) {
self.domain_list = Some(domain_list);
}
}
if let Some(mut search) = self.display_object_search.take() { if let Some(mut search) = self.display_object_search.take() {
if search.show(egui_ctx, context, &mut messages, movie_offset) { if search.show(egui_ctx, context, &mut messages, movie_offset) {
self.display_object_search = Some(search); self.display_object_search = Some(search);
@ -90,6 +104,9 @@ impl DebugUi {
Message::TrackDisplayObject(object) => { Message::TrackDisplayObject(object) => {
self.track_display_object(object); self.track_display_object(object);
} }
Message::TrackDomain(domain) => {
self.domains.insert(domain, Default::default());
}
Message::TrackStage => { Message::TrackStage => {
self.track_display_object(DisplayObjectHandle::new(context, context.stage)); self.track_display_object(DisplayObjectHandle::new(context, context.stage));
} }
@ -111,6 +128,9 @@ impl DebugUi {
Message::ShowKnownMovies => { Message::ShowKnownMovies => {
self.movie_list = Some(Default::default()); self.movie_list = Some(Default::default());
} }
Message::ShowDomains => {
self.domain_list = Some(Default::default());
}
Message::SearchForDisplayObject => { Message::SearchForDisplayObject => {
self.display_object_search = Some(Default::default()); self.display_object_search = Some(Default::default());
} }

View File

@ -0,0 +1,88 @@
use egui::{collapsing_header::CollapsingState, TextEdit, Ui, Window};
use crate::{avm2::Domain, context::UpdateContext};
use super::{handle::DomainHandle, Message};
#[derive(Debug, Default)]
pub struct DomainListWindow {
search: String,
}
impl DomainListWindow {
pub fn show(
&mut self,
egui_ctx: &egui::Context,
context: &mut UpdateContext,
messages: &mut Vec<Message>,
) -> bool {
let mut keep_open = true;
Window::new("Domain List")
.open(&mut keep_open)
.show(egui_ctx, |ui| {
TextEdit::singleline(&mut self.search)
.hint_text("Search")
.show(ui);
ui.add_space(10.0);
// Let's search ascii-insensitive for QOL
let search = self.search.to_ascii_lowercase();
let domain = context.avm2.playerglobals_domain();
egui::ScrollArea::both().show(ui, |ui| {
self.show_domain(ui, context, domain, messages, &search)
});
});
keep_open
}
#[allow(clippy::only_used_in_recursion)]
pub fn show_domain<'gc>(
&mut self,
ui: &mut Ui,
context: &mut UpdateContext<'_, 'gc>,
domain: Domain<'gc>,
messages: &mut Vec<Message>,
search: &str,
) {
CollapsingState::load_with_default_open(ui.ctx(), ui.id().with(domain.as_ptr()), false)
.show_header(ui, |ui| {
open_domain_button(ui, context, messages, domain);
})
.body(|ui| {
let class_props = domain.classes();
let mut classes: Vec<_> = class_props.iter().collect();
classes.sort_by_key(|(name, _, _)| *name);
for (_, _, class) in classes {
let class_name = class.read().name().to_qualified_name(context.gc_context);
if !class_name.to_string().to_ascii_lowercase().contains(search) {
continue;
}
let response = ui.button(format!("Class {class_name}"));
if response.clicked() {
// TODO - display some kind of class info window
}
}
drop(class_props);
for child_domain in domain.children(context.gc_context) {
self.show_domain(ui, context, child_domain, messages, search);
}
});
}
}
pub fn open_domain_button<'gc>(
ui: &mut Ui,
context: &mut UpdateContext<'_, 'gc>,
messages: &mut Vec<Message>,
domain: Domain<'gc>,
) {
let response = ui.button(format!("Domain {:?}", domain.as_ptr()));
if response.clicked() {
messages.push(Message::TrackDomain(DomainHandle::new(context, domain)));
}
}
#[derive(Debug, Default)]
pub struct DomainWindow {}

View File

@ -142,3 +142,48 @@ impl Hash for AVM2ObjectHandle {
} }
impl Eq for AVM2ObjectHandle {} impl Eq for AVM2ObjectHandle {}
// Domain
#[derive(Clone)]
pub struct DomainHandle {
root: DynamicRoot<Rootable![crate::avm2::Domain<'_>]>,
ptr: *const crate::avm2::DomainPtr,
}
impl DomainHandle {
pub fn new<'gc>(
context: &mut UpdateContext<'_, 'gc>,
domain: crate::avm2::Domain<'gc>,
) -> Self {
Self {
root: context.dynamic_root.stash(context.gc_context, domain),
ptr: domain.as_ptr(),
}
}
pub fn fetch<'gc>(&self, dynamic_root_set: DynamicRootSet<'gc>) -> crate::avm2::Domain<'gc> {
*dynamic_root_set.fetch(&self.root)
}
}
impl Debug for DomainHandle {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("DomainHandle").field(&self.ptr).finish()
}
}
impl PartialEq<DomainHandle> for DomainHandle {
#[inline(always)]
fn eq(&self, other: &DomainHandle) -> bool {
self.ptr == other.ptr
}
}
impl Hash for DomainHandle {
fn hash<H: Hasher>(&self, state: &mut H) {
self.ptr.hash(state);
}
}
impl Eq for DomainHandle {}

View File

@ -30,5 +30,6 @@ debug-menu = Debug Tools
debug-menu-open-stage = View Stage Info debug-menu-open-stage = View Stage Info
debug-menu-open-movie = View Movie debug-menu-open-movie = View Movie
debug-menu-open-movie-list = Show Known Movies debug-menu-open-movie-list = Show Known Movies
debug-menu-open-domain-list = Show Domains
debug-menu-search-display-objects = Search Display Objects... debug-menu-search-display-objects = Search Display Objects...

View File

@ -295,6 +295,12 @@ impl RuffleGui {
player.debug_ui().queue_message(DebugMessage::ShowKnownMovies); player.debug_ui().queue_message(DebugMessage::ShowKnownMovies);
} }
} }
if Button::new(text(&self.locale, "debug-menu-open-domain-list")).ui(ui).clicked() {
ui.close_menu();
if let Some(player) = &mut player {
player.debug_ui().queue_message(DebugMessage::ShowDomains);
}
}
if Button::new(text(&self.locale, "debug-menu-search-display-objects")).ui(ui).clicked() { if Button::new(text(&self.locale, "debug-menu-search-display-objects")).ui(ui).clicked() {
ui.close_menu(); ui.close_menu();
if let Some(player) = &mut player { if let Some(player) = &mut player {