mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-05 07:18:29 +08:00
Added a VM registers viewer.
This commit is contained in:
parent
8d4b149fba
commit
8c102976a8
@ -59,3 +59,17 @@ export function assert(condition: any, msg?: string): asserts condition {
|
||||
throw new Error(full_msg);
|
||||
}
|
||||
}
|
||||
|
||||
export function number_to_hex_string(
|
||||
num: number,
|
||||
min_len: number = 8,
|
||||
prefix: string = "0x",
|
||||
): string {
|
||||
let result = num.toString(16);
|
||||
|
||||
if (result.length < min_len) {
|
||||
result = "0".repeat(min_len - result.length) + result;
|
||||
}
|
||||
|
||||
return prefix + result;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ function srcloc_to_string(srcloc: AsmToken): string {
|
||||
}
|
||||
|
||||
export class QuestRunner {
|
||||
private readonly vm: VirtualMachine;
|
||||
public readonly vm: VirtualMachine;
|
||||
private quest?: QuestModel;
|
||||
private animation_frame?: number;
|
||||
/**
|
||||
|
@ -17,6 +17,7 @@ import { ObjectListView } from "./ObjectListView";
|
||||
import { EventsView } from "./EventsView";
|
||||
import Logger = require("js-logger");
|
||||
import { QuestMessageLogView } from "./QuestMessageLogView";
|
||||
import { RegistersView } from "./RegistersView";
|
||||
|
||||
const logger = Logger.get("quest_editor/gui/QuestEditorView");
|
||||
|
||||
@ -30,6 +31,7 @@ const VIEW_TO_NAME = new Map<new () => ResizableWidget, string>([
|
||||
[NpcListView, "npc_list_view"],
|
||||
[ObjectListView, "object_list_view"],
|
||||
[QuestMessageLogView, "message_log_view"],
|
||||
[RegistersView, "registers_view"],
|
||||
]);
|
||||
|
||||
if (gui_store.feature_active("events")) {
|
||||
@ -106,7 +108,13 @@ const DEFAULT_LAYOUT_CONTENT: ItemConfigType[] = [
|
||||
title: "Log",
|
||||
type: "component",
|
||||
componentName: VIEW_TO_NAME.get(QuestMessageLogView),
|
||||
isClosable: true,
|
||||
isClosable: false,
|
||||
},
|
||||
{
|
||||
title: "Registers",
|
||||
type: "component",
|
||||
componentName: VIEW_TO_NAME.get(RegistersView),
|
||||
isClosable: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
31
src/quest_editor/gui/RegistersView.css
Normal file
31
src/quest_editor/gui/RegistersView.css
Normal file
@ -0,0 +1,31 @@
|
||||
.quest_editor_RegistersView {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.quest_editor_RegistersView_container {
|
||||
overflow: auto;
|
||||
height: 97%;
|
||||
}
|
||||
|
||||
.quest_editor_RegistersView_list {
|
||||
padding: 0.5em;
|
||||
column-width: 9.8em;
|
||||
}
|
||||
|
||||
.quest_editor_RegistersView_register {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.quest_editor_RegistersView_register > .core_Label {
|
||||
width: 2.8em;
|
||||
}
|
||||
|
||||
.quest_editor_RegistersView_value {
|
||||
width: 7em;
|
||||
}
|
||||
|
||||
.quest_editor_RegistersView_value > .core_TextInput_inner {
|
||||
text-align: right;
|
||||
font-family: monospace;
|
||||
}
|
174
src/quest_editor/gui/RegistersView.ts
Normal file
174
src/quest_editor/gui/RegistersView.ts
Normal file
@ -0,0 +1,174 @@
|
||||
import { ResizableWidget } from "../../core/gui/ResizableWidget";
|
||||
import { quest_editor_store } from "../stores/QuestEditorStore";
|
||||
import { el } from "../../core/gui/dom";
|
||||
import { REGISTER_COUNT } from "../scripting/vm";
|
||||
import { TextInput } from "../../core/gui/TextInput";
|
||||
import { DropDown } from "../../core/gui/DropDown";
|
||||
import { ToolBar } from "../../core/gui/ToolBar";
|
||||
import { CheckBox } from "../../core/gui/CheckBox";
|
||||
import { number_to_hex_string } from "../../core/util";
|
||||
import "./RegistersView.css";
|
||||
|
||||
enum RegisterDisplayType {
|
||||
Signed,
|
||||
Unsigned,
|
||||
Word,
|
||||
Byte,
|
||||
Float,
|
||||
}
|
||||
|
||||
type RegisterGetterFunction = (register: number) => number;
|
||||
|
||||
export class RegistersView extends ResizableWidget {
|
||||
private readonly type_dropdown = this.disposable(
|
||||
new DropDown(
|
||||
"Display type",
|
||||
[
|
||||
RegisterDisplayType.Signed,
|
||||
RegisterDisplayType.Unsigned,
|
||||
RegisterDisplayType.Word,
|
||||
RegisterDisplayType.Byte,
|
||||
RegisterDisplayType.Float,
|
||||
],
|
||||
type => RegisterDisplayType[type],
|
||||
{
|
||||
tooltip: "Select which data type register values should be displayed as.",
|
||||
},
|
||||
),
|
||||
);
|
||||
private register_getter: RegisterGetterFunction = this.get_register_getter(
|
||||
RegisterDisplayType.Unsigned,
|
||||
);
|
||||
|
||||
private readonly hex_checkbox = this.disposable(
|
||||
new CheckBox(false, {
|
||||
label: "Hex",
|
||||
tooltip: "Display register values in hexadecimal.",
|
||||
}),
|
||||
);
|
||||
|
||||
private readonly settings_bar = this.disposable(
|
||||
new ToolBar({
|
||||
children: [this.type_dropdown, this.hex_checkbox],
|
||||
}),
|
||||
);
|
||||
|
||||
private readonly register_els: TextInput[];
|
||||
private readonly list_element = el.div({ class: "quest_editor_RegistersView_list" });
|
||||
private readonly container_element = el.div(
|
||||
{ class: "quest_editor_RegistersView_container" },
|
||||
this.list_element,
|
||||
);
|
||||
public readonly element = el.div(
|
||||
{ class: "quest_editor_RegistersView" },
|
||||
this.settings_bar.element,
|
||||
this.container_element,
|
||||
);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// create register elements
|
||||
const register_els: TextInput[] = Array(REGISTER_COUNT);
|
||||
for (let i = 0; i < REGISTER_COUNT; i++) {
|
||||
const value_el = this.disposable(
|
||||
new TextInput("", {
|
||||
class: "quest_editor_RegistersView_value",
|
||||
label: "r" + i,
|
||||
readonly: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const wrapper_el = el.div(
|
||||
{ class: "quest_editor_RegistersView_register" },
|
||||
value_el.label!.element,
|
||||
value_el.element,
|
||||
);
|
||||
|
||||
register_els[i] = value_el;
|
||||
|
||||
this.list_element.appendChild(wrapper_el);
|
||||
}
|
||||
this.register_els = register_els;
|
||||
|
||||
// predicate that indicates whether to display
|
||||
// placeholder text or the actual register values
|
||||
const should_use_placeholders = (): boolean =>
|
||||
!quest_editor_store.quest_runner.paused.val ||
|
||||
!quest_editor_store.quest_runner.running.val;
|
||||
|
||||
// set initial values
|
||||
this.update(should_use_placeholders(), this.hex_checkbox.checked.val);
|
||||
|
||||
this.disposables(
|
||||
// check if values need to be updated
|
||||
// when QuestRunner execution state changes
|
||||
quest_editor_store.quest_runner.running.observe(() =>
|
||||
this.update(should_use_placeholders(), this.hex_checkbox.checked.val),
|
||||
),
|
||||
quest_editor_store.quest_runner.paused.observe(() =>
|
||||
this.update(should_use_placeholders(), this.hex_checkbox.checked.val),
|
||||
),
|
||||
|
||||
this.type_dropdown.chosen.observe(change => {
|
||||
this.register_getter = this.get_register_getter(change.value);
|
||||
this.update(should_use_placeholders(), this.hex_checkbox.checked.val);
|
||||
}),
|
||||
|
||||
this.hex_checkbox.checked.observe(change =>
|
||||
this.update(should_use_placeholders(), change.value),
|
||||
),
|
||||
);
|
||||
|
||||
this.finalize_construction(RegistersView.prototype);
|
||||
}
|
||||
|
||||
private get_register_getter(typ: RegisterDisplayType): RegisterGetterFunction {
|
||||
let getter: RegisterGetterFunction;
|
||||
|
||||
switch (typ) {
|
||||
case RegisterDisplayType.Signed:
|
||||
getter = quest_editor_store.quest_runner.vm.get_register_signed;
|
||||
break;
|
||||
case RegisterDisplayType.Unsigned:
|
||||
getter = quest_editor_store.quest_runner.vm.get_register_unsigned;
|
||||
break;
|
||||
case RegisterDisplayType.Word:
|
||||
getter = quest_editor_store.quest_runner.vm.get_register_word;
|
||||
break;
|
||||
case RegisterDisplayType.Byte:
|
||||
getter = quest_editor_store.quest_runner.vm.get_register_byte;
|
||||
break;
|
||||
case RegisterDisplayType.Float:
|
||||
getter = quest_editor_store.quest_runner.vm.get_register_float;
|
||||
break;
|
||||
}
|
||||
|
||||
return getter.bind(quest_editor_store.quest_runner.vm);
|
||||
}
|
||||
|
||||
private update(use_placeholders: boolean, use_hex: boolean): void {
|
||||
if (use_placeholders) {
|
||||
const placeholder_text = "??";
|
||||
for (let i = 0; i < REGISTER_COUNT; i++) {
|
||||
const reg_el = this.register_els[i];
|
||||
|
||||
reg_el.value.set_val(placeholder_text, { silent: true });
|
||||
}
|
||||
} else if (use_hex) {
|
||||
for (let i = 0; i < REGISTER_COUNT; i++) {
|
||||
const reg_el = this.register_els[i];
|
||||
const reg_val = quest_editor_store.quest_runner.vm.get_register_unsigned(i);
|
||||
|
||||
reg_el.value.set_val(number_to_hex_string(reg_val), { silent: true });
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < REGISTER_COUNT; i++) {
|
||||
const reg_el = this.register_els[i];
|
||||
const reg_val = this.register_getter(i);
|
||||
|
||||
reg_el.value.set_val(reg_val.toString(), { silent: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -102,7 +102,7 @@ import { Episode } from "../../../core/data_formats/parsing/quest/Episode";
|
||||
import { ArrayBufferCursor } from "../../../core/data_formats/cursor/ArrayBufferCursor";
|
||||
import { Endianness } from "../../../core/data_formats/Endianness";
|
||||
|
||||
const REGISTER_COUNT = 256;
|
||||
export const REGISTER_COUNT = 256;
|
||||
const REGISTER_SIZE = 4;
|
||||
const VARIABLE_STACK_LENGTH = 16; // TODO: verify this value
|
||||
const ARG_STACK_SLOT_SIZE = 4;
|
||||
|
Loading…
Reference in New Issue
Block a user