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::array::ArrayStorage;
|
||||
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::flv::FlvValueAvm2Ext;
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn playerglobals_domain(&self) -> Domain<'gc> {
|
||||
self.playerglobals_domain
|
||||
}
|
||||
|
||||
/// Return the current set of system classes.
|
||||
///
|
||||
/// This function panics if the interpreter has not yet been initialized.
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! Application Domains
|
||||
|
||||
use std::cell::Ref;
|
||||
|
||||
use crate::avm2::activation::Activation;
|
||||
use crate::avm2::object::{ByteArrayObject, TObject};
|
||||
use crate::avm2::property_map::PropertyMap;
|
||||
|
@ -8,7 +10,7 @@ use crate::avm2::value::Value;
|
|||
use crate::avm2::Error;
|
||||
use crate::avm2::Multiname;
|
||||
use crate::avm2::QName;
|
||||
use gc_arena::{Collect, GcCell, Mutation};
|
||||
use gc_arena::{Collect, GcCell, GcWeakCell, Mutation};
|
||||
use ruffle_wstr::WStr;
|
||||
|
||||
use super::class::Class;
|
||||
|
@ -22,6 +24,10 @@ use super::Avm2;
|
|||
#[collect(no_drop)]
|
||||
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)]
|
||||
#[collect(no_drop)]
|
||||
struct DomainData<'gc> {
|
||||
|
@ -44,6 +50,10 @@ struct DomainData<'gc> {
|
|||
pub 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;
|
||||
|
@ -58,7 +68,7 @@ impl<'gc> Domain<'gc> {
|
|||
/// You must initialize domain memory later on after the ByteArray class is
|
||||
/// instantiated but before user code runs.
|
||||
pub fn uninitialized_domain(mc: &Mutation<'gc>, parent: Option<Domain<'gc>>) -> Domain<'gc> {
|
||||
Self(GcCell::new(
|
||||
let domain = Self(GcCell::new(
|
||||
mc,
|
||||
DomainData {
|
||||
defs: PropertyMap::new(),
|
||||
|
@ -66,14 +76,41 @@ impl<'gc> Domain<'gc> {
|
|||
parent,
|
||||
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 {
|
||||
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.
|
||||
///
|
||||
/// This function must not be called before the player globals have been
|
||||
|
@ -87,11 +124,18 @@ impl<'gc> Domain<'gc> {
|
|||
parent: Some(parent),
|
||||
domain_memory: None,
|
||||
default_domain_memory: None,
|
||||
children: Vec::new(),
|
||||
},
|
||||
));
|
||||
|
||||
this.init_default_domain_memory(activation).unwrap();
|
||||
|
||||
parent
|
||||
.0
|
||||
.write(activation.context.gc_context)
|
||||
.children
|
||||
.push(DomainWeak(GcCell::downgrade(this.0)));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
|
@ -376,8 +420,14 @@ impl<'gc> Domain<'gc> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn as_ptr(self) -> *const DomainPtr {
|
||||
self.0.as_ptr() as _
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DomainPtr {}
|
||||
|
||||
impl<'gc> PartialEq for Domain<'gc> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.as_ptr() == other.0.as_ptr()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
mod avm1;
|
||||
mod avm2;
|
||||
mod display_object;
|
||||
mod domain;
|
||||
mod handle;
|
||||
mod movie;
|
||||
|
||||
|
@ -8,7 +9,10 @@ use crate::context::{RenderContext, UpdateContext};
|
|||
use crate::debug_ui::avm1::Avm1ObjectWindow;
|
||||
use crate::debug_ui::avm2::Avm2ObjectWindow;
|
||||
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::display_object::TDisplayObject;
|
||||
use crate::tag_utils::SwfMovie;
|
||||
|
@ -27,21 +31,25 @@ pub struct DebugUi {
|
|||
movies: PtrWeakKeyHashMap<Weak<SwfMovie>, MovieWindow>,
|
||||
avm1_objects: HashMap<AVM1ObjectHandle, Avm1ObjectWindow>,
|
||||
avm2_objects: HashMap<AVM2ObjectHandle, Avm2ObjectWindow>,
|
||||
domains: HashMap<DomainHandle, DomainListWindow>,
|
||||
queued_messages: Vec<Message>,
|
||||
items_to_save: Vec<ItemToSave>,
|
||||
movie_list: Option<MovieListWindow>,
|
||||
domain_list: Option<DomainListWindow>,
|
||||
display_object_search: Option<DisplayObjectSearchWindow>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Message {
|
||||
TrackDisplayObject(DisplayObjectHandle),
|
||||
TrackDomain(DomainHandle),
|
||||
TrackMovie(Arc<SwfMovie>),
|
||||
TrackAVM1Object(AVM1ObjectHandle),
|
||||
TrackAVM2Object(AVM2ObjectHandle),
|
||||
TrackStage,
|
||||
TrackTopLevelMovie,
|
||||
ShowKnownMovies,
|
||||
ShowDomains,
|
||||
SaveFile(ItemToSave),
|
||||
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 search.show(egui_ctx, context, &mut messages, movie_offset) {
|
||||
self.display_object_search = Some(search);
|
||||
|
@ -90,6 +104,9 @@ impl DebugUi {
|
|||
Message::TrackDisplayObject(object) => {
|
||||
self.track_display_object(object);
|
||||
}
|
||||
Message::TrackDomain(domain) => {
|
||||
self.domains.insert(domain, Default::default());
|
||||
}
|
||||
Message::TrackStage => {
|
||||
self.track_display_object(DisplayObjectHandle::new(context, context.stage));
|
||||
}
|
||||
|
@ -111,6 +128,9 @@ impl DebugUi {
|
|||
Message::ShowKnownMovies => {
|
||||
self.movie_list = Some(Default::default());
|
||||
}
|
||||
Message::ShowDomains => {
|
||||
self.domain_list = Some(Default::default());
|
||||
}
|
||||
Message::SearchForDisplayObject => {
|
||||
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 {}
|
||||
|
||||
// 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-movie = View Movie
|
||||
debug-menu-open-movie-list = Show Known Movies
|
||||
debug-menu-open-domain-list = Show Domains
|
||||
debug-menu-search-display-objects = Search Display Objects...
|
||||
|
||||
|
|
|
@ -295,6 +295,12 @@ impl RuffleGui {
|
|||
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() {
|
||||
ui.close_menu();
|
||||
if let Some(player) = &mut player {
|
||||
|
|
Loading…
Reference in New Issue