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:
parent
bebadb450f
commit
dbcefd060f
|
@ -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.
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {}
|
|
@ -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 {}
|
||||||
|
|
|
@ -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...
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue