core: Add AVM1 values to debug UI

This commit is contained in:
Nathan Adams 2023-06-05 12:36:55 +02:00
parent afdd617d29
commit aaf9ecfa32
5 changed files with 251 additions and 30 deletions

View File

@ -28,7 +28,7 @@ mod tests;
pub use activation::{Activation, ActivationIdentifier}; pub use activation::{Activation, ActivationIdentifier};
pub use debug::VariableDumper; pub use debug::VariableDumper;
pub use error::Error; pub use error::Error;
pub use function::ExecutionReason; pub use function::{Executable, ExecutionReason};
pub use globals::context_menu::make_context_menu_state; pub use globals::context_menu::make_context_menu_state;
pub use globals::shared_object::flush; pub use globals::shared_object::flush;
pub use globals::sound::start as start_sound; pub use globals::sound::start as start_sound;

View File

@ -1,10 +1,12 @@
mod avm1;
mod display_object; mod display_object;
mod handle; mod handle;
mod movie; mod movie;
use crate::context::{RenderContext, UpdateContext}; use crate::context::{RenderContext, UpdateContext};
use crate::debug_ui::avm1::Avm1ObjectWindow;
use crate::debug_ui::display_object::DisplayObjectWindow; use crate::debug_ui::display_object::DisplayObjectWindow;
use crate::debug_ui::handle::DisplayObjectHandle; use crate::debug_ui::handle::{AVM1ObjectHandle, DisplayObjectHandle};
use crate::debug_ui::movie::MovieWindow; use crate::debug_ui::movie::MovieWindow;
use crate::display_object::TDisplayObject; use crate::display_object::TDisplayObject;
use crate::tag_utils::SwfMovie; use crate::tag_utils::SwfMovie;
@ -20,6 +22,7 @@ use weak_table::PtrWeakKeyHashMap;
pub struct DebugUi { pub struct DebugUi {
display_objects: HashMap<DisplayObjectHandle, DisplayObjectWindow>, display_objects: HashMap<DisplayObjectHandle, DisplayObjectWindow>,
movies: PtrWeakKeyHashMap<Weak<SwfMovie>, MovieWindow>, movies: PtrWeakKeyHashMap<Weak<SwfMovie>, MovieWindow>,
avm1_objects: HashMap<AVM1ObjectHandle, Avm1ObjectWindow>,
queued_messages: Vec<Message>, queued_messages: Vec<Message>,
} }
@ -27,6 +30,7 @@ pub struct DebugUi {
pub enum Message { pub enum Message {
TrackDisplayObject(DisplayObjectHandle), TrackDisplayObject(DisplayObjectHandle),
TrackMovie(Arc<SwfMovie>), TrackMovie(Arc<SwfMovie>),
TrackAVM1Object(AVM1ObjectHandle),
TrackStage, TrackStage,
TrackTopLevelMovie, TrackTopLevelMovie,
} }
@ -40,6 +44,11 @@ impl DebugUi {
window.show(egui_ctx, context, object, &mut messages) window.show(egui_ctx, context, object, &mut messages)
}); });
self.avm1_objects.retain(|object, window| {
let object = object.fetch(context.dynamic_root);
window.show(egui_ctx, context, object, &mut messages)
});
self.movies self.movies
.retain(|movie, window| window.show(egui_ctx, context, movie)); .retain(|movie, window| window.show(egui_ctx, context, movie));
@ -55,6 +64,9 @@ impl DebugUi {
Message::TrackTopLevelMovie => { Message::TrackTopLevelMovie => {
self.movies.insert(context.swf.clone(), Default::default()); self.movies.insert(context.swf.clone(), Default::default());
} }
Message::TrackAVM1Object(object) => {
self.avm1_objects.insert(object, Default::default());
}
} }
} }
} }
@ -89,6 +101,15 @@ impl DebugUi {
draw_debug_rect(context, swf::Color::RED, bounds, 5.0); draw_debug_rect(context, swf::Color::RED, bounds, 5.0);
} }
} }
for (_object, window) in self.avm1_objects.iter() {
if let Some(object) = window.hovered_debug_rect() {
let object = object.fetch(dynamic_root_set);
let bounds = world_matrix * object.world_bounds();
draw_debug_rect(context, swf::Color::RED, bounds, 5.0);
}
}
} }
} }

121
core/src/debug_ui/avm1.rs Normal file
View File

