core: Add AVM1 values to debug UI
This commit is contained in:
parent
afdd617d29
commit
aaf9ecfa32
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
Loading…
Reference in New Issue