Use Properties syntax highlighting for .import files

This commit is contained in:
rakkarage
2020-03-31 15:30:43 -04:00
committed by Francois Belair
parent 098fb976b6
commit 85ad8512bb
34 changed files with 2874 additions and 3503 deletions

View File

@@ -21,7 +21,7 @@ experience as comfortable as possible:
- Ctrl + click on a variable or method call to jump to its definition
- Full documentation of the Godot Engine's API supported
- Run a Godot project from VS Code
- Debug your Godot project from VS Code with breakpoints, step-in, and call stack
- Debug your Godot project from VS Code with breakpoints, step-in/out/over, variable watch, call stack, and active scene tree
![Showing the documentation on hover feature](img/godot-tools.png)
@@ -33,16 +33,6 @@ The extension adds a few entries to the VS Code Command Palette under "Godot Too
- Run the workspace as a Godot project
- List Godot's native classes
## Debugger
To configure the debugger:
1. Open the command palette:
2. `>Debug: Open launch.json`
3. Select the Debug Godot configuration.
4. Change any relevant settings.
5. Press F5 to launch.
## Settings
### Godot
@@ -64,6 +54,37 @@ You can use the following settings to configure Godot Tools:
- `gdscript_lsp_server_port` - The WebSocket server port of the GDScript language server.
- `check_status` - Check the GDScript language server connection status.
#### Debugger
To configure the debugger:
1. Open the command palette:
2. `>Debug: Open launch.json`
3. Select the Debug Godot configuration.
4. Change any relevant settings.
5. Press F5 to launch.
*Configurations*
_Required_
- "project": Absolute path to a directory with a project.godot file. Defaults to the currently open VSCode workspace with `${workspaceFolder}`.
- "port": Number that represents the port the Godot remote debugger will connect with. Defaults to `6007`.
- "address": String that represents the IP address that the Godot remote debugger will connect to. Defaults to `127.0.0.1`.
_Optional_
- "launch_game_instance": true/false. If true, an instance of Godot will be launched. Will use the path provided in `editor_path`. Defaults to `true`.
- "launch_scene": true/false. If true, and launch_game_instance is true, will launch an instance of Godot to a currently active opened TSCN file. Defaults to `false`.
- "scene_file": Path _relative to the project.godot file_ to a TSCN file. If launch_game_instance and launch_scene are both true, will use this file instead of looking for the currently active opened TSCN file.
*Usage*
- Stacktrace and variable dumps are the same as any regular debugger
- The active scene tree can be refreshed with the Refresh icon in the top right.
- Nodes can be brought to the fore in the Inspector by clicking the Eye icon next to nodes in the active scene tree, or Objects in the inspector.
- You can edit integers, floats, strings, and booleans within the inspector by clicking the pencil icon next to each.
## Issues and contributions
The [Godot Tools](https://github.com/godotengine/godot-vscode-plugin) extension

View File

@@ -117,7 +117,8 @@
"tscn",
"godot",
"gdns",
"gdnlib"
"gdnlib",
"import"
]
}
],
@@ -138,7 +139,7 @@
{
"type": "godot",
"label": "Godot Debug",
"program": "./out/debugger/debugAdapter.js",
"program": "./out/debugger/debug_adapter.js",
"runtime": "node",
"configurationAttributes": {
"launch": {
@@ -167,6 +168,16 @@
"type": "boolean",
"description": "Whether to launch an instance of the workspace's game, or wait for a debug session to connect.",
"default": true
},
"launch_scene": {
"type": "boolean",
"description": "Whether to launch an instance the currently opened TSCN file, or launch the game project. Only works with launch_game_instance being true.",
"default": false
},
"scene_file": {
"type": "string",
"description": "Relative path from the godot.project file to a TSCN file. If launch_scene and launch_game_instance are true, and this file is defined, will launch the specified file instead of looking for an active TSCN file.",
"default": ""
}
}
}
@@ -179,7 +190,8 @@
"project": "${workspaceFolder}",
"port": 6007,
"address": "127.0.0.1",
"launch_game_instance": true
"launch_game_instance": true,
"launch_scene": false
}
],
"configurationSnippets": [
@@ -192,7 +204,8 @@
"project": "${workspaceFolder}",
"port": 6007,
"address": "127.0.0.1",
"launch_game_instance": true
"launch_game_instance": true,
"launch_scene": false
}
}
]

View File

@@ -1,433 +0,0 @@
import {
TreeDataProvider,
EventEmitter,
Event,
ProviderResult,
TreeItem,
TreeItemCollapsibleState
} from "vscode";
import { RemotePropertyBuilder } from "./tree_builders";
export class InspectorProvider implements TreeDataProvider<RemoteProperty> {
private _on_did_change_tree_data: EventEmitter<
RemoteProperty | undefined
> = new EventEmitter<RemoteProperty | undefined>();
private tree: RemoteProperty | undefined;
public readonly onDidChangeTreeData: Event<RemoteProperty> | undefined = this
._on_did_change_tree_data.event;
constructor() {}
public clean_up() {
if (this.tree) {
this.tree = undefined;
this._on_did_change_tree_data.fire();
}
}
public fill_tree(
element_name: string,
class_name: string,
object_id: number,
properties: any[]
) {
this.tree = RemotePropertyBuilder.build(
element_name,
class_name,
object_id,
properties
);
this.tree.description = class_name;
this._on_did_change_tree_data.fire();
}
public getChildren(
element?: RemoteProperty
): ProviderResult<RemoteProperty[]> {
if (!this.tree) {
return Promise.resolve([]);
}
if (!element) {
return Promise.resolve([this.tree]);
} else {
return Promise.resolve(element.properties);
}
}
public getTreeItem(element: RemoteProperty): TreeItem | Thenable<TreeItem> {
return element;
}
public get_changed_value(
parents: RemoteProperty[],
property: RemoteProperty,
new_parsed_value: any
) {
let idx = parents.length - 1;
let value = parents[idx].value;
switch (value.__type__) {
case "Vector2":
{
let name = property.label;
switch (name) {
case "x":
value.x = new_parsed_value;
break;
case "y":
value.y = new_parsed_value;
break;
}
}
break;
case "Rect2":
{
let name = property.label;
let vector = parents[idx - 1].label;
switch (vector) {
case "position":
switch (name) {
case "x":
value.position.x = new_parsed_value;
break;
case "y":
value.position.y = new_parsed_value;
break;
}
break;
case "size":
switch (name) {
case "x":
value.size.x = new_parsed_value;
break;
case "y":
value.size.y = new_parsed_value;
break;
}
break;
}
}
break;
case "Vector3":
{
let name = property.label;
switch (name) {
case "x":
value.x = new_parsed_value;
break;
case "y":
value.y = new_parsed_value;
break;
case "z":
value.z = new_parsed_value;
break;
}
}
break;
case "Transform2D":
{
let name = property.label;
let vector = parents[idx - 1].label;
switch (vector) {
case "origin":
switch (name) {
case "x":
value.position.x = new_parsed_value;
break;
case "y":
value.position.y = new_parsed_value;
break;
}
break;
case "x":
switch (name) {
case "x":
value.size.x = new_parsed_value;
break;
case "y":
value.size.y = new_parsed_value;
break;
}
break;
case "y":
switch (name) {
case "x":
value.size.x = new_parsed_value;
break;
case "y":
value.size.y = new_parsed_value;
break;
}
break;
}
}
break;
case "Plane":
{
let name = property.label;
let subprop = parents[idx - 1].label;
switch (subprop) {
case "d":
value.d = new_parsed_value;
break;
case "x":
value.x = new_parsed_value;
break;
case "y":
value.y = new_parsed_value;
break;
case "z":
value.z = new_parsed_value;
break;
case "normal":
switch (name) {
case "x":
value.normal.x = new_parsed_value;
break;
case "y":
value.normal.y = new_parsed_value;
break;
case "z":
value.normal.z = new_parsed_value;
break;
}
break;
}
}
break;
case "Quat":
{
let name = property.label;
switch (name) {
case "x":
value.x = new_parsed_value;
break;
case "y":
value.y = new_parsed_value;
break;
case "z":
value.z = new_parsed_value;
break;
case "w":
value.w = new_parsed_value;
break;
}
}
break;
case "AABB":
{
let name = property.label;
let vector = parents[idx - 1].label;
switch (vector) {
case "end":
switch (name) {
case "x":
value.end.x = new_parsed_value;
break;
case "y":
value.end.y = new_parsed_value;
break;
case "z":
value.end.z = new_parsed_value;
break;
}
break;
case "position":
switch (name) {
case "x":
value.position.x = new_parsed_value;
break;
case "y":
value.position.y = new_parsed_value;
break;
case "z":
value.position.z = new_parsed_value;
break;
}
break;
case "size":
switch (name) {
case "x":
value.size.x = new_parsed_value;
break;
case "y":
value.size.y = new_parsed_value;
break;
case "z":
value.size.z = new_parsed_value;
break;
}
break;
}
}
break;
case "Basis":
{
let name = property.label;
let vector = parents[idx - 1].label;
switch (vector) {
case "x":
switch (name) {
case "x":
value.x.x = new_parsed_value;
break;
case "y":
value.x.y = new_parsed_value;
break;
case "z":
value.x.z = new_parsed_value;
break;
}
break;
case "y":
switch (name) {
case "x":
value.y.x = new_parsed_value;
break;
case "y":
value.y.y = new_parsed_value;
break;
case "z":
value.y.z = new_parsed_value;
break;
}
break;
case "z":
switch (name) {
case "x":
value.z.x = new_parsed_value;
break;
case "y":
value.z.y = new_parsed_value;
break;
case "z":
value.z.z = new_parsed_value;
break;
}
break;
}
}
break;
case "Transform":
{
let name = property.label;
let parent_name = parents[idx - 1].label;
if (
parent_name === "x" ||
parent_name === "y" ||
parent_name === "z"
) {
switch (name) {
case "x":
switch (parent_name) {
case "x":
value.basis.x.x = new_parsed_value;
break;
case "y":
value.basis.x.y = new_parsed_value;
break;
case "z":
value.basis.x.z = new_parsed_value;
break;
}
break;
case "y":
switch (parent_name) {
case "x":
value.basis.y.x = new_parsed_value;
break;
case "y":
value.basis.y.y = new_parsed_value;
break;
case "z":
value.basis.y.z = new_parsed_value;
break;
}
break;
case "z":
switch (parent_name) {
case "x":
value.basis.z.x = new_parsed_value;
break;
case "y":
value.basis.z.y = new_parsed_value;
break;
case "z":
value.basis.z.z = new_parsed_value;
break;
}
break;
}
} else {
switch (name) {
case "x":
value.origin.x = new_parsed_value;
break;
case "y":
value.origin.y = new_parsed_value;
break;
case "z":
value.origin.z = new_parsed_value;
break;
}
}
}
break;
case "Color":
{
let name = property.label;
switch (name) {
case "r":
value.r = new_parsed_value;
break;
case "g":
value.g = new_parsed_value;
break;
case "b":
value.b = new_parsed_value;
break;
case "a":
value.a = new_parsed_value;
break;
}
}
break;
default:
if (Array.isArray(value)) {
let idx = parseInt(property.label);
if (idx < value.length) {
value[idx] = new_parsed_value;
}
}
else if(value instanceof Map) {
value.set(property.parent.value.key, new_parsed_value);
}
break;
}
return value;
}
public has_tree() {
return this.tree !== undefined;
}
}
export class RemoteProperty extends TreeItem {
public changes_parent?: boolean;
public parent?: RemoteProperty;
constructor(
public label: string,
public value: any,
public object_id: number,
public properties: RemoteProperty[],
public collapsibleState?: TreeItemCollapsibleState
) {
super(label, collapsibleState);
}
}
export class RemoteObject extends RemoteProperty {}

View File

@@ -1,164 +0,0 @@
import { SceneNode } from "./scene_tree_provider";
import { RemoteProperty, RemoteObject } from "./inspector_provider";
import stringify from "../stringify";
import { TreeItemCollapsibleState } from "vscode";
export class SceneTreeBuilder {
public static build(params: any[]) {
return this.parse_next(params, { offset: 0 });
}
private static parse_next(params: any[], ofs: { offset: number }): SceneNode {
let child_count: number = params[ofs.offset++];
let name: string = params[ofs.offset++];
let class_name: string = params[ofs.offset++];
let id: number = params[ofs.offset++];
let children: SceneNode[] = [];
for (let i = 0; i < child_count; ++i) {
children.push(this.parse_next(params, ofs));
}
return new SceneNode(name, class_name, id, children);
}
}
export class RemotePropertyBuilder {
private static build_property(object_id: number, property: any[], is_dict_key?: boolean) {
let prop_name: string = property[0];
let prop_value: any = property[5];
let is_remote_object = false;
let is_primitive = false;
let child_props: RemoteProperty[] = [];
if (Array.isArray(prop_value) || prop_value instanceof Map) {
let length = 0;
let values: any[];
if (prop_value instanceof Map) {
length = prop_value.size;
let keys = Array.from(prop_value.keys());
values = keys.map(key => {
let value = prop_value.get(key);
let stringified_key = stringify(key).value;
return {
__type__: "Pair",
key: key,
value: value,
__render__: () => stringified_key
};
});
} else {
length = prop_value.length;
values = prop_value;
}
for (let i = 0; i < length; i++) {
let name = `${i}`;
let child_prop = this.build_property(object_id, [
name,
0,
0,
0,
0,
values[i]
]);
child_prop.changes_parent = true;
child_props.push(child_prop);
}
} else if (typeof prop_value === "object") {
if (prop_value.__type__ && prop_value.__type__ === "Object") {
is_remote_object = true;
} else {
for (const PROP in prop_value) {
if (PROP !== "__type__" && PROP !== "__render__") {
let name = `${PROP}`;
let child_prop = this.build_property(object_id, [
name,
0,
0,
0,
0,
prop_value[PROP]
], prop_value.__type__ === "Pair" && name === "key");
child_prop.changes_parent = true;
child_props.push(child_prop);
}
}
}
} else if (!is_dict_key) {
is_primitive = true;
}
let out_prop = new RemoteProperty(
prop_name,
prop_value,
object_id,
child_props,
child_props.length === 0
? TreeItemCollapsibleState.None
: TreeItemCollapsibleState.Collapsed
);
out_prop.properties.forEach(prop => {
prop.parent = out_prop;
});
out_prop.description = stringify(prop_value).value;
if (is_remote_object) {
out_prop.contextValue = "remote_object";
} else if (is_primitive) {
out_prop.contextValue = "editable_value";
}
return out_prop;
}
public static build(
element_name: string,
class_name: string,
object_id: number,
properties: any[][]
) {
let categories = [
["Node", 0, 0, 0, 0, undefined],
...properties.filter(value => value[5] === undefined)
];
let categorized_props: RemoteProperty[] = [];
for (let i = 0; i < categories.length - 1; i++) {
const category = categories[i];
let props =
i > 0
? properties.slice(
properties.findIndex(value => category === value) + 1,
properties.findIndex(value => categories[i + 1] === value)
)
: properties.slice(
0,
properties.findIndex(value => categories[1] === value)
);
let out_props = props.map(value => {
return this.build_property(object_id, value);
});
let category_prop = new RemoteProperty(
category[0],
undefined,
object_id,
out_props,
TreeItemCollapsibleState.Expanded
);
categorized_props.push(category_prop);
}
let out = new RemoteProperty(
element_name,
undefined,
object_id,
categorized_props,
TreeItemCollapsibleState.Expanded
);
out.description = class_name;
return out;
}
}

View File

@@ -0,0 +1,7 @@
import { Mediator } from "../mediator";
export abstract class Command {
public param_count: number = -1;
public abstract trigger(parameters: any[]): void;
}

View File

@@ -0,0 +1,162 @@
import { Command } from "./command";
import { CommandDebugEnter } from "./commands/command_debug_enter";
import { CommandOutput } from "./commands/command_output";
import { CommandStackDump } from "./commands/command_stack_dump";
import { CommandStackFrameVars } from "./commands/command_stack_frame_vars";
import { CommandNull } from "./commands/command_null";
import { CommandMessageSceneTree } from "./commands/command_message_scene_tree";
import { CommandMessageInspectObject } from "./commands/command_message_inspect_object";
import { CommandDebugExit } from "./commands/command_debug_exit";
import { VariantEncoder } from "../variables/variant_encoder";
export class CommandParser {
private commands: Map<string, () => Command> = new Map([
[
"output",
function () {
return new CommandOutput();
},
],
[
"message:scene_tree",
function () {
return new CommandMessageSceneTree();
},
],
[
"message:inspect_object",
function () {
return new CommandMessageInspectObject();
},
],
[
"message:scene_tree",
function () {
return new CommandMessageSceneTree();
},
],
[
"stack_dump",
function () {
return new CommandStackDump();
},
],
[
"stack_frame_vars",
function () {
return new CommandStackFrameVars();
},
],
[
"debug_enter",
function () {
return new CommandDebugEnter();
},
],
[
"debug_exit",
function () {
return new CommandDebugExit();
},
],
]);
private current_command?: Command;
private encoder = new VariantEncoder();
private parameters: any[] = [];
public has_command() {
return this.current_command;
}
public make_break_command(): Buffer {
return this.build_buffered_command("break");
}
public make_continue_command(): Buffer {
return this.build_buffered_command("continue");
}
public make_inspect_object_command(object_id: bigint): Buffer {
return this.build_buffered_command("inspect_object", [object_id]);
}
public make_next_command(): Buffer {
return this.build_buffered_command("next");
}
public make_remove_breakpoint_command(path_to: string, line: number): Buffer {
return this.build_buffered_command("breakpoint", [path_to, line, false]);
}
public make_request_scene_tree_command(): Buffer {
return this.build_buffered_command("request_scene_tree");
}
public make_send_breakpoint_command(path_to: string, line: number): Buffer {
return this.build_buffered_command("breakpoint", [path_to, line, true]);
}
public make_set_object_value_command(
object_id: bigint,
label: string,
new_parsed_value: any
): Buffer {
return this.build_buffered_command("set_object_property", [
object_id,
label,
new_parsed_value,
]);
}
public make_stack_dump_command(): Buffer {
return this.build_buffered_command("get_stack_dump");
}
public make_stack_frame_vars_command(frame_id: number): Buffer {
return this.build_buffered_command("get_stack_frame_vars", [frame_id]);
}
public make_step_command() {
return this.build_buffered_command("step");
}
public parse_message(dataset: any[]) {
while (dataset && dataset.length > 0) {
if (this.current_command) {
this.parameters.push(dataset.shift());
if (this.current_command.param_count !== -1) {
if (this.current_command.param_count === this.parameters.length) {
this.current_command.trigger(this.parameters);
this.current_command = undefined;
this.parameters = [];
}
} else {
this.current_command.param_count = this.parameters.shift();
if (this.current_command.param_count === 0) {
this.current_command.trigger([]);
this.current_command = undefined;
}
}
} else {
let data = dataset.shift();
if (data && this.commands.has(data)) {
this.current_command = this.commands.get(data)();
} else {
this.current_command = new CommandNull();
}
}
}
}
private build_buffered_command(command: string, parameters?: any[]) {
let command_array: any[] = [command];
if (parameters) {
parameters.forEach((param) => {
command_array.push(param);
});
}
let buffer = this.encoder.encode_variant(command_array);
return buffer;
}
}