@ -0,0 +1,121 @@
use crate::avm1::{Activation, ActivationIdentifier, Error, Object, TObject, Value};
use crate::context::UpdateContext;
use crate::debug_ui::display_object::open_display_object_button;
use crate::debug_ui::handle::{AVM1ObjectHandle, DisplayObjectHandle};
use crate::debug_ui::Message;
use egui::{Grid, Id, TextEdit, Ui, Window};
#[derive(Debug, Default)]
pub struct Avm1ObjectWindow {
hovered_debug_rect: Option<DisplayObjectHandle>,
}
impl Avm1ObjectWindow {
pub fn hovered_debug_rect(&self) -> Option<DisplayObjectHandle> {
self.hovered_debug_rect.clone()
}
pub fn show<'gc>(
&mut self,
egui_ctx: &egui::Context,
context: &mut UpdateContext<'_, 'gc>,
object: Object<'gc>,
messages: &mut Vec<Message>,
) -> bool {
let mut keep_open = true;
let base_clip = context.stage.into();
let mut activation = Activation::from_nothing(
context.reborrow(),
ActivationIdentifier::root("Debug"),
base_clip,
);
Window::new(object_name(object))
.id(Id::new(object.as_ptr()))
.open(&mut keep_open)
.scroll2([true, true])
.show(egui_ctx, |ui| {
Grid::new(ui.id().with("properties"))
.num_columns(2)
.show(ui, |ui| {
let mut keys = object.get_keys(&mut activation, true);
keys.sort();
for key in keys {
let value = object.get(key, &mut activation);
ui.label(key.to_string());
show_avm1_value(
ui,
&mut activation,
value,
messages,
&mut self.hovered_debug_rect,
);
ui.end_row();
}
});
});
keep_open
}
}
fn object_name(object: Object) -> String {
// TODO: Find a way to give more meaningful names here.
// Matching __proto__ to a constant and taking the constants name works, but is super expensive
if object.as_executable().is_some() {
format!("Function {:p}", object.as_ptr())
} else if object.as_array_object().is_some() {
format!("Array {:p}", object.as_ptr())
} else {
format!("Object {:p}", object.as_ptr())
}
}
pub fn show_avm1_value<'gc>(
ui: &mut Ui,
activation: &mut Activation<'_, 'gc>,
value: Result<Value<'gc>, Error<'gc>>,
messages: &mut Vec<Message>,
hover: &mut Option<DisplayObjectHandle>,
) {
match value {
Ok(Value::Undefined) => {
ui.label("Undefined");
}
Ok(Value::Null) => {
ui.label("Null");
}
Ok(Value::Bool(value)) => {
ui.label(value.to_string());
}
Ok(Value::Number(value)) => {
ui.label(value.to_string());
}
Ok(Value::String(value)) => {
TextEdit::singleline(&mut value.to_string()).show(ui);
}
Ok(Value::Object(value)) => {
if value.as_executable().is_some() {
ui.label("Function");
} else if ui.button(object_name(value)).clicked() {
messages.push(Message::TrackAVM1Object(AVM1ObjectHandle::new(
&mut activation.context,
value,
)));
}
}
Ok(Value::MovieClip(value)) => {
if let Some((_, _, object)) = value.resolve_reference(activation) {
open_display_object_button(ui, &mut activation.context, messages, object, hover);
} else {
ui.colored_label(
ui.style().visuals.error_fg_color,
format!("Unknown movieclip {}", value.path()),
);
}
}
Err(e) => {
ui.colored_label(ui.style().visuals.error_fg_color, e.to_string());
}
}
}

View File

