mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-05 07:18:29 +08:00
Started working on stepping execution for QuestRunner.
Step over and step in kind of work but there are still bugs. Step out is unimplemented.
This commit is contained in:
parent
6fc543bdff
commit
5c6dc50b4e
@ -41,3 +41,21 @@ export function basename(filename: string): string {
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
export function defined<T>(value: T | undefined): asserts value is T {
|
||||
if (value === undefined) {
|
||||
throw new Error("Assertion Error: value is undefined.");
|
||||
}
|
||||
}
|
||||
|
||||
export function assert(condition: any, msg?: string): asserts condition {
|
||||
if (!condition) {
|
||||
let full_msg = "Assertion Error";
|
||||
|
||||
if (msg) {
|
||||
full_msg += ": " + msg;
|
||||
}
|
||||
|
||||
throw new Error(full_msg);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,15 @@
|
||||
import { ExecutionResult, VirtualMachine } from "./scripting/vm";
|
||||
import { ExecutionResult, VirtualMachine, ExecutionLocation } from "./scripting/vm";
|
||||
import { QuestModel } from "./model/QuestModel";
|
||||
import { VirtualMachineIO } from "./scripting/vm/io";
|
||||
import { AsmToken } from "./scripting/instructions";
|
||||
import { AsmToken, SegmentType, InstructionSegment, Segment, Instruction } from "./scripting/instructions";
|
||||
import { quest_editor_store } from "./stores/QuestEditorStore";
|
||||
import { asm_editor_store } from "./stores/AsmEditorStore";
|
||||
import { defined, assert } from "../core/util";
|
||||
import {
|
||||
OP_CALL,
|
||||
OP_VA_CALL,
|
||||
OP_SWITCH_CALL,
|
||||
} from "./scripting/opcodes";
|
||||
|
||||
const logger = quest_editor_store.get_logger("quest_editor/QuestRunner");
|
||||
|
||||
@ -11,9 +17,19 @@ function srcloc_to_string(srcloc: AsmToken): string {
|
||||
return `[${srcloc.line_no}:${srcloc.col}]`;
|
||||
}
|
||||
|
||||
function execloc_to_string(execloc: ExecutionLocation) {
|
||||
return `[${execloc.seg_idx}:${execloc.inst_idx}]`;
|
||||
}
|
||||
|
||||
export class QuestRunner {
|
||||
private readonly vm: VirtualMachine;
|
||||
private quest?: QuestModel;
|
||||
private animation_frame?: number;
|
||||
/**
|
||||
* Invisible breakpoints that help with stepping over/in/out.
|
||||
*/
|
||||
private readonly stepping_breakpoints: number[] = [];
|
||||
private break_on_next = false;
|
||||
|
||||
constructor() {
|
||||
this.vm = new VirtualMachine(this.create_vm_io());
|
||||
@ -24,12 +40,72 @@ export class QuestRunner {
|
||||
cancelAnimationFrame(this.animation_frame);
|
||||
}
|
||||
|
||||
this.quest = quest;
|
||||
|
||||
this.vm.load_object_code(quest.object_code);
|
||||
this.vm.start_thread(0);
|
||||
|
||||
this.schedule_frame();
|
||||
}
|
||||
|
||||
public resume(): void {
|
||||
this.schedule_frame();
|
||||
}
|
||||
|
||||
public step_over(): void {
|
||||
const execloc = this.vm.get_current_execution_location();
|
||||
|
||||
defined(this.quest);
|
||||
|
||||
const src_segment = this.get_instruction_segment_by_index(execloc.seg_idx);
|
||||
const cur_instr = src_segment.instructions[execloc.inst_idx];
|
||||
const dst_label = this.get_step_innable_instruction_label_argument(cur_instr);
|
||||
|
||||
// nothing to step over, just break on next instruction
|
||||
if (dst_label === undefined) {
|
||||
this.break_on_next = true;
|
||||
}
|
||||
// set a breakpoint on the next line
|
||||
else {
|
||||
const next_execloc = new ExecutionLocation(execloc.seg_idx, execloc.inst_idx + 1);
|
||||
|
||||
// next line is in the next segment
|
||||
if (next_execloc.inst_idx >= src_segment.instructions.length) {
|
||||
next_execloc.seg_idx++;
|
||||
next_execloc.inst_idx = 0;
|
||||
}
|
||||
|
||||
const dst_segment = this.get_instruction_segment_by_index(next_execloc.seg_idx);
|
||||
const dst_instr = dst_segment.instructions[next_execloc.inst_idx];
|
||||
if (dst_instr.asm && dst_instr.asm.mnemonic) {
|
||||
this.stepping_breakpoints.push(dst_instr.asm.mnemonic.line_no);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public step_in(): void {
|
||||
const execloc = this.vm.get_current_execution_location();
|
||||
const src_segment = this.get_instruction_segment_by_index(execloc.seg_idx);
|
||||
const cur_instr = src_segment.instructions[execloc.inst_idx];
|
||||
const dst_label = this.get_step_innable_instruction_label_argument(cur_instr);
|
||||
|
||||
// not a step-innable instruction, behave like step-over
|
||||
if (dst_label === undefined) {
|
||||
this.step_over();
|
||||
}
|
||||
// can step-in
|
||||
else {
|
||||
const dst_segment = this.get_instruction_segment_by_label(dst_label);
|
||||
const dst_instr = dst_segment.instructions[0];
|
||||
|
||||
if (dst_instr.asm && dst_instr.asm.mnemonic) {
|
||||
this.stepping_breakpoints.push(dst_instr.asm.mnemonic.line_no);
|
||||
}
|
||||
|
||||
this.schedule_frame();
|
||||
}
|
||||
}
|
||||
|
||||
private schedule_frame(): void {
|
||||
this.animation_frame = requestAnimationFrame(this.execution_loop);
|
||||
}
|
||||
@ -37,16 +113,24 @@ export class QuestRunner {
|
||||
private execution_loop = (): void => {
|
||||
let result: ExecutionResult;
|
||||
|
||||
exec_loop:
|
||||
while (true) {
|
||||
exec_loop: while (true) {
|
||||
result = this.vm.execute();
|
||||
|
||||
const srcloc = this.vm.get_current_source_location();
|
||||
if (srcloc && asm_editor_store.breakpoints.val.includes(srcloc.line_no)) {
|
||||
asm_editor_store.set_execution_location(srcloc.line_no);
|
||||
break exec_loop;
|
||||
if (srcloc) {
|
||||
const hit_breakpoint =
|
||||
this.break_on_next ||
|
||||
asm_editor_store.breakpoints.val.includes(srcloc.line_no) ||
|
||||
this.stepping_breakpoints.includes(srcloc.line_no);
|
||||
if (hit_breakpoint) {
|
||||
this.stepping_breakpoints.length = 0;
|
||||
asm_editor_store.set_execution_location(srcloc.line_no);
|
||||
break exec_loop;
|
||||
}
|
||||
}
|
||||
|
||||
this.break_on_next = false;
|
||||
|
||||
switch (result) {
|
||||
case ExecutionResult.WaitingVsync:
|
||||
this.vm.vsync();
|
||||
@ -99,4 +183,34 @@ export class QuestRunner {
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
private get_instruction_segment_by_index(index: number): InstructionSegment {
|
||||
defined(this.quest);
|
||||
|
||||
const segment = this.quest.object_code[index];
|
||||
|
||||
assert(
|
||||
segment.type === SegmentType.Instructions,
|
||||
`Expected segment ${index} to be of type ${
|
||||
SegmentType[SegmentType.Instructions]
|
||||
}, but was ${SegmentType[segment.type]}.`,
|
||||
);
|
||||
|
||||
return segment;
|
||||
}
|
||||
|
||||
private get_instruction_segment_by_label(label: number): InstructionSegment {
|
||||
const seg_idx = this.vm.get_segment_index_by_label(label);
|
||||
return this.get_instruction_segment_by_index(seg_idx);
|
||||
}
|
||||
|
||||
private get_step_innable_instruction_label_argument(instr: Instruction): number | undefined {
|
||||
switch (instr.opcode.code) {
|
||||
case OP_VA_CALL.code:
|
||||
case OP_CALL.code:
|
||||
return instr.args[0].value;
|
||||
case OP_SWITCH_CALL.code:
|
||||
return instr.args[1].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -661,7 +661,7 @@ export class VirtualMachine {
|
||||
|
||||
this.set_episode_called = true;
|
||||
|
||||
if (this.get_current_segment_idx() !== ENTRY_SEGMENT) {
|
||||
if (this.get_current_execution_location().seg_idx !== ENTRY_SEGMENT) {
|
||||
this.io.warning(
|
||||
`Calling set_episode outside of segment ${ENTRY_SEGMENT} is not supported.`,
|
||||
srcloc,
|
||||
@ -1000,8 +1000,8 @@ export class VirtualMachine {
|
||||
return str;
|
||||
}
|
||||
|
||||
private get_current_segment_idx(): number {
|
||||
return this.thread[this.thread_idx].call_stack_top().seg_idx;
|
||||
public get_current_execution_location(): Readonly<ExecutionLocation> {
|
||||
return this.thread[this.thread_idx].call_stack_top();
|
||||
}
|
||||
|
||||
private missing_quest_data_warning(info: string, srcloc?: AsmToken): void {
|
||||
@ -1154,7 +1154,7 @@ export class VirtualMachine {
|
||||
}
|
||||
}
|
||||
|
||||
class ExecutionLocation {
|
||||
export class ExecutionLocation {
|
||||
constructor(public seg_idx: number, public inst_idx: number) {}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user