View File

@@ -0,0 +1,9 @@
import { Command } from "../command";
import { Mediator } from "../../mediator";
export class CommandDebugEnter extends Command {
public trigger(parameters: any[]) {
let reason: string = parameters[1];
Mediator.notify("debug_enter", [reason]);
}
}

View File

@@ -0,0 +1,8 @@
import { Command } from "../command";
import { Mediator } from "../../mediator";
export class CommandDebugExit extends Command {
public trigger(parameters: any[]) {
Mediator.notify("debug_exit");
}
}

View File

@@ -0,0 +1,18 @@
import { Command } from "../command";
import { RawObject } from "../../variables/variants";
import { Mediator } from "../../mediator";
export class CommandMessageInspectObject extends Command {
public trigger(parameters: any[]) {
let id = BigInt(parameters[0]);
let class_name: string = parameters[1];
let properties: any[] = parameters[2];
let raw_object = new RawObject(class_name);
properties.forEach((prop) => {
raw_object.set(prop[0], prop[5]);
});
Mediator.notify("inspected_object", [id, raw_object]);
}
}

View File

@@ -0,0 +1,25 @@
import { Command } from "../command";
import { Mediator } from "../../mediator";
import { SceneNode } from "../../scene_tree/scene_tree_provider";
export class CommandMessageSceneTree extends Command {
public trigger(parameters: any[]) {
let scene = this.parse_next(parameters, { offset: 0 });
Mediator.notify("scene_tree", [scene]);
}
private parse_next(params: any[], ofs: { offset: number }): SceneNode {
let child_count: number = params[ofs.offset++];
let name: string = params[ofs.offset++];
let class_name: string = params[ofs.offset++];
let id: number = params[ofs.offset++];
let children: SceneNode[] = [];
for (let i = 0; i < child_count; ++i) {
children.push(this.parse_next(params, ofs));
}
return new SceneNode(name, class_name, id, children);
}
}

View File

@@ -0,0 +1,5 @@
import { Command } from "../command";
export class CommandNull extends Command {
public trigger(parameters: any[]) {}
}

View File

@@ -0,0 +1,9 @@
import { Command } from "../command";
import { Mediator } from "../../mediator";
export class CommandOutput extends Command {
public trigger(parameters: any[]) {
let lines: string[] = parameters;
Mediator.notify("output", lines);
}
}

View File

@@ -0,0 +1,17 @@
import { Command } from "../command";
import { Mediator } from "../../mediator";
import { GodotStackFrame } from "../../debug_runtime";
export class CommandStackDump extends Command {
public trigger(parameters: any[]) {
let frames: GodotStackFrame[] = parameters.map((sf, i) => {
return {
id: i,
file: sf.get("file"),
function: sf.get("function"),
line: sf.get("line"),
};
});
Mediator.notify("stack_dump", frames);
}
}

View File

@@ -0,0 +1,31 @@
import { Command } from "../command";
import { Mediator } from "../../mediator";
export class CommandStackFrameVars extends Command {
public trigger(parameters: any[]) {
let globals: any[] = [];
let locals: any[] = [];
let members: any[] = [];
let local_count = parameters[0] * 2;
let member_count = parameters[1 + local_count] * 2;
let global_count = parameters[2 + local_count + member_count] * 2;
if (local_count > 0) {
let offset = 1;
locals = parameters.slice(offset, offset + local_count);
}
if (member_count > 0) {
let offset = 2 + local_count;
members = parameters.slice(offset, offset + member_count);
}
if (global_count > 0) {
let offset = 3 + local_count + member_count;
globals = parameters.slice(offset, offset + global_count);
}
Mediator.notify("stack_frame_vars", [locals, members, globals]);
}
}

View File

@@ -1,60 +0,0 @@
export class Command {
private callback?: (
parameters: Array<boolean | number | string | {} | [] | undefined>
) => void | undefined;
private param_count = -1;
private param_count_callback?: (paramCount: number) => number;
private parameters: Array<
boolean | number | string | {} | [] | undefined
> = [];
public name: string;
constructor(
name: string,
parameters_fulfilled?: (parameters: Array<any>) => void | undefined,
modify_param_count?: (param_count: number) => number
) {
this.name = name;
this.callback = parameters_fulfilled;
this.param_count_callback = modify_param_count;
}
public append_parameters(
parameter: boolean | number | string | {} | [] | undefined
) {
if (this.param_count <= 0) {
this.param_count = parameter as number;
if (this.param_count === 0) {
if (this.callback) {
this.callback([]);
}
}
return;
}
this.parameters.push(parameter);
if (this.parameters.length === this.get_param_count()) {
if (this.callback) {
this.callback(this.parameters);
}
}
}
public chain() {
if (this.parameters.length === this.get_param_count()) {
this.parameters.length = 0;
this.param_count = -1;
return undefined;
} else {
return this;
}
}
protected get_param_count() {
return this.param_count_callback
? this.param_count_callback(this.param_count)
: this.param_count;
}
}

View File

@@ -1,58 +0,0 @@
import { Command } from "./command";
import { VariantParser } from "../variant_parser";
export class CommandBuilder {
private commands = new Map<string, Command>();
private current_command?: Command;
private dummy_command = new Command("---");
constructor() {}
public create_buffered_command(
command: string,
parser: VariantParser,
parameters?: any[]
): Buffer {
let command_array: any[] = [command];
if (parameters) {
parameters?.forEach(param => {
command_array.push(param);
});
}
let buffer = parser.encode_variant(command_array);
return buffer;
}
public parse_data(
dataset: Array<any>,
error_callback: (error: string) => void
): void {
while (dataset && dataset.length > 0) {
if (this.current_command) {
let next_command = this.current_command.chain();
if (next_command === this.current_command) {
this.current_command.append_parameters(dataset.shift());
} else {
this.current_command = next_command;
}
} else {
let data = dataset.shift();
if (data) {
let command = this.commands.get(data);
if (command) {
this.current_command = command;
} else {
error_callback(`Unsupported command: ${data}. Skipping.`);
this.current_command = this.dummy_command;
}
}
}
}
}
public register_command(command: Command) {
let name = command.name;
this.commands.set(name, command);
}
}

View File

@@ -1,147 +0,0 @@
import { CommandBuilder } from "./command_builder";
import { VariantParser } from "../variant_parser";
import net = require("net");
export class GodotCommands {
private builder: CommandBuilder;
private can_write = false;
private command_buffer: Buffer[] = [];
private connection: net.Socket | undefined;
private parser: VariantParser;
constructor(
builder: CommandBuilder,
parser: VariantParser,
connection?: net.Socket
) {
this.builder = builder;
this.parser = parser;
this.connection = connection;
}
public send_break_command() {
let buffer = this.builder.create_buffered_command("break", this.parser);
this.add_and_send(buffer);
}
public send_continue_Command() {
let buffer = this.builder.create_buffered_command("continue", this.parser);
this.add_and_send(buffer);
}
public send_inspect_object_command(object_id: number) {
let buffer = this.builder.create_buffered_command(
"inspect_object",
this.parser,
[object_id]
);
this.add_and_send(buffer);
}
public set_object_property(
object_id: number,
label: string,
new_parsed_value: any
) {
let buffer = this.builder.create_buffered_command(
"set_object_property",
this.parser,
[BigInt(object_id), label, new_parsed_value]
);
this.add_and_send(buffer);
}
public send_next_command() {
let buffer = this.builder.create_buffered_command("next", this.parser);
this.add_and_send(buffer);
}
public send_remove_breakpoint_command(file: string, line: number) {
this.send_breakpoint_command(false, file, line);
}
public send_request_scene_tree_command() {
let buffer = this.builder.create_buffered_command(
"request_scene_tree",
this.parser
);
this.add_and_send(buffer);
}
public send_set_breakpoint_command(file: string, line: number) {
this.send_breakpoint_command(true, file, line);
}
public send_skip_breakpoints_command(skip_breakpoints: boolean) {
let buffer = this.builder.create_buffered_command(
"set_skip_breakpoints",
this.parser,
[skip_breakpoints]
);
this.add_and_send(buffer);
}
public send_stack_dump_command() {
let buffer = this.builder.create_buffered_command(
"get_stack_dump",
this.parser
);
this.add_and_send(buffer);
}
public send_stack_frame_vars_command(level: number) {
let buffer = this.builder.create_buffered_command(
"get_stack_frame_vars",
this.parser,
[level]
);
this.add_and_send(buffer);
}
public send_step_command() {
let buffer = this.builder.create_buffered_command("step", this.parser);
this.add_and_send(buffer);
}
public set_can_write(value: boolean) {
this.can_write = value;
if (this.can_write) {
this.send_buffer();
}
}
public set_connection(connection: net.Socket) {
this.connection = connection;
this.can_write = true;
}
private add_and_send(buffer: Buffer) {
this.command_buffer.push(buffer);
this.send_buffer();
}
private send_breakpoint_command(set: boolean, file: string, line: number) {
let buffer = this.builder.create_buffered_command(
"breakpoint",
this.parser,
[file, line, set]
);
this.add_and_send(buffer);
}
private send_buffer() {
if (!this.connection) {
return;
}
while (this.can_write && this.command_buffer.length > 0) {
this.can_write = this.connection.write(
this.command_buffer.shift() as Buffer
);
}
}
}

View File

