mirror of
https://github.com/godotengine/godot-vscode-plugin.git
synced 2026-01-05 14:10:13 +03:00
Use Properties syntax highlighting for .import files
This commit is contained in:
committed by
Francois Belair
parent
098fb976b6
commit
85ad8512bb
43
README.md
43
README.md
@@ -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
|
||||
|
||||

|
||||
|
||||
@@ -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
|
||||
|
||||
21
package.json
21
package.json
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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 {}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
7
src/debugger/commands/command.ts
Normal file
7
src/debugger/commands/command.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Mediator } from "../mediator";
|
||||
|
||||
export abstract class Command {
|
||||
public param_count: number = -1;
|
||||
|
||||
public abstract trigger(parameters: any[]): void;
|
||||
}
|
||||
162
src/debugger/commands/command_parser.ts
Normal file
162
src/debugger/commands/command_parser.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
9
src/debugger/commands/commands/command_debug_enter.ts
Normal file
9
src/debugger/commands/commands/command_debug_enter.ts
Normal 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]);
|
||||
}
|
||||
}
|
||||
8
src/debugger/commands/commands/command_debug_exit.ts
Normal file
8
src/debugger/commands/commands/command_debug_exit.ts
Normal 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");
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
25
src/debugger/commands/commands/command_message_scene_tree.ts
Normal file
25
src/debugger/commands/commands/command_message_scene_tree.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
5
src/debugger/commands/commands/command_null.ts
Normal file
5
src/debugger/commands/commands/command_null.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Command } from "../command";
|
||||
|
||||
export class CommandNull extends Command {
|
||||
public trigger(parameters: any[]) {}
|
||||
}
|
||||
9
src/debugger/commands/commands/command_output.ts
Normal file
9
src/debugger/commands/commands/command_output.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
17
src/debugger/commands/commands/command_stack_dump.ts
Normal file
17
src/debugger/commands/commands/command_stack_dump.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
31
src/debugger/commands/commands/command_stack_frame_vars.ts
Normal file
31
src/debugger/commands/commands/command_stack_frame_vars.ts
Normal 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]);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
import { GodotDebugSession } from "./godot_debug";
|
||||
import { GodotDebugSession } from "./debug_session";
|
||||
|
||||
GodotDebugSession.run(GodotDebugSession);
|
||||
|
||||
89
src/debugger/debug_runtime.ts
Normal file
89
src/debugger/debug_runtime.ts
Normal 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]);
|
||||
}
|
||||
}
|
||||
494
src/debugger/debug_session.ts
Normal file
494
src/debugger/debug_session.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
261
src/debugger/mediator.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
209
src/debugger/scene_tree/inspector_provider.ts
Normal file
209
src/debugger/scene_tree/inspector_provider.ts
Normal 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 {}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
296
src/debugger/server_controller.ts
Normal file
296
src/debugger/server_controller.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
392
src/debugger/variables/variant_decoder.ts
Normal file
392
src/debugger/variables/variant_decoder.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
359
src/debugger/variables/variant_encoder.ts
Normal file
359
src/debugger/variables/variant_encoder.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
332
src/debugger/variables/variants.ts
Normal file
332
src/debugger/variables/variants.ts
Normal 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";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user