@ -1,5 +1,6 @@
use crate::avm1::TObject;
use crate::context::UpdateContext; use crate::context::UpdateContext;
use crate::debug_ui::handle::DisplayObjectHandle; use crate::debug_ui::handle::{AVM1ObjectHandle, DisplayObjectHandle};
use crate::debug_ui::movie::open_movie_button; use crate::debug_ui::movie::open_movie_button;
use crate::debug_ui::Message; use crate::debug_ui::Message;
use crate::display_object::{DisplayObject, MovieClip, TDisplayObject, TDisplayObjectContainer}; use crate::display_object::{DisplayObject, MovieClip, TDisplayObject, TDisplayObjectContainer};
@ -268,19 +269,37 @@ impl DisplayObjectWindow {
.show(ui, |ui| { .show(ui, |ui| {
if let Some(other) = object.parent() { if let Some(other) = object.parent() {
ui.label("Parent"); ui.label("Parent");
self.display_object_button(ui, context, messages, other); open_display_object_button(
ui,
context,
messages,
other,
&mut self.hovered_debug_rect,
);
ui.end_row(); ui.end_row();
} }
if let Some(other) = object.masker() { if let Some(other) = object.masker() {
ui.label("Masker"); ui.label("Masker");
self.display_object_button(ui, context, messages, other); open_display_object_button(
ui,
context,
messages,
other,
&mut self.hovered_debug_rect,
);
ui.end_row(); ui.end_row();
} }
if let Some(other) = object.maskee() { if let Some(other) = object.maskee() {
ui.label("Maskee"); ui.label("Maskee");
self.display_object_button(ui, context, messages, other); open_display_object_button(
ui,
context,
messages,
other,
&mut self.hovered_debug_rect,
);
ui.end_row(); ui.end_row();
} }
@ -322,11 +341,11 @@ impl DisplayObjectWindow {
}); });
} }
pub fn show_position( pub fn show_position<'gc>(
&mut self, &mut self,
ui: &mut Ui, ui: &mut Ui,
context: &mut UpdateContext, context: &mut UpdateContext<'_, 'gc>,
object: DisplayObject, object: DisplayObject<'gc>,
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
) { ) {
Grid::new(ui.id().with("position")) Grid::new(ui.id().with("position"))
@ -338,6 +357,16 @@ impl DisplayObjectWindow {
ui.text_edit_singleline(&mut object.name().to_string()); ui.text_edit_singleline(&mut object.name().to_string());
ui.end_row(); ui.end_row();
if let crate::avm1::Value::Object(object) = object.object() {
ui.label("AVM1 Object");
if ui.button(format!("{:p}", object.as_ptr())).clicked() {
messages.push(Message::TrackAVM1Object(AVM1ObjectHandle::new(
context, object,
)));
}
ui.end_row();
}
ui.label("Character"); ui.label("Character");
let id = object.id(); let id = object.id();
if let Some(name) = if let Some(name) =
@ -461,7 +490,13 @@ impl DisplayObjectWindow {
if let Some(ctr) = object.as_container() { if let Some(ctr) = object.as_container() {
CollapsingState::load_with_default_open(ui.ctx(), ui.id().with(object.as_ptr()), false) CollapsingState::load_with_default_open(ui.ctx(), ui.id().with(object.as_ptr()), false)
.show_header(ui, |ui| { .show_header(ui, |ui| {
self.display_object_button(ui, context, messages, object); open_display_object_button(
ui,
context,
messages,
object,
&mut self.hovered_debug_rect,
);
}) })
.body(|ui| { .body(|ui| {
for child in ctr.iter_render_list() { for child in ctr.iter_render_list() {
@ -469,25 +504,7 @@ impl DisplayObjectWindow {
} }
}); });
} else { } else {
self.display_object_button(ui, context, messages, object); open_display_object_button(ui, context, messages, object, &mut self.hovered_debug_rect);
}
}
fn display_object_button<'gc>(
&mut self,
ui: &mut Ui,
context: &mut UpdateContext<'_, 'gc>,
messages: &mut Vec<Message>,
object: DisplayObject<'gc>,
) {
let response = ui.button(summary_name(object));
if response.hovered() {
self.hovered_debug_rect = Some(DisplayObjectHandle::new(context, object));
}
if response.clicked() {
messages.push(Message::TrackDisplayObject(DisplayObjectHandle::new(
context, object,
)));
} }
} }
} }
@ -604,3 +621,21 @@ fn blend_mode_name(mode: BlendMode) -> &'static str {
BlendMode::HardLight => "HardLight", BlendMode::HardLight => "HardLight",
} }
} }
pub fn open_display_object_button<'gc>(
ui: &mut Ui,
context: &mut UpdateContext<'_, 'gc>,
messages: &mut Vec<Message>,
object: DisplayObject<'gc>,
hover: &mut Option<DisplayObjectHandle>,
) {
let response = ui.button(summary_name(object));
if response.hovered() {
*hover = Some(DisplayObjectHandle::new(context, object));
}
if response.clicked() {
messages.push(Message::TrackDisplayObject(DisplayObjectHandle::new(
context, object,
)));
}
}

View File

@ -1,10 +1,11 @@
use crate::avm1::TObject;
use crate::context::UpdateContext; use crate::context::UpdateContext;
use crate::display_object::{DisplayObject, DisplayObjectPtr, TDisplayObject}; use crate::display_object::{DisplayObject, DisplayObjectPtr, TDisplayObject};
use gc_arena::{DynamicRoot, DynamicRootSet, Rootable}; use gc_arena::{DynamicRoot, DynamicRootSet, Rootable};
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
// TODO: Make this generic somehow, we'll want AVM1 and AVM2 object handles too // TODO: Make this generic somehow
#[derive(Clone)] #[derive(Clone)]
pub struct DisplayObjectHandle { pub struct DisplayObjectHandle {
root: DynamicRoot<Rootable![DisplayObject<'gc>]>, root: DynamicRoot<Rootable![DisplayObject<'gc>]>,
@ -50,3 +51,46 @@ impl Hash for DisplayObjectHandle {
} }
impl Eq for DisplayObjectHandle {} impl Eq for DisplayObjectHandle {}
#[derive(Clone)]
pub struct AVM1ObjectHandle {
root: DynamicRoot<Rootable![crate::avm1::Object<'gc>]>,
ptr: *const crate::avm1::ObjectPtr,
}
impl AVM1ObjectHandle {
pub fn new<'gc>(
context: &mut UpdateContext<'_, 'gc>,
object: crate::avm1::Object<'gc>,
) -> Self {
Self {
root: context.dynamic_root.stash(context.gc_context, object),
ptr: object.as_ptr(),
}
}
pub fn fetch<'gc>(&self, dynamic_root_set: DynamicRootSet<'gc>) -> crate::avm1::Object<'gc> {
*dynamic_root_set.fetch(&self.root)
}
}
impl Debug for AVM1ObjectHandle {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("AVM1ObjectHandle").field(&self.ptr).finish()
}
}
impl PartialEq<AVM1ObjectHandle> for AVM1ObjectHandle {
#[inline(always)]
fn eq(&self, other: &AVM1ObjectHandle) -> bool {
self.ptr == other.ptr
}
}
impl Hash for AVM1ObjectHandle {
fn hash<H: Hasher>(&self, state: &mut H) {
self.ptr.hash(state);
}
}
impl Eq for AVM1ObjectHandle {}