@@ -1,454 +0,0 @@
const TERMINATE = require("terminate");
import { EventEmitter } from "events";
import net = require("net");
import cp = require("child_process");
import path = require("path");
import { VariantParser } from "../variant_parser";
import { Command } from "./command";
import vscode = require("vscode");
import { GodotCommands } from "./godot_commands";
import { CommandBuilder } from "./command_builder";
import { GodotBreakpoint, GodotStackFrame } from "../godot_debug_runtime";
import utils = require("../../utils");
import { SceneTreeBuilder } from "../SceneTree/tree_builders";
import { SceneTreeProvider } from "../SceneTree/scene_tree_provider";
export class ServerController {
private breakpoints: { file: string; line: number }[] = [];
private builder: CommandBuilder | undefined;
private connection: net.Socket | undefined;
private emitter: EventEmitter;
private exception = "";
private godot_commands: GodotCommands | undefined;
private godot_pid: number | undefined;
private initial_output = false;
private inspected_callbacks: ((
class_name: string,
properties: any[]
) => void)[] = [];
private last_frame:
| { line: number; file: string; function: string }
| undefined;
private log_output = "";
private logging = false;
private output_channel: vscode.OutputChannel | undefined;
private parser: VariantParser | undefined;
private project_path: string;
private scope_callbacks: ((
stack_level: number,
stack_files: string[],
scopes: {
locals: { name: string; value: any }[];
members: { name: string; value: any }[];
globals: { name: string; value: any }[];
}
) => void)[] = [];
private server: net.Server | undefined;
private stack_count = 0;
private stack_files: string[] = [];
private stack_level = 0;
private stepping_out = false;
private tree_provider: SceneTreeProvider | undefined;
constructor(
event_emitter: EventEmitter,
output_channel?: vscode.OutputChannel,
tree_provider?: SceneTreeProvider
) {
this.emitter = event_emitter;
this.output_channel = output_channel;
this.tree_provider = tree_provider;
}
public break() {
this.godot_commands?.send_break_command();
}
public continue() {
this.godot_commands?.send_continue_Command();
}
public get_scope(
level: number,
callback?: (
stack_level: number,
stack_files: string[],
scopes: {
locals: { name: string; value: any }[];
members: { name: string; value: any }[];
globals: { name: string; value: any }[];
}
) => void
) {
this.godot_commands?.send_stack_frame_vars_command(level);
this.stack_level = level;
if (callback) {
this.scope_callbacks.push(callback);
}
}
public inspect_object(
id: number,
inspected: (class_name: string, properties: any[]) => void
) {
this.inspected_callbacks.push(inspected);
this.godot_commands?.send_inspect_object_command(id);
}
public next() {
this.godot_commands?.send_next_command();
}
public remove_breakpoint(path_to: string, line: number) {
this.breakpoints.splice(
this.breakpoints.findIndex(bp => bp.file === path_to && bp.line === line),
1
);
this.godot_commands?.send_remove_breakpoint_command(path_to, line);
}
public request_scene_tree() {
this.godot_commands.send_request_scene_tree_command();
}
public set_object_property(
object_id: number,
label: string,
new_parsed_value: any
) {
this.godot_commands.set_object_property(object_id, label, new_parsed_value);
}
public set_breakpoint(path_to: string, line: number) {
this.breakpoints.push({ file: path_to, line: line });
this.godot_commands?.send_set_breakpoint_command(path_to, line);
}
public start(
project_path: string,
port: number,
address: string,
launch_game_instance?: boolean,
breakpoints?: GodotBreakpoint[]
) {
this.builder = new CommandBuilder();
this.parser = new VariantParser();
this.project_path = project_path.replace(/\\/g, "/");
if (this.project_path.match(/^[A-Z]:\//)) {
this.project_path =
this.project_path[0].toLowerCase() + this.project_path.slice(1);
}
this.godot_commands = new GodotCommands(this.builder, this.parser);
if (breakpoints) {
this.breakpoints = breakpoints.map(bp => {
return { file: bp.file, line: bp.line };
});
}
this.builder.register_command(new Command("debug_exit", params => {}));
this.builder.register_command(
new Command("debug_enter", params => {
let reason = params[1];
if (reason !== "Breakpoint") {
this.exception = params[1];
} else {
this.exception = "";
}
this.godot_commands?.send_stack_dump_command();
})
);
this.builder.register_command(
new Command("stack_dump", params => {
let frames: Map<string, any>[] = params;
this.trigger_breakpoint(
frames.map((sf, i) => {
return {
id: i,
thread_id: sf.get("id"),
file: sf.get("file"),
function: sf.get("function"),
line: sf.get("line")
};
})
);
this.request_scene_tree();
})
);
this.builder.register_command(
new Command("output", params => {
if (!this.initial_output) {
this.initial_output = true;
this.request_scene_tree();
}
params.forEach(line => {
this.output_channel?.appendLine(line);
});
})
);
this.builder.register_command(
new Command("error", params => {
params.forEach(param => {});
})
);
this.builder.register_command(new Command("performance", params => {}));
this.builder.register_command(
new Command("message:inspect_object", params => {
let id = params[0];
let class_name = params[1];
let properties = params[2];
let callback = this.inspected_callbacks.shift();
if (callback) {
callback(class_name, properties);
}
})
);
this.builder.register_command(
new Command("message:scene_tree", params => {
if (this.tree_provider) {
let tree = SceneTreeBuilder.build(params);
this.tree_provider.fill_tree(tree);
}
})
);
this.builder.register_command(
new Command("stack_frame_vars", params => {
let locals: any[] = [];
let members: any[] = [];
let globals: any[] = [];
let local_count = (params[0] as number) * 2;
let member_count = params[1 + local_count] * 2;
let global_count = params[2 + local_count + member_count] * 2;
if (local_count > 0) {
locals = params.slice(1, 1 + local_count);
}
if (member_count > 0) {
members = params.slice(
2 + local_count,
2 + local_count + member_count
);
}
if (global_count > 0) {
globals = params.slice(
3 + local_count + member_count,
3 + local_count + member_count + global_count
);
}
this.pumpScope(
{
locals: locals,
members: members,
globals: globals
},
project_path
);
})
);
this.server = net.createServer(connection => {
this.connection = connection;
this.godot_commands?.set_connection(connection);
if (!launch_game_instance) {
this.breakpoints.forEach(bp => {
let path_to = path
.relative(this.project_path, bp.file)
.replace(/\\/g, "/");
this.godot_commands?.send_set_breakpoint_command(
`res://${path_to}`,
bp.line
);
});
}
connection.on("data", buffer => {
if (!this.parser || !this.builder) {
return;
}
let split_buffers = this.split_buffer(buffer);
while(split_buffers.length > 0) {
let sub_buffer = split_buffers.shift()
let data = this.parser.get_buffer_dataset(sub_buffer, 0);
this.builder.parse_data(data.slice(1), error => {
console.log(error);
console.log(this.log_output);
})
}
});
connection.on("close", hadError => {
if (hadError) {
this.send_event("terminated");
}
});
connection.on("end", () => {
this.send_event("terminated");
});
connection.on("error", error => {
console.error(error);
});
connection.on("drain", () => {
connection.resume();
this.godot_commands?.set_can_write(true);
});
});
this.server?.listen(port, address);
if (launch_game_instance) {
let godot_path = utils.get_configuration(
"editor_path",
"godot"
) as string;
let executable_line = `${godot_path} --path ${project_path} --remote-debug ${address}:${port}`;
executable_line += this.build_breakpoint_string(
breakpoints,
project_path
);
let godot_exec = cp.exec(executable_line);
this.godot_pid = godot_exec.pid;
}
}
private split_buffer(buffer: Buffer) {
let len = buffer.byteLength;
let offset = 0;
let buffers: Buffer[] = [];
while(len > 0) {
let sub_len = buffer.readUInt32LE(offset)+4;
buffers.push(buffer.slice(offset, offset+sub_len));
offset += sub_len;
len -= sub_len;
}
return buffers;
}
public step() {
this.godot_commands?.send_step_command();
}
public step_out() {
this.stepping_out = true;
this.next();
}
public stop() {
this.connection?.end(() => {
this.server?.close();
if (this.godot_pid) {
TERMINATE(this.godot_pid, (error: string | undefined) => {
if (error) {
console.error(error);
}
});
}
});
this.send_event("terminated");
}
private build_breakpoint_string(
breakpoints: GodotBreakpoint[],
project: string
): string {
let output = "";
if (breakpoints.length > 0) {
output += " --breakpoints ";
breakpoints.forEach(bp => {
let relative_path = path.relative(project, bp.file).replace(/\\/g, "/");
if (relative_path.length !== 0) {
output += `res://${relative_path}:${bp.line},`;
}
});
output = output.slice(0, -1);
}
return output;
}
private pumpScope(
scopes: {
locals: any[];
members: any[];
globals: any[];
},
projectPath: string
) {
if (this.scope_callbacks.length > 0) {
let cb = this.scope_callbacks.shift();
if (cb) {
let stack_files = this.stack_files.map(sf => {
return sf.replace("res://", `${projectPath}/`);
});
cb(this.stack_level, stack_files, scopes);
}
}
}
private send_event(event: string, ...args: any[]) {
setImmediate(_ => {
this.emitter.emit(event, ...args);
});
}
private trigger_breakpoint(stack_frames: GodotStackFrame[]) {
let continue_stepping = false;
let stack_count = stack_frames.length;
let file = stack_frames[0].file.replace("res://", `${this.project_path}/`);
let line = stack_frames[0].line;
if (this.stepping_out) {
let breakpoint = this.breakpoints.find(
k => k.file === file && k.line === line
);
if (!breakpoint) {
if (this.stack_count > 1) {
continue_stepping = this.stack_count === stack_count;
} else {
let file_same = stack_frames[0].file === this.last_frame.file;
let func_same = stack_frames[0].function === this.last_frame.function;
let line_greater = stack_frames[0].line >= this.last_frame.line;
continue_stepping = file_same && func_same && line_greater;
}
}
}
this.stack_count = stack_count;
this.last_frame = stack_frames[0];
if (continue_stepping) {
this.next();
return;
}
this.stepping_out = false;
this.stack_files = stack_frames.map(sf => {
return sf.file;
});
if (this.exception.length === 0) {
this.send_event("stopOnBreakpoint", stack_frames);
} else {
this.send_event("stopOnException", stack_frames, this.exception);
}
}
}

View File

@@ -1,3 +1,3 @@
import { GodotDebugSession } from "./godot_debug";
import { GodotDebugSession } from "./debug_session";
GodotDebugSession.run(GodotDebugSession);

View File

@@ -0,0 +1,89 @@
import { Mediator } from "./mediator";
import { SceneTreeProvider } from "./scene_tree/scene_tree_provider";
const path = require("path");
export interface GodotBreakpoint {
file: string;
id: number;
line: number;
}
export interface GodotStackFrame {
file: string;
function: string;
id: number;
line: number;
}
export interface GodotVariable {
name: string;
scope_path?: string;
sub_values?: GodotVariable[];
value: any;
}
export class GodotDebugData {
private breakpoint_id = 0;
private breakpoints: Map<string, GodotBreakpoint[]> = new Map();
public last_frame: GodotStackFrame;
public last_frames: GodotStackFrame[] = [];
public project_path: string;
public scene_tree?: SceneTreeProvider;
public stack_count: number = 0;
public stack_files: string[] = [];
public constructor() {}
public get_all_breakpoints(): GodotBreakpoint[] {
let output: GodotBreakpoint[] = [];
Array.from(this.breakpoints.values()).forEach((bp_array) => {
output.push(...bp_array);
});
return output;
}
public get_breakpoints(path: string) {
return this.breakpoints.get(path) || [];
}
public remove_breakpoint(path_to: string, line: number) {
let bps = this.breakpoints.get(path_to);
if (bps) {
let index = bps.findIndex((bp) => {
return bp.line === line;
});
if (index !== -1) {
let bp = bps[index];
bps.splice(index, 1);
this.breakpoints.set(path_to, bps);
let file = `res://${path.relative(this.project_path, bp.file)}`;
Mediator.notify("remove_breakpoint", [
file.replace(/\\/g, "/"),
bp.line,
]);
}
}
}
public set_breakpoint(path_to: string, line: number) {
let bp = {
file: path_to.replace(/\\/g, "/"),
line: line,
id: this.breakpoint_id++,
};
let bps: GodotBreakpoint[] = this.breakpoints.get(bp.file);
if (!bps) {
bps = [];
this.breakpoints.set(bp.file, bps);
}
bps.push(bp);
let out_file = `res://${path.relative(this.project_path, bp.file)}`;
Mediator.notify("set_breakpoint", [out_file.replace(/\\/g, "/"), line]);
}
}

View File

@@ -0,0 +1,494 @@
import {
LoggingDebugSession,
InitializedEvent,
Thread,
Source,
Breakpoint,
} from "vscode-debugadapter";
import { DebugProtocol } from "vscode-debugprotocol";
import { Mediator } from "./mediator";
import { GodotDebugData, GodotVariable } from "./debug_runtime";
import { ObjectId, RawObject } from "./variables/variants";
import { ServerController } from "./server_controller";
const { Subject } = require("await-notify");
import fs = require("fs");
import { SceneTreeProvider } from "./scene_tree/scene_tree_provider";
interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
address: string;
launch_game_instance: boolean;
launch_scene: boolean;
port: number;
project: string;
scene_file: string;
}
export class GodotDebugSession extends LoggingDebugSession {
private all_scopes: GodotVariable[];
private configuration_done = new Subject();
private controller?: ServerController;
private debug_data = new GodotDebugData();
private exception = false;
private got_scope = new Subject();
private ongoing_inspections: bigint[] = [];
private previous_inspections: bigint[] = [];
public constructor() {
super();
this.setDebuggerLinesStartAt1(false);
this.setDebuggerColumnsStartAt1(false);
Mediator.set_session(this);
this.controller = new ServerController();
Mediator.set_controller(this.controller);
Mediator.set_debug_data(this.debug_data);
}
public dispose() {}
public set_exception(exception: boolean) {
this.exception = true;
}
public set_inspection(id: bigint, replacement: GodotVariable) {
let variables = this.all_scopes.filter(
(va) => va && va.value instanceof ObjectId && va.value.id === id
);
variables.forEach((va) => {
let index = this.all_scopes.findIndex((va_id) => va_id === va);
let old = this.all_scopes.splice(index, 1);
replacement.name = old[0].name;
replacement.scope_path = old[0].scope_path;
this.append_variable(replacement, index);
});
this.ongoing_inspections.splice(
this.ongoing_inspections.findIndex((va_id) => va_id === id),
1
);
this.previous_inspections.push(id);
this.add_to_inspections();
if (this.ongoing_inspections.length === 0) {
this.previous_inspections = [];
this.got_scope.notify();
}
}
public set_scene_tree(scene_tree_provider: SceneTreeProvider) {
this.debug_data.scene_tree = scene_tree_provider;
}
public set_scopes(
locals: GodotVariable[],
members: GodotVariable[],
globals: GodotVariable[]
) {
this.all_scopes = [
undefined,
{ name: "local", value: undefined, sub_values: locals, scope_path: "@" },
{
name: "member",
value: undefined,
sub_values: members,
scope_path: "@",
},
{
name: "global",
value: undefined,
sub_values: globals,
scope_path: "@",
},
];
locals.forEach((va) => {
va.scope_path = `@.local`;
this.append_variable(va);
});
members.forEach((va) => {
va.scope_path = `@.member`;
this.append_variable(va);
});
globals.forEach((va) => {
va.scope_path = `@.global`;
this.append_variable(va);
});
this.add_to_inspections();
if (this.ongoing_inspections.length === 0) {
this.previous_inspections = [];
this.got_scope.notify();
}
}
protected configurationDoneRequest(
response: DebugProtocol.ConfigurationDoneResponse,
args: DebugProtocol.ConfigurationDoneArguments
): void {
super.configurationDoneRequest(response, args);
this.configuration_done.notify();
}
protected continueRequest(
response: DebugProtocol.ContinueResponse,
args: DebugProtocol.ContinueArguments
) {
if (!this.exception) {
response.body = { allThreadsContinued: true };
Mediator.notify("continue");
this.sendResponse(response);
}
}
protected evaluateRequest(
response: DebugProtocol.EvaluateResponse,
args: DebugProtocol.EvaluateArguments
) {
if (this.all_scopes) {
let expression = args.expression;
let matches = expression.match(/^[_a-zA-Z0-9]+?$/);
if (matches) {
let result_idx = this.all_scopes.findIndex(
(va) => va && va.name === expression
);
if (result_idx !== -1) {
let result = this.all_scopes[result_idx];
response.body = {
result: this.parse_variable(result).value,
variablesReference: result_idx,
};
}
}
}
if (!response.body) {
response.body = {
result: "null",
variablesReference: 0,
};
}
this.sendResponse(response);
}
protected initializeRequest(
response: DebugProtocol.InitializeResponse,
args: DebugProtocol.InitializeRequestArguments
) {
response.body = response.body || {};
response.body.supportsConfigurationDoneRequest = true;
response.body.supportsTerminateRequest = true;
response.body.supportsEvaluateForHovers = false;
response.body.supportsStepBack = false;
response.body.supportsGotoTargetsRequest = false;
response.body.supportsCancelRequest = false;
response.body.supportsCompletionsRequest = false;
response.body.supportsFunctionBreakpoints = false;
response.body.supportsDataBreakpoints = false;
response.body.supportsBreakpointLocationsRequest = false;
response.body.supportsConditionalBreakpoints = false;
response.body.supportsHitConditionalBreakpoints = false;
response.body.supportsLogPoints = false;
response.body.supportsModulesRequest = false;
response.body.supportsReadMemoryRequest = false;
response.body.supportsRestartFrame = false;
response.body.supportsRestartRequest = false;
response.body.supportsSetExpression = false;
response.body.supportsStepInTargetsRequest = false;
response.body.supportsTerminateThreadsRequest = false;
this.sendResponse(response);
this.sendEvent(new InitializedEvent());
}
protected async launchRequest(
response: DebugProtocol.LaunchResponse,
args: LaunchRequestArguments
) {
await this.configuration_done.wait(2000);
this.exception = false;
this.debug_data.project_path = args.project;
Mediator.notify("start", [
args.project,
args.address,
args.port,
args.launch_game_instance,
args.launch_scene,
args.scene_file,
]);
this.sendResponse(response);
}
protected nextRequest(
response: DebugProtocol.NextResponse,
args: DebugProtocol.NextArguments
) {
if (!this.exception) {
Mediator.notify("next");
this.sendResponse(response);
}
}
protected pauseRequest(
response: DebugProtocol.PauseResponse,
args: DebugProtocol.PauseArguments
) {
if (!this.exception) {
Mediator.notify("break");
this.sendResponse(response);
}
}
protected async scopesRequest(
response: DebugProtocol.ScopesResponse,
args: DebugProtocol.ScopesArguments
) {
while (this.ongoing_inspections.length > 0) {
await this.got_scope.wait(100);
}
Mediator.notify("get_scopes", [args.frameId]);
await this.got_scope.wait(2000);
response.body = {
scopes: [
{ name: "Locals", variablesReference: 1, expensive: false },
{ name: "Members", variablesReference: 2, expensive: false },
{ name: "Globals", variablesReference: 3, expensive: false },
],
};
this.sendResponse(response);
}
protected setBreakPointsRequest(
response: DebugProtocol.SetBreakpointsResponse,
args: DebugProtocol.SetBreakpointsArguments
) {
let path = (args.source.path as string).replace(/\\/g, "/");
let client_lines = args.lines || [];
if (fs.existsSync(path)) {
let bps = this.debug_data.get_breakpoints(path);
let bp_lines = bps.map((bp) => bp.line);
bps.forEach((bp) => {
if (client_lines.indexOf(bp.line) === -1) {
this.debug_data.remove_breakpoint(path, bp.line);
}
});
client_lines.forEach((l) => {
if (bp_lines.indexOf(l) === -1) {
this.debug_data.set_breakpoint(path, l);
}
});
bps = this.debug_data.get_breakpoints(path);
response.body = {
breakpoints: bps.map((bp) => {
return new Breakpoint(
true,
bp.line,
1,
new Source(bp.file.split("/").reverse()[0], bp.file)
);
}),
};
this.sendResponse(response);
}
}
protected stackTraceRequest(
response: DebugProtocol.StackTraceResponse,
args: DebugProtocol.StackTraceArguments
) {
if (this.debug_data.last_frame) {
response.body = {
totalFrames: this.debug_data.last_frames.length,
stackFrames: this.debug_data.last_frames.map((sf) => {
return {
id: sf.id,
name: sf.function,
line: sf.line,
column: 1,
source: new Source(
sf.file,
`${this.debug_data.project_path}/${sf.file.replace("res://", "")}`
),
};
}),
};
}
this.sendResponse(response);
}
protected stepInRequest(
response: DebugProtocol.StepInResponse,
args: DebugProtocol.StepInArguments
) {
if (!this.exception) {
Mediator.notify("step");
this.sendResponse(response);
}
}
protected stepOutRequest(
response: DebugProtocol.StepOutResponse,
args: DebugProtocol.StepOutArguments
) {
if (!this.exception) {
Mediator.notify("step_out");
this.sendResponse(response);
}
}
protected terminateRequest(
response: DebugProtocol.TerminateResponse,
args: DebugProtocol.TerminateArguments
) {
Mediator.notify("stop");
this.sendResponse(response);
}
protected threadsRequest(response: DebugProtocol.ThreadsResponse) {
response.body = { threads: [new Thread(0, "thread_1")] };
this.sendResponse(response);
}
protected async variablesRequest(
response: DebugProtocol.VariablesResponse,
args: DebugProtocol.VariablesArguments
) {
let reference = this.all_scopes[args.variablesReference];
let variables: DebugProtocol.Variable[];
if (!reference.sub_values) {
variables = [];
} else {
variables = reference.sub_values.map((va) => {
let sva = this.all_scopes.find(
(sva) =>
sva && sva.scope_path === va.scope_path && sva.name === va.name
);
if (sva) {
return this.parse_variable(
sva,
this.all_scopes.findIndex(
(va_idx) =>
va_idx &&
va_idx.scope_path ===
`${reference.scope_path}.${reference.name}` &&
va_idx.name === va.name
)
);
}
});
}
response.body = {
variables: variables,
};
this.sendResponse(response);
}
private add_to_inspections() {
this.all_scopes.forEach((va) => {
if (va && va.value instanceof ObjectId) {
if (
!this.ongoing_inspections.find((va_id) => va_id === va.value.id) &&
!this.previous_inspections.find((va_id) => va_id === va.value.id)
) {
Mediator.notify("inspect_object", [va.value.id]);
this.ongoing_inspections.push(va.value.id);
}
}
});
}
private append_variable(variable: GodotVariable, index?: number) {
if (index) {
this.all_scopes.splice(index, 0, variable);
} else {
this.all_scopes.push(variable);
}
let base_path = `${variable.scope_path}.${variable.name}`;
if (variable.sub_values) {
variable.sub_values.forEach((va, i) => {
va.scope_path = `${base_path}`;
this.append_variable(va, index ? index + i + 1 : undefined);
});
}
}
private parse_variable(va: GodotVariable, i?: number) {
let value = va.value;
let rendered_value = "";
let reference = 0;
let array_size = 0;
let array_type = undefined;
if (typeof value === "number") {
if (Number.isInteger(value)) {
rendered_value = `${value}`;
} else {
rendered_value = `${parseFloat(value.toFixed(5))}`;
}
} else if (
typeof value === "bigint" ||
typeof value === "boolean" ||
typeof value === "string"
) {
rendered_value = `${value}`;
} else if (typeof value === "undefined") {
rendered_value = "null";
} else {
if (Array.isArray(value)) {
rendered_value = `Array[${value.length}]`;
array_size = value.length;
array_type = "indexed";
reference = i ? i : 0;
} else if (value instanceof Map) {
if (value instanceof RawObject) {
rendered_value = `${value.class_name}`;
} else {
rendered_value = `Dictionary[${value.size}]`;
}
array_size = value.size;
array_type = "named";
reference = i ? i : 0;
} else {
rendered_value = `${value.type_name()}${value.stringify_value()}`;
reference = i ? i : 0;
}
}
return {
name: va.name,
value: rendered_value,
variablesReference: reference,
array_size: array_size > 0 ? array_size : undefined,
filter: array_type,
};
}
}

View File

