Added a dropdown menu that selects which thread is currently being debugged.

This commit is contained in:
jtuu 2020-04-27 04:33:27 +03:00
parent c7f4e3eb8e
commit e6bb183c46
5 changed files with 114 additions and 12 deletions

View File

@ -67,6 +67,10 @@ export class QuestRunner {
private readonly _breakpoints: WritableListProperty<Breakpoint> = list_property();
private readonly _pause_location: WritableProperty<number | undefined> = property(undefined);
private readonly _thread_ids: WritableListProperty<number> = list_property();
private readonly _debugging_thread_id: WritableProperty<number | undefined> = property(
undefined,
);
private readonly debugger: Debugger;
@ -88,6 +92,8 @@ export class QuestRunner {
);
readonly breakpoints: ListProperty<Breakpoint> = this._breakpoints;
readonly pause_location: Property<number | undefined> = this._pause_location;
readonly thread_ids: ListProperty<number> = this._thread_ids;
readonly debugging_thread_id: Property<number | undefined> = this._debugging_thread_id;
get game_state(): GameState {
return this._game_state;
@ -159,6 +165,8 @@ export class QuestRunner {
this.debugger.deactivate_breakpoints();
this._state.val = QuestRunnerState.Stopped;
this._pause_location.val = undefined;
this._debugging_thread_id.val = undefined;
this._thread_ids.clear();
this.npcs.splice(0, this.npcs.length);
this.objects.splice(0, this.objects.length);
this._game_state = new GameStateInternal(Episode.I);
@ -192,6 +200,17 @@ export class QuestRunner {
this._breakpoints.splice(0, Infinity, ...this.debugger.breakpoints);
}
set_debugging_thread(thread_id: number): void {
if (this.thread_ids.val.indexOf(thread_id) > -1) {
this._debugging_thread_id.val = thread_id;
this.vm.set_debugging_thread(thread_id);
this._pause_location.val = this.vm.get_instruction_pointer(
this.debugging_thread_id.val,
)?.source_location?.line_no;
}
}
private schedule_frame(): void {
if (this.animation_frame == undefined) {
this.animation_frame = requestAnimationFrame(this.execution_loop);
@ -213,11 +232,13 @@ export class QuestRunner {
switch (result) {
case ExecutionResult.Suspended:
this._state.val = QuestRunnerState.Running;
this.update_thread_info();
break;
case ExecutionResult.Paused:
this._state.val = QuestRunnerState.Paused;
pause_location = this.vm.get_instruction_pointer()?.source_location?.line_no;
this.update_thread_info();
break;
case ExecutionResult.WaitingVsync:
@ -256,6 +277,11 @@ export class QuestRunner {
}
};
private update_thread_info(): void {
this._thread_ids.splice(0, this._thread_ids.length.val, ...this.vm.get_thread_ids());
this._debugging_thread_id.val = this.vm.get_debugging_thread_id();
}
private create_vm_io = (): VirtualMachineIO => {
function message_with_inst_ptr(message: string, inst_ptr?: InstructionPointer): string {
const msg = [message];

View File

@ -24,6 +24,7 @@ import { Version } from "../../core/data_formats/parsing/quest/Version";
import { WritableProperty } from "../../core/observable/property/WritableProperty";
import { failure, Result } from "../../core/Result";
import { Severity } from "../../core/Severity";
import { ListProperty } from "../../core/observable/property/list/ListProperty";
const logger = LogManager.get("quest_editor/controllers/QuestEditorToolBarController");
@ -54,6 +55,9 @@ export class QuestEditorToolBarController extends Controller {
readonly can_debug: Property<boolean>;
readonly can_step: Property<boolean>;
readonly can_stop: Property<boolean>;
readonly thread_ids: ListProperty<number>;
readonly debugging_thread_id: Property<number | undefined>;
readonly can_select_thread: Property<boolean>;
readonly save_as_dialog_visible: Property<boolean> = this._save_as_dialog_visible;
readonly filename: Property<string> = this._filename;
readonly version: Property<Version> = this._version;
@ -115,6 +119,12 @@ export class QuestEditorToolBarController extends Controller {
this.can_stop = quest_editor_store.quest_runner.running;
this.thread_ids = quest_editor_store.quest_runner.thread_ids;
this.debugging_thread_id = quest_editor_store.quest_runner.debugging_thread_id;
this.can_select_thread = quest_editor_store.quest_runner.thread_ids.map(
ids => ids.length > 0 && quest_editor_store.quest_runner.running.val,
);
this.disposables(
gui_store.on_global_keydown(GuiTool.QuestEditor, "Ctrl-O", async () => {
const files = await open_files({ accept: ".bin, .dat, .qst", multiple: true });
@ -292,6 +302,10 @@ export class QuestEditorToolBarController extends Controller {
this._result_dialog_visible.val = false;
};
select_thread = (thread_id: number): void => {
this.quest_editor_store.quest_runner.set_debugging_thread(thread_id);
};
private set_result(result: Result<unknown>): void {
this._result.val = result;

View File

@ -16,6 +16,7 @@ import { TextInput } from "../../core/gui/TextInput";
import "./QuestEditorToolBarView.css";
import { Version } from "../../core/data_formats/parsing/quest/Version";
import { ResultDialog } from "../../core/gui/ResultDialog";
import { Widget } from "../../core/gui/Widget";
export class QuestEditorToolBarView extends View {
private readonly toolbar: ToolBar;
@ -109,8 +110,14 @@ export class QuestEditorToolBarView extends View {
error_message: ctrl.result_error_message,
}),
);
const thread_select = this.disposable(
new Select({
items: ctrl.thread_ids,
to_label: num => "Thread #" + num,
}),
);
const children = [
const children: Widget[] = [
new_quest_button,
open_file_button,
save_as_button,
@ -127,6 +134,7 @@ export class QuestEditorToolBarView extends View {
step_in_button,
step_out_button,
stop_button,
thread_select,
);
}
@ -229,6 +237,10 @@ export class QuestEditorToolBarView extends View {
stop_button.onclick.observe(ctrl.stop),
stop_button.enabled.bind_to(ctrl.can_stop),
thread_select.selected.observe(({ value }) => ctrl.select_thread(value!)),
thread_select.selected.bind_to(ctrl.debugging_thread_id),
thread_select.enabled.bind_to(ctrl.can_select_thread),
dialog.ondismiss.observe(ctrl.dismiss_result_dialog),
);

View File

@ -192,4 +192,8 @@ export class Thread {
);
}
}
static reset_id_counter(): void {
this.next_id = 0;
}
}

View File

@ -104,7 +104,7 @@ import { Endianness } from "../../../core/data_formats/Endianness";
import { Random } from "./Random";
import { Memory } from "./Memory";
import { InstructionPointer } from "./InstructionPointer";
import { StackFrame, StepMode, Thread } from "./Thread";
import { StepMode, Thread } from "./Thread";
import { LogManager } from "../../../core/Logger";
export const REGISTER_COUNT = 256;
@ -186,6 +186,7 @@ export class VirtualMachine {
private readonly breakpoints: InstructionPointer[] = [];
private paused = false;
private debugging_thread_id: number | undefined = undefined;
/**
* Set of unsupported opcodes that have already been logged. Each unsupported opcode will only
@ -203,7 +204,7 @@ export class VirtualMachine {
set step_mode(step_mode: StepMode) {
if (step_mode != undefined) {
const thread = this.current_thread();
const thread = this.threads.find(thread => thread.id === this.debugging_thread_id);
if (thread) {
thread.step_mode = step_mode;
@ -259,9 +260,15 @@ export class VirtualMachine {
);
}
this.threads.push(
new Thread(this.io, new InstructionPointer(seg_idx!, 0, this.object_code), area_id),
const thread = new Thread(
this.io,
new InstructionPointer(seg_idx!, 0, this.object_code),
area_id,
);
if (this.debugging_thread_id === undefined) {
this.debugging_thread_id = thread.id;
}
this.threads.push(thread);
}
/**
@ -290,6 +297,9 @@ export class VirtualMachine {
return ExecutionResult.Suspended;
}
// This thread is the one currently selected for debugging?
const debugging_current_thread = thread.id === this.debugging_thread_id;
// Get current instruction.
const frame = thread.current_stack_frame()!;
inst_ptr = frame.instruction_pointer;
@ -299,15 +309,19 @@ export class VirtualMachine {
// it's resuming.
if (!this.paused) {
switch (thread.step_mode) {
// Always pause on breakpoints regardless of selected thread.
case StepMode.BreakPoint:
if (this.breakpoints.findIndex(bp => bp.equals(inst_ptr!)) !== -1) {
this.paused = true;
this.debugging_thread_id = thread.id;
return ExecutionResult.Paused;
}
break;
// Only pause on steps if we are in the currently selected thread.
case StepMode.Over:
if (
debugging_current_thread &&
thread.step_frame &&
frame.idx <= thread.step_frame.idx &&
inst.asm?.mnemonic
@ -318,7 +332,7 @@ export class VirtualMachine {
break;
case StepMode.In:
if (inst.asm?.mnemonic) {
if (debugging_current_thread && inst.asm?.mnemonic) {
this.paused = true;
return ExecutionResult.Paused;
}
@ -326,6 +340,7 @@ export class VirtualMachine {
case StepMode.Out:
if (
debugging_current_thread &&
thread.step_frame &&
frame.idx < thread.step_frame.idx &&
inst.asm?.mnemonic
@ -395,16 +410,21 @@ export class VirtualMachine {
this._halted = true;
this.paused = false;
this.breakpoints.splice(0, Infinity);
this.debugging_thread_id = undefined;
this.unsupported_opcodes_logged.clear();
Thread.reset_id_counter();
}
}
get_current_stack_frame(): StackFrame | undefined {
return this.current_thread()?.current_stack_frame();
}
get_instruction_pointer(): InstructionPointer | undefined {
return this.get_current_stack_frame()?.instruction_pointer;
/**
* @param thread_id - If argument not given returns current thread's instruction pointer.
*/
get_instruction_pointer(thread_id?: number): InstructionPointer | undefined {
const thread =
thread_id === undefined
? this.current_thread()
: this.threads.find(thread => thread.id === thread_id);
return thread?.current_stack_frame()?.instruction_pointer;
}
get_segment_index_by_label(label: number): number {
@ -439,16 +459,42 @@ export class VirtualMachine {
}
}
set_debugging_thread(thread_id: number): void {
if (this.threads.find(thread => thread.id === thread_id)) {
this.debugging_thread_id = thread_id;
}
}
get_debugging_thread_id(): number | undefined {
return this.debugging_thread_id;
}
get_thread_ids(): number[] {
return this.threads.map(thread => thread.id);
}
private current_thread(): Thread | undefined {
return this.threads[this.thread_idx];
}
private terminate_thread(thread_idx: number): void {
const thread = this.threads[thread_idx];
this.threads.splice(thread_idx, 1);
if (thread.id === this.debugging_thread_id) {
if (this.threads.length === 0) {
this.debugging_thread_id = undefined;
} else {
this.debugging_thread_id = this.threads[0].id;
}
}
if (this.thread_idx >= thread_idx && this.thread_idx > 0) {
this.thread_idx--;
}
logger.debug(`Thread #${thread.id} terminated.`);
}
/**