@@ -11,15 +11,16 @@ import {
CancellationToken,
ProviderResult,
window,
commands
commands,
} from "vscode";
import { GodotDebugSession } from "./godot_debug";
import { GodotDebugSession } from "./debug_session";
import fs = require("fs");
import { SceneTreeProvider, SceneNode } from "./SceneTree/scene_tree_provider";
import { SceneTreeProvider, SceneNode } from "./scene_tree/scene_tree_provider";
import {
RemoteProperty,
InspectorProvider,
RemoteProperty
} from "./SceneTree/inspector_provider";
} from "./scene_tree/inspector_provider";
import { Mediator } from "./mediator";
export function register_debugger(context: ExtensionContext) {
let provider = new GodotConfigurationProvider();
@@ -33,10 +34,7 @@ export function register_debugger(context: ExtensionContext) {
let scene_tree_provider = new SceneTreeProvider();
window.registerTreeDataProvider("active-scene-tree", scene_tree_provider);
let factory = new GodotDebugAdapterFactory(
scene_tree_provider,
inspector_provider
);
let factory = new GodotDebugAdapterFactory(scene_tree_provider);
context.subscriptions.push(
debug.registerDebugAdapterDescriptorFactory("godot", factory)
);
@@ -45,49 +43,47 @@ export function register_debugger(context: ExtensionContext) {
"godot-tool.debugger.inspect_node",
(element: SceneNode | RemoteProperty) => {
if (element instanceof SceneNode) {
factory.session.inspect_node(
element.label,
Mediator.notify("inspect_object", [
element.object_id,
(class_name, properties) => {
(class_name, variable) => {
inspector_provider.fill_tree(
element.label,
class_name,
element.object_id,
properties
);
}
variable
);
},
]);
} else if (element instanceof RemoteProperty) {
factory.session.inspect_node(
element.label,
element.value.id,
Mediator.notify("inspect_object", [
element.object_id,
(class_name, properties) => {
inspector_provider.fill_tree(
element.label,
class_name,
element.value.id,
element.object_id,
properties
);
}
);
},
]);
}
}
);
commands.registerCommand("godot-tool.debugger.refresh_scene_tree", () => {
factory.session.request_scene_tree();
Mediator.notify("request_scene_tree", []);
});
commands.registerCommand("godot-tool.debugger.refresh_inspector", () => {
if (inspector_provider.has_tree()) {
factory.session.reinspect_node((name, class_name, properties) => {
inspector_provider.fill_tree(
name,
class_name,
factory.session.get_last_id(),
properties
);
});
let name = inspector_provider.get_top_name();
let id = inspector_provider.get_top_id();
Mediator.notify("inspect_object", [
id,
(class_name, properties) => {
inspector_provider.fill_tree(name, class_name, id, properties);
},
]);
}
});
@@ -97,7 +93,9 @@ export function register_debugger(context: ExtensionContext) {
let previous_value = property.value;
let type = typeof previous_value;
let is_float = type === "number" && !Number.isInteger(previous_value);
window.showInputBox({ value: `${property.description}` }).then(value => {
window
.showInputBox({ value: `${property.description}` })
.then((value) => {
let new_parsed_value: any;
switch (type) {
case "string":
@@ -117,8 +115,16 @@ export function register_debugger(context: ExtensionContext) {
}
break;
case "boolean":
if (
value.toLowerCase() === "true" ||
value.toLowerCase() === "false"
) {
new_parsed_value = value.toLowerCase() === "true";
break;
} else if (value === "0" || value === "1") {
new_parsed_value = value === "1";
} else {
return;
}
}
if (property.changes_parent) {
let parents = [property.parent];
@@ -131,26 +137,30 @@ export function register_debugger(context: ExtensionContext) {
property,
new_parsed_value
);
factory.session.set_object_property(
Mediator.notify("changed_value", [
property.object_id,
parents[idx].label,
changed_value
);
changed_value,
]);
} else {
factory.session.set_object_property(
Mediator.notify("changed_value", [
property.object_id,
property.label,
new_parsed_value
);
new_parsed_value,
]);
}
factory.session.reinspect_node((name, class_name, properties) => {
Mediator.notify("inspect_object", [
inspector_provider.get_top_id(),
(class_name, properties) => {
inspector_provider.fill_tree(
name,
inspector_provider.get_top_name(),
class_name,
factory.session.get_last_id(),
inspector_provider.get_top_id(),
properties
);
});
},
]);
});
}
);
@@ -174,6 +184,7 @@ class GodotConfigurationProvider implements DebugConfigurationProvider {
config.port = 6007;
config.address = "127.0.0.1";
config.launch_game_instance = true;
config.launch_scene = false;
}
}
@@ -194,17 +205,13 @@ class GodotConfigurationProvider implements DebugConfigurationProvider {
class GodotDebugAdapterFactory implements DebugAdapterDescriptorFactory {
public session: GodotDebugSession | undefined;
constructor(
private tree_provider: SceneTreeProvider,
private inspector_provider: InspectorProvider
) {}
constructor(private scene_tree_provider: SceneTreeProvider) {}
public createDebugAdapterDescriptor(
session: DebugSession
): ProviderResult<DebugAdapterDescriptor> {
this.session = new GodotDebugSession();
this.inspector_provider.clean_up();
this.session.set_tree_provider(this.tree_provider);
this.session.set_scene_tree(this.scene_tree_provider);
return new DebugAdapterInlineImplementation(this.session);
}

View File

@@ -1,588 +0,0 @@
import {
LoggingDebugSession,
InitializedEvent,
TerminatedEvent,
StoppedEvent,
Thread,
Source,
Breakpoint
} from "vscode-debugadapter";
import { DebugProtocol } from "vscode-debugprotocol";
import { window, OutputChannel } from "vscode";
const { Subject } = require("await-notify");
import { GodotDebugRuntime, GodotStackFrame } from "./godot_debug_runtime";
import { VariableScope, VariableScopeBuilder } from "./variable_scope";
import { SceneTreeProvider } from "./SceneTree/scene_tree_provider";
import stringify from "./stringify";
import fs = require("fs");
interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
address: string;
launch_game_instance: boolean;
port: number;
project: string;
}
var output_channel: OutputChannel | undefined;
export class GodotDebugSession extends LoggingDebugSession {
private static MAIN_THREAD_ID = 0;
private configuration_done = new Subject();
private current_stack_level = 0;
private excepted = false;
private have_scopes: (() => void)[] = [];
private last_frames: GodotStackFrame[] = [];
private last_inspection_id = -1;
private last_inspection_name = "";
private runtime: GodotDebugRuntime;
private scope_builder: VariableScopeBuilder | undefined;
private tree_provider: SceneTreeProvider | undefined;
public constructor() {
super();
if (!output_channel) {
output_channel = window.createOutputChannel("Godot");
} else {
output_channel.clear();
}
this.setDebuggerLinesStartAt1(false);
this.setDebuggerColumnsStartAt1(false);
this.runtime = new GodotDebugRuntime();
this.runtime.on("stopOnBreakpoint", frames => {
this.last_frames = frames;
this.sendEvent(
new StoppedEvent("breakpoint", GodotDebugSession.MAIN_THREAD_ID)
);
});
this.runtime.on("stopOnException", (frames, exception) => {
this.last_frames = frames;
this.sendEvent(
new StoppedEvent(
"exception",
GodotDebugSession.MAIN_THREAD_ID,
exception
)
);
});
this.runtime.on("terminated", () => {
this.sendEvent(new TerminatedEvent(false));
});
}
public dispose() {}
public get_last_id(): number {
return this.last_inspection_id;
}
public inspect_node(
object_name: string,
object_id: number,
inspected: (class_name: string, properties: any[]) => void
) {
this.last_inspection_id = object_id;
this.last_inspection_name = object_name;
this.runtime.inspect_object(object_id, inspected);
}
public reinspect_node(
callback: (name: string, class_name: string, properties: any[]) => void
) {
this.inspect_node(
this.last_inspection_name,
this.last_inspection_id,
(class_name, properties) => {
callback(this.last_inspection_name, class_name, properties);
}
);
}
public request_scene_tree() {
this.runtime.request_scene_tree();
}
public set_object_property(
object_id: number,
label: string,
new_parsed_value: any
) {
this.runtime.set_object_property(object_id, label, new_parsed_value);
}
public set_tree_provider(tree_provider: SceneTreeProvider) {
this.tree_provider = tree_provider;
}
protected configurationDoneRequest(
response: DebugProtocol.ConfigurationDoneResponse,
args: DebugProtocol.ConfigurationDoneArguments
): void {
super.configurationDoneRequest(response, args);
this.configuration_done.notify();
}
protected continueRequest(
response: DebugProtocol.ContinueResponse,
args: DebugProtocol.ContinueArguments
): void {
if (this.excepted) {
return;
}
response.body = {
allThreadsContinued: true
};
this.runtime.continue();
this.sendResponse(response);
}
protected evaluateRequest(
response: DebugProtocol.EvaluateResponse,
args: DebugProtocol.EvaluateArguments
) {
this.have_scopes.push(() => {
if (args.expression.match(/[^a-zA-Z0-9_\[\]\.]/g)) {
response.body = {
result: "not supported",
variablesReference: 0
};
this.sendResponse(response);
return;
}
let is_self = args.expression.match(/^self\./);
let expression = args.expression
.replace(/[\[\]]/g, ".")
.replace(/\.$/, "")
.replace(/^self./, "");
let variable: { name: string; value: any } | undefined;
let scope_keys = this.scope_builder.get_keys(this.current_stack_level);
let variable_id = -1;
for (let i = 0; i < scope_keys.length; ++i) {
let scopes = this.scope_builder.get(
this.current_stack_level,
scope_keys[i]
);
for (let l = is_self ? 1 : 0; l < 3; ++l) {
variable_id = scopes[l].get_id_for(expression);
if (variable_id !== -1) {
variable = scopes[l].get_variable(variable_id);
break;
}
}
if (variable) {
break;
}
}
if (!variable) {
response.body = {
result: "not available",
variablesReference: 0
};
this.sendResponse(response);
return;
}
let value_type_pair = stringify(variable.value);
response.body = {
result: value_type_pair.value,
type: value_type_pair.type,
variablesReference: variable_id
};
this.sendResponse(response);
});
if (
this.scope_builder.size() > 0 &&
this.scope_builder.get_keys(this.current_stack_level).length > 0
) {
this.have_scopes.shift()();
}
}
protected initializeRequest(
response: DebugProtocol.InitializeResponse,
args: DebugProtocol.InitializeRequestArguments
): void {
response.body = response.body || {};
response.body.supportsConfigurationDoneRequest = true;
response.body.supportsTerminateRequest = true;
response.body.supportsEvaluateForHovers = false;
response.body.supportsStepBack = false;
response.body.supportsGotoTargetsRequest = false;
response.body.supportsCancelRequest = false;
response.body.supportsCompletionsRequest = false;
response.body.supportsFunctionBreakpoints = false;
response.body.supportsDataBreakpoints = false;
response.body.supportsBreakpointLocationsRequest = false;
response.body.supportsConditionalBreakpoints = false;
response.body.supportsHitConditionalBreakpoints = false;
response.body.supportsLogPoints = false;
response.body.supportsModulesRequest = false;
response.body.supportsReadMemoryRequest = false;
response.body.supportsRestartFrame = false;
response.body.supportsRestartRequest = false;
response.body.supportsSetExpression = false;
//TODO: Implement
response.body.supportsSetVariable = false;
response.body.supportsStepInTargetsRequest = false;
response.body.supportsTerminateThreadsRequest = false;
this.sendResponse(response);
this.sendEvent(new InitializedEvent());
}
protected async launchRequest(
response: DebugProtocol.LaunchResponse,
args: LaunchRequestArguments
) {
await this.configuration_done.wait(1000);
this.excepted = false;
this.runtime.start(
args.project,
args.address,
args.port,
args.launch_game_instance,
output_channel,
this.tree_provider
);
this.sendResponse(response);
}
protected nextRequest(
response: DebugProtocol.NextResponse,
args: DebugProtocol.NextArguments
): void {
if (this.excepted) {
return;
}
this.runtime.next();
this.sendResponse(response);
}
protected pauseRequest(
response: DebugProtocol.PauseResponse,
args: DebugProtocol.PauseArguments
): void {
if (this.excepted) {
return;
}
this.runtime.break();
this.sendResponse(response);
}
protected scopesRequest(
response: DebugProtocol.ScopesResponse,
args: DebugProtocol.ScopesArguments
): void {
this.runtime.getScope(args.frameId, (stack_level, stack_files, scopes) => {
this.current_stack_level = stack_level;
this.scope_builder = new VariableScopeBuilder(
this.runtime,
stack_level,
stack_files,
scopes,
this.have_scopes
);
this.scope_builder.parse(over_scopes => {
response.body = { scopes: over_scopes };
this.sendResponse(response);
});
});
}
protected setBreakPointsRequest(
response: DebugProtocol.SetBreakpointsResponse,
args: DebugProtocol.SetBreakpointsArguments
): void {
let path = (args.source.path as string).replace(/\\/g, "/");
let client_lines = args.lines || [];
if (fs.existsSync(path)) {
let bps = this.runtime.get_breakpoints(path);
let bp_lines = bps.map(bp => bp.line);
bps.forEach(bp => {
if (client_lines.indexOf(bp.line) === -1) {
this.runtime.remove_breakpoint(path, bp.line);
}
});
client_lines.forEach(l => {
if (bp_lines.indexOf(l) === -1) {
this.runtime.set_breakpoint(path, l);
}
});
bps = this.runtime.get_breakpoints(path);
response.body = {
breakpoints: bps.map(bp => {
return new Breakpoint(
true,
bp.line,
1,
new Source(bp.file.split("/").reverse()[0], bp.file, bp.id)
);
})
};
this.sendResponse(response);
}
}
protected setExceptionBreakPointsRequest(
response: DebugProtocol.SetExceptionBreakpointsResponse,
args: DebugProtocol.SetExceptionBreakpointsArguments
) {
this.excepted = true;
this.sendResponse(response);
}
protected stackTraceRequest(
response: DebugProtocol.StackTraceResponse,
args: DebugProtocol.StackTraceArguments
): void {
if (this.last_frames) {
response.body = {
totalFrames: this.last_frames.length,
stackFrames: this.last_frames.map(sf => {
return {
id: sf.id,
name: sf.function,
line: sf.line,
column: 1,
source: new Source(
sf.file,
`${this.runtime.getProject()}/${sf.file.replace("res://", "")}`
)
};
})
};
}
this.sendResponse(response);
}
protected stepInRequest(
response: DebugProtocol.StepInResponse,
args: DebugProtocol.StepInArguments
) {
if (this.excepted) {
return;
}
this.runtime.step();
this.sendResponse(response);
}
protected stepOutRequest(
response: DebugProtocol.StepOutResponse,
args: DebugProtocol.StepOutArguments
) {
if (this.excepted) {
return;
}
this.runtime.step_out();
this.sendResponse(response);
}
protected terminateRequest(
response: DebugProtocol.TerminateResponse,
args: DebugProtocol.TerminateArguments
) {
this.runtime.terminate();
this.sendResponse(response);
}
protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
response.body = {
threads: [new Thread(GodotDebugSession.MAIN_THREAD_ID, "thread_1")]
};
this.sendResponse(response);
}
protected async variablesRequest(
response: DebugProtocol.VariablesResponse,
args: DebugProtocol.VariablesArguments,
request?: DebugProtocol.Request
) {
let out_id = args.variablesReference;
let files = this.scope_builder.get_keys(this.current_stack_level);
let out_scope_object = this.get_variable_scope(files, out_id);
let is_scope = out_scope_object.isScope;
let out_scope = out_scope_object.scope;
if (out_scope) {
if (is_scope) {
let var_ids = out_scope.get_variable_ids();
response.body = {
variables: this.parse_scope(var_ids, out_scope)
};
} else {
let variable = out_scope.get_variable(out_id);
if (variable) {
let sub_variables = out_scope.get_sub_variables_for(out_id);
if (sub_variables) {
let ids = out_scope.get_variable_ids();
let path_to = variable.name;
response.body = {
variables: []
};
if (args.filter === "indexed") {
let count = args.count || 0;
for (let i = 0; i < count; i++) {
let name = `${path_to}.${i}`;
let id_index = ids.findIndex(id => {
let variable = out_scope?.get_variable(id);
return variable && name === variable.name;
});
response.body.variables.push(
this.get_variable_response(
name,
variable.value[i],
ids[id_index]
)
);
}
} else {
sub_variables.forEach(sv => {
let name = sv.name;
let id_index = ids.findIndex(id => {
let variable = out_scope?.get_variable(id);
return variable && name === variable.name;
});
response.body.variables.push(
this.get_variable_response(name, sv.value, ids[id_index])
);
});
}
} else {
response.body = {
variables: [
this.get_variable_response(
variable.name,
variable.value,
0,
true
)
]
};
}
} else {
response.body = { variables: [] };
}
}
this.sendResponse(response);
}
}
private get_variable_response(
var_name: string,
var_value: any,
id: number,
skip_sub_var?: boolean
) {
let value = "";
let ref_id = 0;
let array_count = 0;
let type = "";
if (!skip_sub_var) {
let output = stringify(var_value);
value = output.value;
type = output.type;
ref_id = output.skip_id ? 0 : id;
}
return {
name: var_name.replace(/([a-zA-Z0-9_]+?\.)*/g, ""),
value: value,
variablesReference: ref_id,
indexedVariables: array_count,
type: type
};
}
private get_variable_scope(files: string[], scope_id: number) {
let out_scope: VariableScope | undefined;
let is_scope = false;
for (let i = 0; i < files.length; i++) {
let file = files[i];
let scopes = this.scope_builder.get(this.current_stack_level, file);
if (scopes) {
let index = scopes.findIndex(s => {
return s.id === scope_id;
});
if (index !== -1) {
out_scope = scopes[index];
is_scope = true;
break;
} else {
for (let l = 0; l < scopes.length; l++) {
let scope = scopes[l];
let ids = scope.get_variable_ids();
for (let k = 0; k < ids.length; k++) {
let id = ids[k];
if (scope_id === id) {
out_scope = scope;
is_scope = false;
break;
}
}
}
}
}
}
return { isScope: is_scope, scope: out_scope };
}
private parse_scope(var_ids: number[], out_scope: VariableScope) {
let output: DebugProtocol.Variable[] = [];
var_ids.forEach(id => {
let variable = out_scope?.get_variable(id);
if (variable && variable.name.indexOf(".") === -1) {
output.push(
this.get_variable_response(variable.name, variable.value, id)
);
}
});
return output;
}
}

View File

@@ -1,177 +0,0 @@
import vscode = require("vscode");
import { EventEmitter } from "events";
import { ServerController } from "./communication/server_controller";
import { SceneTreeProvider } from "./SceneTree/scene_tree_provider";
import { InspectorProvider } from "./SceneTree/inspector_provider";
export interface GodotBreakpoint {
file: string;
id: number;
line: number;
}
export interface GodotStackFrame {
file: string;
function: string;
id: number;
line: number;
}
export class GodotDebugRuntime extends EventEmitter {
private breakpointId = 0;
private breakpoints = new Map<string, GodotBreakpoint[]>();
private out: vscode.OutputChannel | undefined;
private paused = false;
private project = "";
private server_controller: ServerController | undefined;
constructor() {
super();
}
public break() {
if (this.paused) {
this.server_controller?.continue();
} else {
this.server_controller?.break();
}
}
public continue() {
this.server_controller?.continue();
}
public getProject(): string {
return this.project;
}
public getScope(
level: number,
callback?: (
stackLevel: number,
stackFiles: string[],
scopes: {
locals: any[];
members: any[];
globals: any[];
}
) => void
) {
this.server_controller?.get_scope(level, callback);
}
public get_breakpoints(path: string): GodotBreakpoint[] {
let bps = this.breakpoints.get(path);
return bps ? bps : [];
}
public inspect_object(
objectId: number,
inspected: (className: string, properties: any[]) => void
) {
this.server_controller?.inspect_object(objectId, inspected);
}
public next() {
this.server_controller?.next();
}
public remove_breakpoint(pathTo: string, line: number) {
let bps = this.breakpoints.get(pathTo);
if (bps) {
let index = bps.findIndex(bp => {
return bp.line === line;
});
if (index !== -1) {
let bp = bps[index];
bps.splice(index, 1);
this.breakpoints.set(pathTo, bps);
this.server_controller?.remove_breakpoint(
bp.file.replace(new RegExp(`${this.project}/`), "res://"),
bp.line
);
}
}
}
public request_scene_tree() {
this.server_controller.request_scene_tree();
}
public set_object_property(
object_id: number,
label: string,
new_parsed_value: any
) {
this.server_controller.set_object_property(object_id, label, new_parsed_value);
}
public set_breakpoint(pathTo: string, line: number): GodotBreakpoint {
const BP = {
file: pathTo.replace(/\\/g, "/"),
line: line,
id: this.breakpointId++
};
let bps = this.breakpoints.get(BP.file);
if (!bps) {
bps = new Array<GodotBreakpoint>();
this.breakpoints.set(BP.file, bps);
}
bps.push(BP);
this.server_controller?.set_breakpoint(
BP.file.replace(new RegExp(`${this.project}/`), "res://"),
line
);
return BP;
}
public start(
project: string,
address: string,
port: number,
launchGameInstance: boolean,
out: vscode.OutputChannel,
tree_provider: SceneTreeProvider
) {
this.out = out;
this.out.show();
this.project = project.replace(/\\/g, "/");
if (this.project.match(/^[A-Z]:\//)) {
this.project = this.project[0].toLowerCase() + this.project.slice(1);
}
this.server_controller = new ServerController(
this,
this.out,
tree_provider
);
let breakpointList: GodotBreakpoint[] = [];
Array.from(this.breakpoints.values()).forEach(fbp => {
breakpointList = breakpointList.concat(fbp);
});
this.server_controller.start(
project,
port,
address,
launchGameInstance,
breakpointList
);
}
public step() {
this.server_controller?.step();
}
public step_out() {
this.server_controller?.step_out();
}
public terminate() {
this.server_controller?.stop();
}
}

261
src/debugger/mediator.ts Normal file
View File

@@ -0,0 +1,261 @@
import { ServerController } from "./server_controller";
import { window, OutputChannel } from "vscode";
import { GodotDebugSession } from "./debug_session";
import { StoppedEvent, TerminatedEvent } from "vscode-debugadapter";
import { GodotDebugData, GodotVariable } from "./debug_runtime";
let output: OutputChannel;
export class Mediator {
private static controller?: ServerController;
private static debug_data?: GodotDebugData;
private static inspect_callbacks: Map<
number,
(class_name: string, variable: GodotVariable) => void
> = new Map();
private static session?: GodotDebugSession;
private static first_output = false;
private constructor() {
if (!output) {
output = window.createOutputChannel("Godot");
} else {
output.clear();
}
}
public static notify(event: string, parameters: any[] = []) {
switch (event) {
case "output":
let lines: string[] = parameters;
lines.forEach((line) => {
output?.appendLine(line);
});
if(!this.first_output) {
this.first_output = true;
this.controller?.send_request_scene_tree_command();
}
break;
case "continue":
this.controller?.continue();
break;
case "next":
this.controller?.next();
break;
case "step":
this.controller?.step();
break;
case "step_out":
this.controller?.step_out();
break;
case "inspect_object":
this.controller?.send_inspect_object_request(parameters[0]);
if (parameters[1]) {
this.inspect_callbacks.set(parameters[0], parameters[1]);
}
break;
case "inspected_object":
let inspected_variable = { name: "", value: parameters[1] };
this.build_sub_values(inspected_variable);
if (this.inspect_callbacks.has(Number(parameters[0]))) {
this.inspect_callbacks.get(Number(parameters[0]))(
inspected_variable.name,
inspected_variable
);
this.inspect_callbacks.delete(Number(parameters[0]));
} else {
this.session?.set_inspection(parameters[0], inspected_variable);
}
break;
case "stack_dump":
this.controller?.trigger_breakpoint(parameters);
this.controller?.send_request_scene_tree_command();
break;
case "request_scene_tree":
this.controller?.send_request_scene_tree_command();
break;
case "scene_tree":
this.debug_data?.scene_tree?.fill_tree(parameters[0]);
break;
case "get_scopes":
this.controller?.send_scope_request(parameters[0]);
break;
case "stack_frame_vars":
this.do_stack_frame_vars(parameters[0], parameters[1], parameters[2]);
break;
case "remove_breakpoint":
this.controller?.remove_breakpoint(parameters[0], parameters[1]);
break;
case "set_breakpoint":
this.controller?.set_breakpoint(parameters[0], parameters[1]);
break;
case "stopped_on_breakpoint":
this.debug_data.last_frames = parameters[0];
this.session?.sendEvent(new StoppedEvent("breakpoint", 0));
break;
case "stopped_on_exception":
this.debug_data.last_frames = parameters[0];
this.session?.set_exception(true);
this.session?.sendEvent(
new StoppedEvent("exception", 0, parameters[1])
);
break;
case "break":
this.controller?.break();
break;
case "changed_value":
this.controller?.set_object_property(
parameters[0],
parameters[1],
parameters[2]
);
break;
case "debug_enter":
let reason: string = parameters[0];
if (reason !== "Breakpoint") {
this.controller?.set_exception(reason);
} else {
this.controller?.set_exception("");
}
this.controller?.stack_dump();
break;
case "start":
this.first_output = false;
this.controller?.start(
parameters[0],
parameters[1],
parameters[2],
parameters[3],
parameters[4],
parameters[5],
this.debug_data
);
break;
case "debug_exit":
break;
case "stop":
this.controller?.stop();
this.session?.sendEvent(new TerminatedEvent(false));
break;
case "error":
this.controller?.set_exception(parameters[0]);
this.controller?.stop();
this.session?.sendEvent(new TerminatedEvent(false));
break;
}
}
public static set_controller(controller: ServerController) {
this.controller = controller;
}
public static set_debug_data(debug_data: GodotDebugData) {
this.debug_data = debug_data;
}
public static set_session(debug_session: GodotDebugSession) {
this.session = debug_session;
}
private static build_sub_values(va: GodotVariable) {
let value = va.value;
let sub_values: GodotVariable[] = undefined;
if (value && Array.isArray(value)) {
sub_values = value.map((va, i) => {
return { name: `${i}`, value: va } as GodotVariable;
});
} else if (value instanceof Map) {
sub_values = Array.from(value.keys()).map((va) => {
if (typeof va["stringify_value"] === "function") {
return {
name: `${va.type_name()}${va.stringify_value()}`,
value: value.get(va),
} as GodotVariable;
} else {
return {
name: `${va}`,
value: value.get(va),
} as GodotVariable;
}
});
} else if (value && typeof value["sub_values"] === "function") {
sub_values = value.sub_values().map((sva) => {
return { name: sva.name, value: sva.value } as GodotVariable;
});
}
va.sub_values = sub_values;
sub_values?.forEach((sva) => this.build_sub_values(sva));
}
private static do_stack_frame_vars(
locals: any[],
members: any[],
globals: any[]
) {
let locals_out: GodotVariable[] = [];
let members_out: GodotVariable[] = [];
let globals_out: GodotVariable[] = [];
for (
let i = 0;
i < locals.length + members.length + globals.length;
i += 2
) {
const name =
i < locals.length
? locals[i]
: i < members.length + locals.length
? members[i - locals.length]
: globals[i - locals.length - members.length];
const value =
i < locals.length
? locals[i + 1]
: i < members.length + locals.length
? members[i - locals.length + 1]
: globals[i - locals.length - members.length + 1];
let variable: GodotVariable = {
name: name,
value: value,
};
this.build_sub_values(variable);
i < locals.length
? locals_out.push(variable)
: i < members.length + locals.length
? members_out.push(variable)
: globals_out.push(variable);
}
this.session?.set_scopes(locals_out, members_out, globals_out);
}
}

View File

@@ -0,0 +1,209 @@
import {
TreeDataProvider,
EventEmitter,
Event,
ProviderResult,
TreeItem,
TreeItemCollapsibleState,
} from "vscode";
import { GodotVariable } from "../debug_runtime";
import { RawObject, ObjectId } from "../variables/variants";
export class InspectorProvider implements TreeDataProvider<RemoteProperty> {
private _on_did_change_tree_data: EventEmitter<
RemoteProperty | undefined
> = new EventEmitter<RemoteProperty | undefined>();
private tree: RemoteProperty | undefined;
public readonly onDidChangeTreeData: Event<RemoteProperty> | undefined = this
._on_did_change_tree_data.event;
constructor() {}
public clean_up() {
if (this.tree) {
this.tree = undefined;
this._on_did_change_tree_data.fire();
}
}
public fill_tree(
element_name: string,
class_name: string,
object_id: number,
variable: GodotVariable
) {
this.tree = this.parse_variable(variable, object_id);
this.tree.label = element_name;
this.tree.collapsibleState = TreeItemCollapsibleState.Expanded;
this.tree.description = class_name;
this._on_did_change_tree_data.fire();
}
public getChildren(
element?: RemoteProperty
): ProviderResult<RemoteProperty[]> {
if (!this.tree) {
return Promise.resolve([]);
}
if (!element) {
return Promise.resolve([this.tree]);
} else {
return Promise.resolve(element.properties);
}
}
public getTreeItem(element: RemoteProperty): TreeItem | Thenable<TreeItem> {
return element;
}
public get_changed_value(
parents: RemoteProperty[],
property: RemoteProperty,
new_parsed_value: any
) {
let idx = parents.length - 1;
let value = parents[idx].value;
if (Array.isArray(value)) {
let idx = parseInt(property.label);
if (idx < value.length) {
value[idx] = new_parsed_value;
}
} else if (value instanceof Map) {
value.set(property.parent.value.key, new_parsed_value);
} else if (value[property.label]) {
value[property.label] = new_parsed_value;
}
return value;
}
public get_top_id(): number {
if (this.tree) {
return this.tree.object_id;
}
return undefined;
}
public get_top_name() {
if (this.tree) {
return this.tree.label;
}
return undefined;
}
public has_tree() {
return this.tree !== undefined;
}
private parse_variable(va: GodotVariable, object_id?: number) {
let value = va.value;
let rendered_value = "";
if (typeof value === "number") {
if (Number.isInteger(value)) {
rendered_value = `${value}`;
} else {
rendered_value = `${parseFloat(value.toFixed(5))}`;
}
} else if (
typeof value === "bigint" ||
typeof value === "boolean" ||
typeof value === "string"
) {
rendered_value = `${value}`;
} else if (typeof value === "undefined") {
rendered_value = "null";
} else {
if (Array.isArray(value)) {
rendered_value = `Array[${value.length}]`;
} else if (value instanceof Map) {
if (value instanceof RawObject) {
rendered_value = `${value.class_name}`;
} else {
rendered_value = `Dictionary[${value.size}]`;
}
} else {
rendered_value = `${value.type_name()}${value.stringify_value()}`;
}
}
let child_props: RemoteProperty[] = [];
if (value) {
let sub_variables =
typeof value["sub_values"] === "function" && value instanceof ObjectId === false
? value.sub_values()
: Array.isArray(value)
? value.map((va, i) => {
return { name: `${i}`, value: va };
})
: value instanceof Map
? Array.from(value.keys()).map((va) => {
let name =
typeof va["rendered_value"] === "function"
? va.rendered_value()
: `${va}`;
let map_value = value.get(va);
return { name: name, value: map_value };
})
: [];
child_props = sub_variables?.map((va) => {
return this.parse_variable(va, object_id);
});
}
let out_prop = new RemoteProperty(
va.name,
value,
object_id,
child_props,
child_props.length === 0
? TreeItemCollapsibleState.None
: TreeItemCollapsibleState.Collapsed
);
out_prop.description = rendered_value;
out_prop.properties.forEach((prop) => {
prop.parent = out_prop;
});
out_prop.description = rendered_value;
if (value instanceof ObjectId) {
out_prop.contextValue = "remote_object";
out_prop.object_id = Number(value.id);
} else if (
typeof value === "number" ||
typeof value === "bigint" ||
typeof value === "boolean" ||
typeof value === "string"
) {
out_prop.contextValue = "editable_value";
} else if (
Array.isArray(value) ||
(value instanceof Map && value instanceof RawObject === false)
) {
out_prop.properties.forEach((prop) => (prop.changes_parent = true));
}
return out_prop;
}
}
export class RemoteProperty extends TreeItem {
public changes_parent?: boolean;
public parent?: RemoteProperty;
constructor(
public label: string,
public value: any,
public object_id: number,
public properties: RemoteProperty[],
public collapsibleState?: TreeItemCollapsibleState
) {
super(label, collapsibleState);
}
}
export class RemoteObject extends RemoteProperty {}

View File

@@ -4,7 +4,7 @@ import {
Event,
ProviderResult,
TreeItem,
TreeItemCollapsibleState
TreeItemCollapsibleState,
} from "vscode";
import path = require("path");
import fs = require("fs");
@@ -121,7 +121,7 @@ export class SceneNode extends TreeItem {
this.iconPath = {
light: light,
dark: dark
dark: dark,
};
}
}

View File

@@ -0,0 +1,296 @@
import { CommandParser } from "./commands/command_parser";
import { Mediator } from "./mediator";
import { VariantDecoder } from "./variables/variant_decoder";
import {
GodotBreakpoint,
GodotStackFrame,
GodotDebugData,
} from "./debug_runtime";
import { window } from "vscode";
const TERMINATE = require("terminate");
import net = require("net");
import utils = require("../utils");
import cp = require("child_process");
import path = require("path");
export class ServerController {
private command_buffer: Buffer[] = [];
private commands = new CommandParser();
private debug_data: GodotDebugData;
private decoder = new VariantDecoder();
private draining = false;
private exception = "";
private godot_pid: number;
private server?: net.Server;
private socket?: net.Socket;
private stepping_out = false;
public break() {
this.add_and_send(this.commands.make_break_command());
}
public continue() {
this.add_and_send(this.commands.make_continue_command());
}
public next() {
this.add_and_send(this.commands.make_next_command());
}
public remove_breakpoint(path_to: string, line: number) {
this.debug_data.remove_breakpoint(path_to, line);
this.add_and_send(
this.commands.make_remove_breakpoint_command(path_to, line)
);
}
public send_inspect_object_request(object_id: bigint) {
this.add_and_send(this.commands.make_inspect_object_command(object_id));
}
public send_request_scene_tree_command() {
this.add_and_send(this.commands.make_request_scene_tree_command());
}
public send_scope_request(frame_id: number) {
this.add_and_send(this.commands.make_stack_frame_vars_command(frame_id));
}
public set_breakpoint(path_to: string, line: number) {
this.add_and_send(
this.commands.make_send_breakpoint_command(path_to, line)
);
}
public set_exception(exception: string) {
this.exception = exception;
}
public set_object_property(
object_id: bigint,
label: string,
new_parsed_value: any
) {
this.add_and_send(
this.commands.make_set_object_value_command(
BigInt(object_id),
label,
new_parsed_value
)
);
}
public stack_dump() {
this.add_and_send(this.commands.make_stack_dump_command());
}
public start(
project_path: string,
address: string,
port: number,
launch_instance: boolean,
launch_scene: boolean,
scene_file: string | undefined,
debug_data: GodotDebugData
) {
this.debug_data = debug_data;
this.server = net.createServer((socket) => {
this.socket = socket;
if (!launch_instance) {
let breakpoints = this.debug_data.get_all_breakpoints();
breakpoints.forEach((bp) => {
this.set_breakpoint(
this.breakpoint_path(project_path, bp.file),
bp.line
);
});
}
socket.on("data", (buffer) => {
let buffers = this.split_buffers(buffer);
while (buffers.length > 0) {
let sub_buffer = buffers.shift();
let data = this.decoder.get_dataset(sub_buffer, 0).slice(1);
this.commands.parse_message(data);
}
});
socket.on("close", (had_error) => {
Mediator.notify("stop");
});
socket.on("end", () => {
Mediator.notify("stop");
});
socket.on("error", (error) => {
Mediator.notify("error", [error]);
});
socket.on("drain", () => {
socket.resume();
this.draining = false;
this.send_buffer();
});
});
this.server.listen(port, address);
if (launch_instance) {
let godot_path: string = utils.get_configuration("editor_path", "godot");
let executable_line = `"${godot_path}" --path "${project_path}" --remote-debug ${address}:${port}`;
if (launch_scene) {
let filename = "";
if (scene_file) {
filename = scene_file;
} else {
filename = window.activeTextEditor.document.fileName;
}
if (path.extname(filename).toLowerCase() === ".tscn") {
executable_line += ` ${path.relative(project_path, filename)}`;
} else {
window.showErrorMessage("Active file is not a TSCN file.");
Mediator.notify("stop");
return;
}
}
executable_line += this.breakpoint_string(
debug_data.get_all_breakpoints(),
project_path
);
let godot_exec = cp.exec(executable_line);
this.godot_pid = godot_exec.pid;
}
}
public step() {
this.add_and_send(this.commands.make_step_command());
}
public step_out() {
this.stepping_out = true;
this.add_and_send(this.commands.make_next_command());
}
public stop() {
this.socket?.end(() => {
this.server.close();
this.server = undefined;
});
if (this.godot_pid) {
TERMINATE(this.godot_pid, (error: string | undefined) => {
if (error) {
Mediator.notify("error", [error]);
}
});
this.godot_pid = undefined;
}
}
public trigger_breakpoint(stack_frames: GodotStackFrame[]) {
let continue_stepping = false;
let stack_count = stack_frames.length;
let file = stack_frames[0].file.replace(
"res://",
`${this.debug_data.project_path}/`
);
let line = stack_frames[0].line;
if (this.stepping_out) {
let breakpoint = this.debug_data
.get_breakpoints(file)
.find((bp) => bp.line === line);
if (!breakpoint) {
if (this.debug_data.stack_count > 1) {
continue_stepping = this.debug_data.stack_count === stack_count;
} else {
let file_same =
stack_frames[0].file === this.debug_data.last_frame.file;
let func_same =
stack_frames[0].function === this.debug_data.last_frame.function;
let line_greater =
stack_frames[0].line >= this.debug_data.last_frame.line;
continue_stepping = file_same && func_same && line_greater;
}
}
}
this.debug_data.stack_count = stack_count;
this.debug_data.last_frame = stack_frames[0];
if (continue_stepping) {
this.next();
return;
}
this.stepping_out = false;
this.debug_data.stack_files = stack_frames.map((sf) => {
return sf.file;
});
if (this.exception.length === 0) {
Mediator.notify("stopped_on_breakpoint", [stack_frames]);
} else {
Mediator.notify("stopped_on_exception", [stack_frames, this.exception]);
}
}
private add_and_send(buffer: Buffer) {
this.command_buffer.push(buffer);
this.send_buffer();
}
private breakpoint_path(project_path: string, file: string) {
let relative_path = path.relative(project_path, file).replace(/\\/g, "/");
if (relative_path.length !== 0) {
return `res://${relative_path}`;
}
return undefined;
}
private breakpoint_string(
breakpoints: GodotBreakpoint[],
project_path: string
) {
let output = "";
if (breakpoints.length > 0) {
output += " --breakpoints ";
breakpoints.forEach((bp, i) => {
output += `${this.breakpoint_path(project_path, bp.file)}:${bp.line}${
i < breakpoints.length - 1 ? "," : ""
}`;
});
}
return output;
}
private send_buffer() {
if (!this.socket) {
return;
}
while (!this.draining && this.command_buffer.length > 0) {
this.draining = !this.socket.write(this.command_buffer.shift());
}
}
private split_buffers(buffer: Buffer) {
let len = buffer.byteLength;
let offset = 0;
let buffers: Buffer[] = [];
while (len > 0) {
let sub_len = buffer.readUInt32LE(offset) + 4;
buffers.push(buffer.slice(offset, offset + sub_len));
offset += sub_len;
len -= sub_len;
}
return buffers;
}
}

View File

@@ -1,91 +0,0 @@
export default function stringify(
var_value: any,
decimal_precision: number = 4
) {
let type = "";
let value = "";
let skip_id = true;
if (typeof var_value === "number" && !Number.isInteger(var_value)) {
value = String(
+Number.parseFloat(no_exponents(var_value)).toFixed(decimal_precision)
);
type = "Float";
} else if (Array.isArray(var_value)) {
value = "Array";
type = "Array";
skip_id = false;
} else if (var_value instanceof Map) {
value = "Dictionary";
type = "Dictionary";
skip_id = false;
} else if (typeof var_value === "object") {
skip_id = false;
if (var_value.__type__) {
if (var_value.__type__ === "Object") {
skip_id = true;
}
if (var_value.__render__) {
value = var_value.__render__();
} else {
value = var_value.__type__;
}
type = var_value.__type__;
} else {
value = "Object";
}
} else {
if (var_value) {
if (Number.isInteger(var_value)) {
type = "Int";
value = `${var_value}`;
} else if (typeof var_value === "string") {
type = "String";
value = String(var_value);
} else if (typeof var_value === "boolean") {
type = "Bool";
value = "true";
} else {
type = "unknown";
value = `${var_value}`;
}
} else {
if (Number.isInteger(var_value)) {
type = "Int";
value = "0";
} else if (typeof var_value === "boolean") {
type = "Bool";
value = "false";
} else {
type = "unknown";
value = "null";
}
}
}
return { type: type, value: value, skip_id: skip_id };
}
function no_exponents(value: number): string {
let data = String(value).split(/[eE]/);
if (data.length === 1) {
return data[0];
}
let z = "",
sign = value < 0 ? "-" : "";
let str = data[0].replace(".", "");
let mag = Number(data[1]) + 1;
if (mag < 0) {
z = sign + "0.";
while (mag++) {
z += "0";
}
return z + str.replace(/^\-/, "");
}
mag -= str.length;
while (mag--) {
z += 0;
}
return str + z;
}

View File

@@ -1,316 +0,0 @@
import { DebugProtocol } from "vscode-debugprotocol";
import { GodotDebugRuntime } from "./godot_debug_runtime";
import stringify from "./stringify";
export class VariableScopeBuilder {
private inspect_callback: (() => void) | undefined;
private inspected: number[] = [];
private inspected_cache = new Map<
number,
{ class_name: string; properties: any[] }
>();
private over_scopes: DebugProtocol.Scope[];
private scope_id = 1;
private scopes = new Map<number, Map<string, VariableScope[]>>();
constructor(
private runtime: GodotDebugRuntime,
private stack_level: number,
private stack_files: string[],
private raw_scopes: { locals: any[]; members: any[]; globals: any[] },
private have_scopes: (() => void)[] = []
) {}
public get(level: number, file: string) {
return this.scopes.get(level).get(file);
}
public get_keys(level: number) {
return Array.from(this.scopes.get(level).keys());
}
public parse(callback: (over_scopes: DebugProtocol.Scope[]) => void) {
let file = this.stack_files[this.stack_level];
let file_scopes: VariableScope[] = [];
let local_scope = new VariableScope(this.scope_id++);
let member_scope = new VariableScope(this.scope_id++);
let global_scope = new VariableScope(this.scope_id++);
file_scopes.push(local_scope);
file_scopes.push(member_scope);
file_scopes.push(global_scope);
this.scopes.set(
this.stack_level,
new Map<string, VariableScope[]>([[file, file_scopes]])
);
let out_local_scope: DebugProtocol.Scope = {
name: "Locals",
namedVariables: this.raw_scopes.locals.length / 2,
presentationHint: "locals",
expensive: false,
variablesReference: local_scope.id
};
for (let i = 0; i < this.raw_scopes.locals.length; i += 2) {
let name = this.raw_scopes.locals[i];
let value = this.raw_scopes.locals[i + 1];
this.drill_scope(
local_scope,
{
name: name,
value: value ? value : undefined
},
!value && typeof value === "number"
);
}
let out_member_scope: DebugProtocol.Scope = {
name: "Members",
namedVariables: this.raw_scopes.members.length / 2,
presentationHint: "locals",
expensive: false,
variablesReference: member_scope.id
};
for (let i = 0; i < this.raw_scopes.members.length; i += 2) {
let name = this.raw_scopes.members[i];
let value = this.raw_scopes.members[i + 1];
this.drill_scope(
member_scope,
{ name: name, value: value },
!value && typeof value === "number"
);
}
let out_global_scope: DebugProtocol.Scope = {
name: "Globals",
namedVariables: this.raw_scopes.globals.length / 2,
presentationHint: "locals",
expensive: false,
variablesReference: global_scope.id
};
for (let i = 0; i < this.raw_scopes.globals.length; i += 2) {
let name = this.raw_scopes.globals[i];
let value = this.raw_scopes.globals[i + 1];
this.drill_scope(
global_scope,
{ name: name, value: value },
!value && typeof value === "number"
);
}
this.over_scopes = [out_local_scope, out_member_scope, out_global_scope];
if (this.inspected.length === 0) {
while (this.have_scopes.length > 0) {
this.have_scopes.shift()();
}
callback(this.over_scopes);
} else {
this.inspect_callback = () => {
while (this.have_scopes.length > 0) {
this.have_scopes.shift()();
}
callback(this.over_scopes);
};
}
}
public size() {
return this.scopes.size;
}
private drill_scope(
scope: VariableScope,
variable: any,
is_zero_number?: boolean
) {
if (is_zero_number) {
variable.value = 0;
}
let id = scope.get_id_for(variable.name);
if (id === -1) {
id = this.scope_id++;
}
scope.set_variable(variable.name, variable.value, id);
if (Array.isArray(variable.value) || variable.value instanceof Map) {
let length = 0;
let values: any[];
if (variable.value instanceof Map) {
length = variable.value.size;
let keys = Array.from(variable.value.keys());
values = keys.map(key => {
let value = variable.value.get(key);
let stringified_key = stringify(key).value;
return {
__type__: "Pair",
key: key,
value: value,
__render__: () => stringified_key
};
});
variable.value = values;
} else {
length = variable.value.length;
values = variable.value;
}
for (let i = 0; i < length; i++) {
let name = `${variable.name}.${i}`;
scope.set_sub_variable_for(id, name, values[i]);
this.drill_scope(scope, {
name: name,
value: values[i]
});
}
} else if (typeof variable.value === "object") {
if (variable.value.__type__ && variable.value.__type__ === "Object") {
if (!this.inspected_cache.has(id)) {
if (this.inspected.indexOf(id) === -1) {
this.inspected.push(id);
this.runtime.inspect_object(
variable.value.id,
(class_name, properties) => {
this.inspected_cache.set(id, {
class_name: class_name,
properties: properties
});
this.parse_deeper(variable, scope, id, class_name, properties);
}
);
}
} else {
let cached = this.inspected_cache.get(id);
this.parse_deeper(
variable,
scope,
id,
cached.class_name,
cached.properties
);
}
} else {
for (const PROP in variable.value) {
if (PROP !== "__type__" && PROP !== "__render__") {
let name = `${variable.name}.${PROP}`;
scope.set_sub_variable_for(id, name, variable.value[PROP]);
this.drill_scope(scope, {
name: name,
value: variable.value[PROP]
});
}
}
}
}
}
private parse_deeper(
variable: any,
scope: VariableScope,
id: number,
class_name: string,
properties: any[][]
) {
variable.value.__type__ = class_name;
let start_index = 0;
variable.value.__render__ = () => `${class_name}`;
let relevant_properties = properties.slice(start_index + 1).filter(p => {
if (!p[5]) {
return Number.isInteger(p[5]);
}
return true;
});
relevant_properties.forEach(p => {
let sub_name = `${variable.name}.${p[0]}`;
scope.set_sub_variable_for(id, sub_name, p[5]);
this.drill_scope(scope, { name: sub_name, value: p[5] });
});
let inspected_idx = this.inspected.indexOf(variable.value.id);
if (inspected_idx !== -1) {
this.inspected.splice(inspected_idx, 1);
}
if (this.inspected.length === 0 && this.inspect_callback) {
this.inspect_callback();
}
}
}
export class VariableScope {
private sub_variables = new Map<number, { name: string; value: any }[]>();
private variables = new Map<number, { name: string; value: any }>();
public readonly id: number;
constructor(id: number) {
this.id = id;
}
public get_id_for(name: string) {
let ids = Array.from(this.variables.keys());
return (
ids.find(v => {
let var_name = this.variables.get(v).name;
return var_name === name;
}) || -1
);
}
public get_sub_variable_for(name: string, id: number) {
let sub_variables = this.sub_variables.get(id);
if (sub_variables) {
let index = sub_variables.findIndex(sv => {
return sv.name === name;
});
if (index !== -1) {
return sub_variables[index];
}
}
return undefined;
}
public get_sub_variables_for(id: number) {
return this.sub_variables.get(id);
}
public get_variable(id: number): { name: string; value: any } | undefined {
return this.variables.get(id);
}
public get_variable_ids() {
return Array.from(this.variables.keys());
}
public set_sub_variable_for(variable_id: number, name: string, value: any) {
let sub_variables = this.sub_variables.get(variable_id);
if (!sub_variables) {
sub_variables = [];
this.sub_variables.set(variable_id, sub_variables);
}
let index = sub_variables.findIndex(sv => {
return sv.name === name;
});
if (index === -1) {
sub_variables.push({ name: name, value: value });
}
}
public set_variable(name: string, value: any, id: number) {
let variable = { name: name, value: value };
this.variables.set(id, variable);
}
}

View File

@@ -0,0 +1,392 @@
import {
GDScriptTypes,
BufferModel,
Vector3,
Vector2,
Basis,
AABB,
Color,
NodePath,
ObjectId,
Plane,
Quat,
Rect2,
Transform,
Transform2D,
RawObject,
} from "./variants";
export class VariantDecoder {
public decode_variant(model: BufferModel) {
let type = this.decode_UInt32(model);
switch (type & 0xff) {
case GDScriptTypes.BOOL:
return this.decode_UInt32(model) !== 0;
case GDScriptTypes.INT:
if (type & (1 << 16)) {
return this.decode_Int64(model);
} else {
return this.decode_Int32(model);
}
case GDScriptTypes.REAL:
if (type & (1 << 16)) {
return this.decode_Double(model);
} else {
return this.decode_Float(model);
}
case GDScriptTypes.STRING:
return this.decode_String(model);
case GDScriptTypes.VECTOR2:
return this.decode_Vector2(model);
case GDScriptTypes.RECT2:
return this.decode_Rect2(model);
case GDScriptTypes.VECTOR3:
return this.decode_Vector3(model);
case GDScriptTypes.TRANSFORM2D:
return this.decode_Transform2D(model);
case GDScriptTypes.PLANE:
return this.decode_Plane(model);
case GDScriptTypes.QUAT:
return this.decode_Quat(model);
case GDScriptTypes.AABB:
return this.decode_AABB(model);
case GDScriptTypes.BASIS:
return this.decode_Basis(model);
case GDScriptTypes.TRANSFORM:
return this.decode_Transform(model);
case GDScriptTypes.COLOR:
return this.decode_Color(model);
case GDScriptTypes.NODE_PATH:
return this.decode_NodePath(model);
case GDScriptTypes.OBJECT:
if (type & (1 << 16)) {
return this.decode_Object_id(model);
} else {
return this.decode_Object(model);
}
case GDScriptTypes.DICTIONARY:
return this.decode_Dictionary(model);
case GDScriptTypes.ARRAY:
return this.decode_Array(model);
case GDScriptTypes.POOL_BYTE_ARRAY:
return this.decode_PoolByteArray(model);
case GDScriptTypes.POOL_INT_ARRAY:
return this.decode_PoolIntArray(model);
case GDScriptTypes.POOL_REAL_ARRAY:
return this.decode_PoolFloatArray(model);
case GDScriptTypes.POOL_STRING_ARRAY:
return this.decode_PoolStringArray(model);
case GDScriptTypes.POOL_VECTOR2_ARRAY:
return this.decode_PoolVector2Array(model);
case GDScriptTypes.POOL_VECTOR3_ARRAY:
return this.decode_PoolVector3Array(model);
case GDScriptTypes.POOL_COLOR_ARRAY:
return this.decode_PoolColorArray(model);
default:
return undefined;
}
}
public get_dataset(buffer: Buffer, offset: number) {
let len = buffer.readUInt32LE(offset);
let model: BufferModel = {
buffer: buffer,
offset: offset + 4,
len: len,
};
let output = [];
output.push(len + 4);
do {
let value = this.decode_variant(model);
output.push(value);
} while (model.len > 0);
return output;
}
private decode_AABB(model: BufferModel) {
return new AABB(this.decode_Vector3(model), this.decode_Vector3(model));
}
private decode_Array(model: BufferModel) {
let output: Array<any> = [];
let count = this.decode_UInt32(model);
for (let i = 0; i < count; i++) {
let value = this.decode_variant(model);
output.push(value);
}
return output;
}
private decode_Basis(model: BufferModel) {
return new Basis(
this.decode_Vector3(model),
this.decode_Vector3(model),
this.decode_Vector3(model)
);
}
private decode_Color(model: BufferModel) {
let rgb = this.decode_Vector3(model);
let a = this.decode_Float(model);
return new Color(rgb.x, rgb.y, rgb.z, a);
}
private decode_Dictionary(model: BufferModel) {
let output = new Map<any, any>();
let count = this.decode_UInt32(model);
for (let i = 0; i < count; i++) {
let key = this.decode_variant(model);
let value = this.decode_variant(model);
output.set(key, value);
}
return output;
}
private decode_Double(model: BufferModel) {
let d = model.buffer.readDoubleLE(model.offset);
model.offset += 8;
model.len -= 8;
return d; // + (d < 0 ? -1e-10 : 1e-10);
}
private decode_Float(model: BufferModel) {
let f = model.buffer.readFloatLE(model.offset);
model.offset += 4;
model.len -= 4;
return f; // + (f < 0 ? -1e-10 : 1e-10);
}
private decode_Int32(model: BufferModel) {
let u = model.buffer.readInt32LE(model.offset);
model.len -= 4;
model.offset += 4;
return u;
}
private decode_Int64(model: BufferModel) {
let hi = model.buffer.readInt32LE(model.offset);
let lo = model.buffer.readInt32LE(model.offset + 4);
let u: BigInt = BigInt((hi << 32) | lo);
model.len -= 8;
model.offset += 8;
return u;
}
private decode_NodePath(model: BufferModel) {
let name_count = this.decode_UInt32(model) & 0x7fffffff;
let subname_count = this.decode_UInt32(model);
let flags = this.decode_UInt32(model);
let is_absolute = (flags & 1) === 1;
if (flags & 2) {
//Obsolete format with property separate from subPath
subname_count++;
}
let total = name_count + subname_count;
let names: string[] = [];
let sub_names: string[] = [];
for (let i = 0; i < total; i++) {
let str = this.decode_String(model);
if (i < name_count) {
names.push(str);
} else {
sub_names.push(str);
}
}
return new NodePath(names, sub_names, is_absolute);
}
private decode_Object(model: BufferModel) {
let class_name = this.decode_String(model);
let prop_count = this.decode_UInt32(model);
let output = new RawObject(class_name);
for (let i = 0; i < prop_count; i++) {
let name = this.decode_String(model);
let value = this.decode_variant(model);
output.set(name, value);
}
return output;
}
private decode_Object_id(model: BufferModel) {
let id = this.decode_UInt64(model);
return new ObjectId(id);
}
private decode_Plane(model: BufferModel) {
let x = this.decode_Float(model);
let y = this.decode_Float(model);
let z = this.decode_Float(model);
let d = this.decode_Float(model);
return new Plane(x, y, z, d);
}
private decode_PoolByteArray(model: BufferModel) {
let count = this.decode_UInt32(model);
let output: number[] = [];
for (let i = 0; i < count; i++) {
output.push(model.buffer.readUInt8(model.offset));
model.offset++;
model.len--;
}
return output;
}
private decode_PoolColorArray(model: BufferModel) {
let count = this.decode_UInt32(model);
let output: Color[] = [];
for (let i = 0; i < count; i++) {
output.push(this.decode_Color(model));
}
return output;
}
private decode_PoolFloatArray(model: BufferModel) {
let count = this.decode_UInt32(model);
let output: number[] = [];
for (let i = 0; i < count; i++) {
output.push(this.decode_Float(model));
}
return output;
}
private decode_PoolIntArray(model: BufferModel) {
let count = this.decode_UInt32(model);
let output: number[] = [];
for (let i = 0; i < count; i++) {
output.push(this.decode_Int32(model));
}
return output;
}
private decode_PoolStringArray(model: BufferModel) {
let count = this.decode_UInt32(model);
let output: string[] = [];
for (let i = 0; i < count; i++) {
output.push(this.decode_String(model));
}
return output;
}
private decode_PoolVector2Array(model: BufferModel) {
let count = this.decode_UInt32(model);
let output: Vector2[] = [];
for (let i = 0; i < count; i++) {
output.push(this.decode_Vector2(model));
}
return output;
}
private decode_PoolVector3Array(model: BufferModel) {
let count = this.decode_UInt32(model);
let output: Vector3[] = [];
for (let i = 0; i < count; i++) {
output.push(this.decode_Vector3(model));
}
return output;
}
private decode_Quat(model: BufferModel) {
let x = this.decode_Float(model);
let y = this.decode_Float(model);
let z = this.decode_Float(model);
let w = this.decode_Float(model);
return new Quat(x, y, z, w);
}
private decode_Rect2(model: BufferModel) {
return new Rect2(this.decode_Vector2(model), this.decode_Vector2(model));
}
private decode_String(model: BufferModel) {
let len = this.decode_UInt32(model);
let pad = 0;
if (len % 4 !== 0) {
pad = 4 - (len % 4);
}
let str = model.buffer.toString("utf8", model.offset, model.offset + len);
len += pad;
model.offset += len;
model.len -= len;
return str;
}
private decode_Transform(model: BufferModel) {
return new Transform(this.decode_Basis(model), this.decode_Vector3(model));
}
private decode_Transform2D(model: BufferModel) {
return new Transform2D(
this.decode_Vector2(model),
this.decode_Vector2(model),
this.decode_Vector2(model)
);
}
private decode_UInt32(model: BufferModel) {
let u = model.buffer.readUInt32LE(model.offset);
model.len -= 4;
model.offset += 4;
return u;
}
private decode_UInt64(model: BufferModel) {
let hi = model.buffer.readUInt32LE(model.offset);
let lo = model.buffer.readUInt32LE(model.offset + 4);
let u = BigInt((hi << 32) | lo);
model.len -= 8;
model.offset += 8;
return u;
}
private decode_Vector2(model: BufferModel) {
let x = this.decode_Float(model);
let y = this.decode_Float(model);
return new Vector2(x, y);
}
private decode_Vector3(model: BufferModel) {
let x = this.decode_Float(model);
let y = this.decode_Float(model);
let z = this.decode_Float(model);
return new Vector3(x, y, z);
}
}

View File

@@ -0,0 +1,359 @@
import {
GDScriptTypes,
BufferModel,
Vector3,
Vector2,
Basis,
AABB,
Color,
Plane,
Quat,
Rect2,
Transform,
Transform2D,
} from "./variants";
export class VariantEncoder {
public encode_variant(
value:
| number
| bigint
| boolean
| string
| Map<any, any>
| Array<any>
| object
| undefined,
model?: BufferModel
) {
if (
typeof value === "number" &&
Number.isInteger(value) &&
(value > 2147483647 || value < -2147483648)
) {
value = BigInt(value);
}
if (!model) {
let size = this.size_variant(value);
let buffer = Buffer.alloc(size + 4);
model = {
buffer: buffer,
offset: 0,
len: 0,
};
this.encode_UInt32(size, model);
}
switch (typeof value) {
case "number":
{
let is_integer = Number.isInteger(value);
if (is_integer) {
this.encode_UInt32(GDScriptTypes.INT, model);
this.encode_UInt32(value, model);
} else {
this.encode_UInt32(GDScriptTypes.REAL | (1 << 16), model);
this.encode_Float(value, model);
}
}
break;
case "bigint":
this.encode_UInt32(GDScriptTypes.INT | (1 << 16), model);
this.encode_UInt64(value, model);
break;
case "boolean":
this.encode_UInt32(GDScriptTypes.BOOL, model);
this.encode_Bool(value, model);
break;
case "string":
this.encode_UInt32(GDScriptTypes.STRING, model);
this.encode_String(value, model);
break;
case "undefined":
break;
default:
if (Array.isArray(value)) {
this.encode_UInt32(GDScriptTypes.ARRAY, model);
this.encode_Array(value, model);
} else if (value instanceof Map) {
this.encode_UInt32(GDScriptTypes.DICTIONARY, model);
this.encode_Dictionary(value, model);
} else {
if (value instanceof Vector2) {
this.encode_UInt32(GDScriptTypes.VECTOR2, model);
this.encode_Vector2(value, model);
} else if (value instanceof Rect2) {
this.encode_UInt32(GDScriptTypes.RECT2, model);
this.encode_Rect2(value, model);
} else if (value instanceof Vector3) {
this.encode_UInt32(GDScriptTypes.VECTOR3, model);
this.encode_Vector3(value, model);
} else if (value instanceof Transform2D) {
this.encode_UInt32(GDScriptTypes.TRANSFORM2D, model);
this.encode_Transform2D(value, model);
} else if (value instanceof Plane) {
this.encode_UInt32(GDScriptTypes.PLANE, model);
this.encode_Plane(value, model);
} else if (value instanceof Quat) {
this.encode_UInt32(GDScriptTypes.QUAT, model);
this.encode_Quat(value, model);
} else if (value instanceof AABB) {
this.encode_UInt32(GDScriptTypes.AABB, model);
this.encode_AABB(value, model);
} else if (value instanceof Basis) {
this.encode_UInt32(GDScriptTypes.BASIS, model);
this.encode_Basis(value, model);
} else if (value instanceof Transform) {
this.encode_UInt32(GDScriptTypes.TRANSFORM, model);
this.encode_Transform(value, model);
} else if (value instanceof Color) {
this.encode_UInt32(GDScriptTypes.COLOR, model);
this.encode_Color(value, model);
}
}
}
return model.buffer;
}
private encode_AABB(value: AABB, model: BufferModel) {
this.encode_Vector3(value.position, model);
this.encode_Vector3(value.size, model);
}
private encode_Array(arr: any[], model: BufferModel) {
let size = arr.length;
this.encode_UInt32(size, model);
arr.forEach((e) => {
this.encode_variant(e, model);
});
}
private encode_Basis(value: Basis, model: BufferModel) {
this.encode_Vector3(value.x, model);
this.encode_Vector3(value.y, model);
this.encode_Vector3(value.z, model);
}
private encode_Bool(bool: boolean, model: BufferModel) {
this.encode_UInt32(bool ? 1 : 0, model);
}
private encode_Color(value: Color, model: BufferModel) {
this.encode_Float(value.r, model);
this.encode_Float(value.g, model);
this.encode_Float(value.b, model);
this.encode_Float(value.a, model);
}
private encode_Dictionary(dict: Map<any, any>, model: BufferModel) {
let size = dict.size;
this.encode_UInt32(size, model);
let keys = Array.from(dict.keys());
keys.forEach((key) => {
let value = dict.get(key);
this.encode_variant(key, model);
this.encode_variant(value, model);
});
}
private encode_Double(value: number, model: BufferModel) {
model.buffer.writeDoubleLE(value, model.offset);
model.offset += 8;
}
private encode_Float(value: number, model: BufferModel) {
model.buffer.writeFloatLE(value, model.offset);
model.offset += 4;
}
private encode_Plane(value: Plane, model: BufferModel) {
this.encode_Float(value.x, model);
this.encode_Float(value.y, model);
this.encode_Float(value.z, model);
this.encode_Float(value.d, model);
}
private encode_Quat(value: Quat, model: BufferModel) {
this.encode_Float(value.x, model);
this.encode_Float(value.y, model);
this.encode_Float(value.z, model);
this.encode_Float(value.w, model);
}
private encode_Rect2(value: Rect2, model: BufferModel) {
this.encode_Vector2(value.position, model);
this.encode_Vector2(value.size, model);
}
private encode_String(str: string, model: BufferModel) {
let str_len = str.length;
this.encode_UInt32(str_len, model);
model.buffer.write(str, model.offset, str_len, "utf8");
model.offset += str_len;
str_len += 4;
while (str_len % 4) {
str_len++;
model.buffer.writeUInt8(0, model.offset);
model.offset++;
}
}
private encode_Transform(value: Transform, model: BufferModel) {
this.encode_Basis(value.basis, model);
this.encode_Vector3(value.origin, model);
}
private encode_Transform2D(value: Transform2D, model: BufferModel) {
this.encode_Vector2(value.origin, model);
this.encode_Vector2(value.x, model);
this.encode_Vector2(value.y, model);
}
private encode_UInt32(int: number, model: BufferModel) {
model.buffer.writeUInt32LE(int, model.offset);
model.offset += 4;
}
private encode_UInt64(value: bigint, model: BufferModel) {
let hi = Number(value >> BigInt(32));
let lo = Number(value);
this.encode_UInt32(lo, model);
this.encode_UInt32(hi, model);
}
private encode_Vector2(value: Vector2, model: BufferModel) {
this.encode_Float(value.x, model);
this.encode_Float(value.y, model);
}
private encode_Vector3(value: Vector3, model: BufferModel) {
this.encode_Float(value.x, model);
this.encode_Float(value.y, model);
this.encode_Float(value.z, model);
}
private size_Bool(): number {
return this.size_UInt32();
}
private size_Dictionary(dict: Map<any, any>): number {
let size = this.size_UInt32();
let keys = Array.from(dict.keys());
keys.forEach((key) => {
let value = dict.get(key);
size += this.size_variant(key);
size += this.size_variant(value);
});
return size;
}
private size_String(str: string): number {
let size = this.size_UInt32() + str.length;
while (size % 4) {
size++;
}
return size;
}
private size_UInt32(): number {
return 4;
}
private size_UInt64(): number {
return 8;
}
private size_array(arr: any[]): number {
let size = this.size_UInt32();
arr.forEach((e) => {
size += this.size_variant(e);
});
return size;
}
private size_variant(
value:
| number
| bigint
| boolean
| string
| Map<any, any>
| any[]
| object
| undefined
): number {
let size = 4;
if (
typeof value === "number" &&
(value > 2147483647 || value < -2147483648)
) {
value = BigInt(value);
}
switch (typeof value) {
case "number":
size += this.size_UInt32();
break;
case "bigint":
size += this.size_UInt64();
break;
case "boolean":
size += this.size_Bool();
break;
case "string":
size += this.size_String(value);
break;
case "undefined":
break;
default:
if (Array.isArray(value)) {
size += this.size_array(value);
break;
} else if (value instanceof Map) {
size += this.size_Dictionary(value);
break;
} else {
switch (value["__type__"]) {
case "Vector2":
size += this.size_UInt32() * 2;
break;
case "Rect2":
size += this.size_UInt32() * 4;
break;
case "Vector3":
size += this.size_UInt32() * 3;
break;
case "Transform2D":
size += this.size_UInt32() * 6;
break;
case "Plane":
size += this.size_UInt32() * 4;
break;
case "Quat":
size += this.size_UInt32() * 4;
break;
case "AABB":
size += this.size_UInt32() * 6;
break;
case "Basis":
size += this.size_UInt32() * 9;
break;
case "Transform":
size += this.size_UInt32() * 12;
break;
case "Color":
size += this.size_UInt32() * 4;
break;
}
}
}
return size;
}
}

View File

@@ -0,0 +1,332 @@
import { GodotVariable } from "../debug_runtime";
export enum GDScriptTypes {
NIL,
// atomic types
BOOL,
INT,
REAL,
STRING,
// math types
VECTOR2, // 5
RECT2,
VECTOR3,
TRANSFORM2D,
PLANE,
QUAT, // 10
AABB,
BASIS,
TRANSFORM,
// misc types
COLOR,
NODE_PATH, // 15
_RID,
OBJECT,
DICTIONARY,
ARRAY,
// arrays
POOL_BYTE_ARRAY, // 20
POOL_INT_ARRAY,
POOL_REAL_ARRAY,
POOL_STRING_ARRAY,
POOL_VECTOR2_ARRAY,
POOL_VECTOR3_ARRAY, // 25
POOL_COLOR_ARRAY,
VARIANT_MAX,
}
export interface BufferModel {
buffer: Buffer;
len: number;
offset: number;
}
export interface GDObject {
stringify_value(): string;
sub_values(): GodotVariable[];
type_name(): string;
}
function clean_number(value: number) {
return +Number.parseFloat(String(value)).toFixed(1);
}
export class Vector3 implements GDObject {
constructor(
public x: number = 0.0,
public y: number = 0.0,
public z: number = 0.0
) {}
public stringify_value(): string {
return `(${clean_number(this.x)}, ${clean_number(this.y)}, ${clean_number(
this.z
)})`;
}
public sub_values(): GodotVariable[] {
return [
{ name: "x", value: this.x },
{ name: "y", value: this.y },
{ name: "z", value: this.z },
];
}
public type_name(): string {
return "Vector3";
}
}
export class Vector2 implements GDObject {
constructor(public x: number = 0.0, public y: number = 0.0) {}
public stringify_value(): string {
return `(${clean_number(this.x)}, ${clean_number(this.y)})`;
}
public sub_values(): GodotVariable[] {
return [
{ name: "x", value: this.x },
{ name: "y", value: this.y },
];
}
public type_name(): string {
return "Vector2";
}
}
export class Basis implements GDObject {
constructor(public x: Vector3, public y: Vector3, public z: Vector3) {}
public stringify_value(): string {
return `(${this.x.stringify_value()}, ${this.y.stringify_value()}, ${this.z.stringify_value()})`;
}
public sub_values(): GodotVariable[] {
return [
{ name: "x", value: this.x },
{ name: "y", value: this.y },
{ name: "z", value: this.z },
];
}
public type_name(): string {
return "Basis";
}
}
export class AABB implements GDObject {
constructor(public position: Vector3, public size: Vector3) {}
public stringify_value(): string {
return `(${this.position.stringify_value()}, ${this.size.stringify_value()})`;
}
public sub_values(): GodotVariable[] {
return [
{ name: "position", value: this.position },
{ name: "size", value: this.size },
];
}
public type_name(): string {
return "AABB";
}
}
export class Color implements GDObject {
constructor(
public r: number,
public g: number,
public b: number,
public a: number = 1.0
) {}
public stringify_value(): string {
return `(${clean_number(this.r)}, ${clean_number(this.g)}, ${clean_number(
this.b
)}, ${clean_number(this.a)})`;
}
public sub_values(): GodotVariable[] {
return [
{ name: "r", value: this.r },
{ name: "g", value: this.g },
{ name: "b", value: this.b },
{ name: "a", value: this.a },
];
}
public type_name(): string {
return "Color";
}
}
export class NodePath implements GDObject {
constructor(
public names: string[],
public sub_names: string[],
public absolute: boolean
) {}
public stringify_value(): string {
return `(/${this.names.join("/")}${
this.sub_names.length > 0 ? ":" : ""
}${this.sub_names.join(":")})`;
}
public sub_values(): GodotVariable[] {
return [
{ name: "names", value: this.names },
{ name: "sub_names", value: this.sub_names },
{ name: "absolute", value: this.absolute },
];
}
public type_name(): string {
return "NodePath";
}
}
export class RawObject extends Map<any, any> {
constructor(public class_name: string) {
super();
}
}
export class ObjectId implements GDObject {
constructor(public id: bigint) {}
public stringify_value(): string {
return `<${this.id}>`;
}
public sub_values(): GodotVariable[] {
return [{ name: "id", value: this.id }];
}
public type_name(): string {
return "Object";
}
}
export class Plane implements GDObject {
constructor(
public x: number,
public y: number,
public z: number,
public d: number
) {}
public stringify_value(): string {
return `(${clean_number(this.x)}, ${clean_number(this.y)}, ${clean_number(
this.z
)}, ${clean_number(this.d)})`;
}
public sub_values(): GodotVariable[] {
return [
{ name: "x", value: this.x },
{ name: "y", value: this.y },
{ name: "z", value: this.z },
{ name: "d", value: this.d },
];
}
public type_name(): string {
return "Plane";
}
}
export class Quat implements GDObject {
constructor(
public x: number,
public y: number,
public z: number,
public w: number
) {}
public stringify_value(): string {
return `(${clean_number(this.x)}, ${clean_number(this.y)}, ${clean_number(
this.z
)}, ${clean_number(this.w)})`;
}
public sub_values(): GodotVariable[] {
return [
{ name: "x", value: this.x },
{ name: "y", value: this.y },
{ name: "z", value: this.z },
{ name: "w", value: this.w },
];
}
public type_name(): string {
return "Quat";
}
}
export class Rect2 implements GDObject {
constructor(public position: Vector2, public size: Vector2) {}
public stringify_value(): string {
return `(${this.position.stringify_value()} - ${this.size.stringify_value()})`;
}
public sub_values(): GodotVariable[] {
return [
{ name: "position", value: this.position },
{ name: "size", value: this.size },
];
}
public type_name(): string {
return "Rect2";
}
}
export class Transform implements GDObject {
constructor(public basis: Basis, public origin: Vector3) {}
public stringify_value(): string {
return `(${this.basis.stringify_value()} - ${this.origin.stringify_value()})`;
}
public sub_values(): GodotVariable[] {
return [
{ name: "basis", value: this.basis },
{ name: "origin", value: this.origin },
];
}
public type_name(): string {
return "Transform";
}
}
export class Transform2D implements GDObject {
constructor(public origin: Vector2, public x: Vector2, public y: Vector2) {}
public stringify_value(): string {
return `(${this.origin.stringify_value()} - (${this.x.stringify_value()}, ${this.y.stringify_value()})`;
}
public sub_values(): GodotVariable[] {
return [
{ name: "origin", value: this.origin },
{ name: "x", value: this.x },
{ name: "y", value: this.y },
];
}
public type_name(): string {
return "Transform2D";
}
}

View File

@@ -1,905 +0,0 @@
enum GDScriptTypes {
NIL,
// atomic types
BOOL,
INT,
REAL,
STRING,
// math types
VECTOR2, // 5
RECT2,
VECTOR3,
TRANSFORM2D,
PLANE,
QUAT, // 10
AABB,
BASIS,
TRANSFORM,
// misc types
COLOR,
NODE_PATH, // 15
_RID,
OBJECT,
DICTIONARY,
ARRAY,
// arrays
POOL_BYTE_ARRAY, // 20
POOL_INT_ARRAY,
POOL_REAL_ARRAY,
POOL_STRING_ARRAY,
POOL_VECTOR2_ARRAY,
POOL_VECTOR3_ARRAY, // 25
POOL_COLOR_ARRAY,
VARIANT_MAX
}
interface BufferModel {
buffer: Buffer;
len: number;
offset: number;
}
export class VariantParser {
public decode_variant(model: BufferModel) {
let type = this.decode_UInt32(model);
switch (type & 0xff) {
case GDScriptTypes.BOOL:
return this.decode_UInt32(model) !== 0;
case GDScriptTypes.INT:
if (type & (1 << 16)) {
return this.decode_Int64(model);
} else {
return this.decode_Int32(model);
}
case GDScriptTypes.REAL:
if (type & (1 << 16)) {
return this.decode_Double(model);
} else {
return this.decode_Float(model);
}
case GDScriptTypes.STRING:
return this.decode_String(model);
case GDScriptTypes.VECTOR2:
return this.decode_Vector2(model);
case GDScriptTypes.RECT2:
return this.decode_Rect2(model);
case GDScriptTypes.VECTOR3:
return this.decode_Vector3(model);
case GDScriptTypes.TRANSFORM2D:
return this.decode_Transform2D(model);
case GDScriptTypes.PLANE:
return this.decode_Plane(model);
case GDScriptTypes.QUAT:
return this.decode_Quat(model);
case GDScriptTypes.AABB:
return this.decode_AABB(model);
case GDScriptTypes.BASIS:
return this.decode_Basis(model);
case GDScriptTypes.TRANSFORM:
return this.decode_Transform(model);
case GDScriptTypes.COLOR:
return this.decode_Color(model);
case GDScriptTypes.NODE_PATH:
return this.decode_NodePath(model);
case GDScriptTypes.OBJECT:
if (type & (1 << 16)) {
return this.decode_Object_id(model);
} else {
return this.decode_Object(model);
}
case GDScriptTypes.DICTIONARY:
return this.decode_Dictionary(model);
case GDScriptTypes.ARRAY:
return this.decode_Array(model);
case GDScriptTypes.POOL_BYTE_ARRAY:
return this.decode_PoolByteArray(model);
case GDScriptTypes.POOL_INT_ARRAY:
return this.decode_PoolIntArray(model);
case GDScriptTypes.POOL_REAL_ARRAY:
return this.decode_PoolFloatArray(model);
case GDScriptTypes.POOL_STRING_ARRAY:
return this.decode_PoolStringArray(model);
case GDScriptTypes.POOL_VECTOR2_ARRAY:
return this.decode_PoolVector2Array(model);
case GDScriptTypes.POOL_VECTOR3_ARRAY:
return this.decode_PoolVector3Array(model);
case GDScriptTypes.POOL_COLOR_ARRAY:
return this.decode_PoolColorArray(model);
default:
return undefined;
}
}
public encode_variant(
value:
| number
| bigint
| boolean
| string
| Map<any, any>
| Array<any>
| object
| undefined,
model?: BufferModel
) {
if(typeof value === "number" && (value > 2147483647 || value < -2147483648)) {
value = BigInt(value);
}
if (!model) {
let size = this.size_variant(value);
let buffer = Buffer.alloc(size + 4);
model = {
buffer: buffer,
offset: 0,
len: 0
};
this.encode_UInt32(size, model);
}
switch (typeof value) {
case "number":
{
let is_integer = Number.isInteger(value);
if (is_integer) {
this.encode_UInt32(GDScriptTypes.INT, model);
this.encode_UInt32(value, model);
} else {
this.encode_UInt32(GDScriptTypes.REAL | (1 << 16), model);
this.encode_Float(value, model);
}
}
break;
case "bigint":
this.encode_UInt32(GDScriptTypes.INT | (1 << 16), model);
this.encode_UInt64(value, model);
break;
case "boolean":
this.encode_UInt32(GDScriptTypes.BOOL, model);
this.encode_Bool(value, model);
break;
case "string":
this.encode_UInt32(GDScriptTypes.STRING, model);
this.encode_String(value, model);
break;
case "undefined":
break;
default:
if (Array.isArray(value)) {
this.encode_UInt32(GDScriptTypes.ARRAY, model);
this.encode_Array(value, model);
} else if (value instanceof Map) {
this.encode_UInt32(GDScriptTypes.DICTIONARY, model);
this.encode_Dictionary(value, model);
} else {
switch (value["__type__"]) {
case "Vector2":
this.encode_UInt32(GDScriptTypes.VECTOR2, model);
this.encode_Vector2(value, model);
break;
case "Rect2":
this.encode_UInt32(GDScriptTypes.RECT2, model);
this.encode_Rect2(value, model);
break;
case "Vector3":
this.encode_UInt32(GDScriptTypes.VECTOR3, model);
this.encode_Vector3(value, model);
break;
case "Transform2D":
this.encode_UInt32(GDScriptTypes.TRANSFORM2D, model);
this.encode_Transform2D(value, model);
break;
case "Plane":
this.encode_UInt32(GDScriptTypes.PLANE, model);
this.encode_Plane(value, model);
break;
case "Quat":
this.encode_UInt32(GDScriptTypes.QUAT, model);
this.encode_Quat(value, model);
break;
case "AABB":
this.encode_UInt32(GDScriptTypes.AABB, model);
this.encode_AABB(value, model);
break;
case "Basis":
this.encode_UInt32(GDScriptTypes.BASIS, model);
this.encode_Basis(value, model);
break;
case "Transform":
this.encode_UInt32(GDScriptTypes.TRANSFORM, model);
this.encode_Transform(value, model);
break;
case "Color":
this.encode_UInt32(GDScriptTypes.COLOR, model);
this.encode_Color(value, model);
break;
}
}
}
return model.buffer;
}
public get_buffer_dataset(buffer: Buffer, offset: number) {
let len = buffer.readUInt32LE(offset);
let model: BufferModel = {
buffer: buffer,
offset: offset + 4,
len: len
};
let output = [];
output.push(len + 4);
do {
let value = this.decode_variant(model);
output.push(value);
} while (model.len > 0);
return output;
}
private clean(value: number) {
return +Number.parseFloat(String(value)).toFixed(1);
}
private decode_AABB(model: BufferModel) {
let px = this.decode_Float(model);
let py = this.decode_Float(model);
let pz = this.decode_Float(model);
let sx = this.decode_Float(model);
let sy = this.decode_Float(model);
let sz = this.decode_Float(model);
return {
__type__: "AABB",
position: this.make_Vector3(px, py, pz),
size: this.make_Vector3(sx, sy, sz),
__render__: () =>
`AABB (${this.clean(px)}, ${this.clean(py)}, ${this.clean(
pz
)} - ${this.clean(sx)}, ${this.clean(sy)}, ${this.clean(sz)})`
};
}
private decode_Array(model: BufferModel) {
let output: Array<any> = [];
let count = this.decode_UInt32(model);
for (let i = 0; i < count; i++) {
let value = this.decode_variant(model);
output.push(value);
}
return output;
}
private decode_Basis(model: BufferModel) {
let x = this.decode_Vector3(model);
let y = this.decode_Vector3(model);
let z = this.decode_Vector3(model);
return this.make_Basis(
[x.x, x.y, z.z as number],
[y.x, y.y, y.z as number],
[z.x, z.y, z.z as number]
);
}
private decode_Color(model: BufferModel) {
let r = this.decode_Float(model);
let g = this.decode_Float(model);
let b = this.decode_Float(model);
let a = this.decode_Float(model);
return {
__type__: "Color",
r: r,
g: g,
b: b,
a: a,
__render__: () =>
`Color (${this.clean(r)}, ${this.clean(g)}, ${this.clean(
b
)}, ${this.clean(a)})`
};
}
private decode_Dictionary(model: BufferModel) {
let output = new Map<any, any>();
let count = this.decode_UInt32(model);
for (let i = 0; i < count; i++) {
let key = this.decode_variant(model);
let value = this.decode_variant(model);
output.set(key, value);
}
return output;
}
private decode_Double(model: BufferModel) {
let view = new DataView(model.buffer.buffer, model.offset, 8);
let d = view.getFloat64(0, true);
model.offset += 8;
model.len -= 8;
return d + 0.00000000001;
}
private decode_Float(model: BufferModel) {
let view = new DataView(model.buffer.buffer, model.offset, 4);
let f = view.getFloat32(0, true);
model.offset += 4;
model.len -= 4;
return f + 0.00000000001;
}
private decode_Int32(model: BufferModel) {
let u = model.buffer.readInt32LE(model.offset);
model.len -= 4;
model.offset += 4;
return u;
}
private decode_Int64(model: BufferModel) {
let view = new DataView(model.buffer.buffer, model.offset, 8);
let u = view.getBigInt64(0, true);
model.len -= 8;
model.offset += 8;
return Number(u);
}
private decode_NodePath(model: BufferModel) {
let name_count = this.decode_UInt32(model) & 0x7fffffff;
let subname_count = this.decode_UInt32(model);
let flags = this.decode_UInt32(model);
let is_absolute = (flags & 1) === 1;
if (flags & 2) {
//Obsolete format with property separate from subPath
subname_count++;
}
let total = name_count + subname_count;
let names: string[] = [];
let sub_names: string[] = [];
for (let i = 0; i < total; i++) {
let str = this.decode_String(model);
if (i < name_count) {
names.push(str);
} else {
sub_names.push(str);
}
}
return {
__type__: "NodePath",
path: names,
subpath: sub_names,
absolute: is_absolute,
__render__: () => `NodePath (${names.join(".")}:${sub_names.join(":")})`
};
}
private decode_Object(model: BufferModel) {
let class_name = this.decode_String(model);
let prop_count = this.decode_UInt32(model);
let props: { name: string; value: any }[] = [];
for (let i = 0; i < prop_count; i++) {
let name = this.decode_String(model);
let value = this.decode_variant(model);
props.push({ name: name, value: value });
}
return { __type__: class_name, properties: props };
}
private decode_Object_id(model: BufferModel) {
let id = this.decode_UInt64(model);
return {
__type__: "Object",
id: id,
__render__: () => `Object<${id}>`
};
}
private decode_Plane(model: BufferModel) {
let x = this.decode_Float(model);
let y = this.decode_Float(model);
let z = this.decode_Float(model);
let d = this.decode_Float(model);
return {
__type__: "Plane",
x: x,
y: y,
z: z,
d: d,
__render__: () =>
`Plane (${this.clean(x)}, ${this.clean(y)}, ${this.clean(
z
)}, ${this.clean(d)})`
};
}
private decode_PoolByteArray(model: BufferModel) {
let count = this.decode_UInt32(model);
let output: number[] = [];
for (let i = 0; i < count; i++) {
output.push(model.buffer.readUInt8(model.offset));
model.offset++;
model.len--;
}
return output;
}
private decode_PoolColorArray(model: BufferModel) {
let count = this.decode_UInt32(model);
let output: { r: number; g: number; b: number; a: number }[] = [];
for (let i = 0; i < count; i++) {
output.push(this.decode_Color(model));
}
return output;
}
private decode_PoolFloatArray(model: BufferModel) {
let count = this.decode_UInt32(model);
let output: number[] = [];
for (let i = 0; i < count; i++) {
output.push(this.decode_Float(model));
}
return output;
}
private decode_PoolIntArray(model: BufferModel) {
let count = this.decode_UInt32(model);
let output: number[] = [];
for (let i = 0; i < count; i++) {
output.push(this.decode_Int32(model));
}
return output;
}
private decode_PoolStringArray(model: BufferModel) {
let count = this.decode_UInt32(model);
let output: string[] = [];
for (let i = 0; i < count; i++) {
output.push(this.decode_String(model));
}
return output;
}
private decode_PoolVector2Array(model: BufferModel) {
let count = this.decode_UInt32(model);
let output: { x: number; y: number }[] = [];
for (let i = 0; i < count; i++) {
output.push(this.decode_Vector2(model));
}
return output;
}
private decode_PoolVector3Array(model: BufferModel) {
let count = this.decode_UInt32(model);
let output: { x: number; y: number; z: number | undefined }[] = [];
for (let i = 0; i < count; i++) {
output.push(this.decode_Vector3(model));
}
return output;
}
private decode_Quat(model: BufferModel) {
let x = this.decode_Float(model);
let y = this.decode_Float(model);
let z = this.decode_Float(model);
let w = this.decode_Float(model);
return {
__type__: "Quat",
x: x,
y: y,
z: z,
w: w,
__render__: () =>
`Quat (${this.clean(x)}, ${this.clean(y)}, ${this.clean(
z
)}, ${this.clean(w)})`
};
}
private decode_Rect2(model: BufferModel) {
let x = this.decode_Float(model);
let y = this.decode_Float(model);
let sizeX = this.decode_Float(model);
let sizeY = this.decode_Float(model);
return {
__type__: "Rect2",
position: this.make_Vector2(x, y),
size: this.make_Vector2(sizeX, sizeY),
__render__: () =>
`Rect2 (${this.clean(x)}, ${this.clean(y)} - ${this.clean(
sizeX
)}, ${this.clean(sizeY)})`
};
}
private decode_String(model: BufferModel) {
let len = this.decode_UInt32(model);
let pad = 0;
if (len % 4 !== 0) {
pad = 4 - (len % 4);
}
let str = model.buffer.toString("utf8", model.offset, model.offset + len);
len += pad;
model.offset += len;
model.len -= len;
return str;
}
private decode_Transform(model: BufferModel) {
let b = this.decode_Basis(model);
let o = this.decode_Vector3(model);
return {
__type__: "Transform",
basis: this.make_Basis(
[b.x.x, b.x.y, b.x.z as number],
[b.y.x, b.y.y, b.y.z as number],
[b.z.x, b.z.y, b.z.z as number]
),
origin: this.make_Vector3(o.x, o.y, o.z),
__render__: () =>
`Transform ((${this.clean(b.x.x)}, ${this.clean(b.x.y)}, ${this.clean(
b.x.z as number
)}), (${this.clean(b.y.x)}, ${this.clean(b.y.y)}, ${this.clean(
b.y.z as number
)}), (${this.clean(b.z.x)}, ${this.clean(b.z.y)}, ${this.clean(
b.z.z as number
)}) - (${this.clean(o.x)}, ${this.clean(o.y)}, ${this.clean(
o.z as number
)}))`
};
}
private decode_Transform2D(model: BufferModel) {
let origin = this.decode_Vector2(model);
let x = this.decode_Vector2(model);
let y = this.decode_Vector2(model);
return {
__type__: "Transform2D",
origin: this.make_Vector2(origin.x, origin.y),
x: this.make_Vector2(x.x, x.y),
y: this.make_Vector2(y.x, y.y),
__render__: () =>
`Transform2D ((${this.clean(origin.x)}, ${this.clean(
origin.y
)}) - (${this.clean(x.x)}, ${this.clean(x.y)}), (${this.clean(
y.x
)}, ${this.clean(y.x)}))`
};
}
private decode_UInt32(model: BufferModel) {
let u = model.buffer.readUInt32LE(model.offset);
model.len -= 4;
model.offset += 4;
return u;
}
private decode_UInt64(model: BufferModel) {
let view = new DataView(model.buffer.buffer, model.offset, 8);
let u = view.getBigUint64(0, true);
model.len -= 8;
model.offset += 8;
return Number(u);
}
private decode_Vector2(model: BufferModel) {
let x = this.decode_Float(model);
let y = this.decode_Float(model);
return this.make_Vector2(x, y);
}
private decode_Vector3(model: BufferModel) {
let x = this.decode_Float(model);
let y = this.decode_Float(model);
let z = this.decode_Float(model);
return this.make_Vector3(x, y, z);
}
private encode_AABB(value: object, model: BufferModel) {
this.encode_Vector3(value["position"], model);
this.encode_Vector3(value["size"], model);
}
private encode_Array(arr: any[], model: BufferModel) {
let size = arr.length;
this.encode_UInt32(size, model);
arr.forEach(e => {
this.encode_variant(e, model);
});
}
private encode_Basis(value: object, model: BufferModel) {
this.encode_Vector3(value["x"], model);
this.encode_Vector3(value["y"], model);
this.encode_Vector3(value["z"], model);
}
private encode_Bool(bool: boolean, model: BufferModel) {
this.encode_UInt32(bool ? 1 : 0, model);
}
private encode_Color(value: object, model: BufferModel) {
this.encode_Float(value["r"], model);
this.encode_Float(value["g"], model);
this.encode_Float(value["b"], model);
this.encode_Float(value["a"], model);
}
private encode_Dictionary(dict: Map<any, any>, model: BufferModel) {
let size = dict.size;
this.encode_UInt32(size, model);
let keys = Array.from(dict.keys());
keys.forEach(key => {
let value = dict.get(key);
this.encode_variant(key, model);
this.encode_variant(value, model);
});
}
private encode_Float(value: number, model: BufferModel) {
let view = new DataView(model.buffer.buffer, model.offset);
view.setFloat32(0, value, true);
model.offset += 4;
}
private encode_Double(value: number, model: BufferModel) {
let view = new DataView(model.buffer.buffer, model.offset);
view.setFloat64(0, value, true);
model.offset += 8;
}
private encode_Plane(value: object, model: BufferModel) {
this.encode_Vector3(value["normal"], model);
this.encode_Float(value["d"], model);
}
private encode_Quat(value: object, model: BufferModel) {
this.encode_Float(value["x"], model);
this.encode_Float(value["y"], model);
this.encode_Float(value["z"], model);
this.encode_Float(value["w"], model);
}
private encode_Rect2(value: object, model: BufferModel) {
this.encode_Vector2(value["position"], model);
this.encode_Vector2(value["size"], model);
}
private encode_String(str: string, model: BufferModel) {
let str_len = str.length;
this.encode_UInt32(str_len, model);
model.buffer.write(str, model.offset, str_len, "utf8");
model.offset += str_len;
str_len += 4;
while (str_len % 4) {
str_len++;
model.buffer.writeUInt8(0, model.offset);
model.offset++;
}
}
private encode_Transform(value: object, model: BufferModel) {
this.encode_Basis(value["basis"], model);
this.encode_Vector3(value["origin"], model);
}
private encode_Transform2D(value: object, model: BufferModel) {
this.encode_Vector2(value["origin"], model);
this.encode_Vector2(value["x"], model);
this.encode_Vector2(value["y"], model);
}
private encode_UInt32(int: number, model: BufferModel) {
model.buffer.writeUInt32LE(int, model.offset);
model.offset += 4;
}
private encode_UInt64(value: bigint, model: BufferModel) {
let view = new DataView(model.buffer.buffer, model.offset, 8);
view.setBigUint64(0, value, true);
model.offset += 8;
}
private encode_Vector2(value: any, model: BufferModel) {
this.encode_Float(value.x, model);
this.encode_Float(value.y, model);
}
private encode_Vector3(value: any, model: BufferModel) {
this.encode_Float(value.x, model);
this.encode_Float(value.y, model);
this.encode_Float(value.z, model);
}
private make_Basis(x: number[], y: number[], z: number[]) {
return {
__type__: "Basis",
x: this.make_Vector3(x[0], x[1], x[2]),
y: this.make_Vector3(y[0], y[1], y[2]),
z: this.make_Vector3(z[0], z[1], z[2]),
__render__: () =>
`Basis ((${this.clean(x[0])}, ${this.clean(x[1])}, ${this.clean(
x[2]
)}), (${this.clean(y[0])}, ${this.clean(y[1])}, ${this.clean(
y[2]
)}), (${this.clean(z[0])}, ${this.clean(z[1])}, ${this.clean(z[2])}))`
};
}
private make_Vector2(x: number, y: number) {
return {
__type__: `Vector2`,
x: x,
y: y,
__render__: () => `Vector2 (${this.clean(x)}, ${this.clean(y)})`
};
}
private make_Vector3(x: number, y: number, z: number) {
return {
__type__: `Vector3`,
x: x,
y: y,
z: z,
__render__: () =>
`Vector3 (${this.clean(x)}, ${this.clean(y)}, ${this.clean(z)})`
};
}
private size_Bool(): number {
return this.size_UInt32();
}
private size_Dictionary(dict: Map<any, any>): number {
let size = this.size_UInt32();
let keys = Array.from(dict.keys());
keys.forEach(key => {
let value = dict.get(key);
size += this.size_variant(key);
size += this.size_variant(value);
});
return size;
}
private size_String(str: string): number {
let size = this.size_UInt32() + str.length;
while (size % 4) {
size++;
}
return size;
}
private size_UInt32(): number {
return 4;
}
private size_UInt64(): number {
return 8;
}
private size_array(arr: any[]): number {
let size = this.size_UInt32();
arr.forEach(e => {
size += this.size_variant(e);
});
return size;
}
private size_variant(
value:
| number
| bigint
| boolean
| string
| Map<any, any>
| any[]
| object
| undefined
): number {
let size = 4;
if(typeof value === "number" && (value > 2147483647 || value < -2147483648)) {
value = BigInt(value);
}
switch (typeof value) {
case "number":
size += this.size_UInt32();
break;
case "bigint":
size += this.size_UInt64();
break;
case "boolean":
size += this.size_Bool();
break;
case "string":
size += this.size_String(value);
break;
case "undefined":
break;
default:
if (Array.isArray(value)) {
size += this.size_array(value);
break;
} else if (value instanceof Map) {
size += this.size_Dictionary(value);
break;
} else {
switch (value["__type__"]) {
case "Vector2":
size += this.size_UInt32() * 2;
break;
case "Rect2":
size += this.size_UInt32() * 4;
break;
case "Vector3":
size += this.size_UInt32() * 3;
break;
case "Transform2D":
size += this.size_UInt32() * 6;
break;
case "Plane":
size += this.size_UInt32() * 4;
break;
case "Quat":
size += this.size_UInt32() * 4;
break;
case "AABB":
size += this.size_UInt32() * 6;
break;
case "Basis":
size += this.size_UInt32() * 9;
break;
case "Transform":
size += this.size_UInt32() * 12;
break;
case "Color":
size += this.size_UInt32() * 4;
break;
}
}
}
return size;
}
}