浏览代码

Use Properties syntax highlighting for `.import` files

rakkarage 5 年之前
父节点
当前提交
85ad8512bb
共有 34 个文件被更改,包括 2867 次插入3496 次删除
  1. 32 11
      README.md
  2. 17 4
      package.json
  3. 0 433
      src/debugger/SceneTree/inspector_provider.ts
  4. 0 164
      src/debugger/SceneTree/tree_builders.ts
  5. 7 0
      src/debugger/commands/command.ts
  6. 162 0
      src/debugger/commands/command_parser.ts
  7. 9 0
      src/debugger/commands/commands/command_debug_enter.ts
  8. 8 0
      src/debugger/commands/commands/command_debug_exit.ts
  9. 18 0
      src/debugger/commands/commands/command_message_inspect_object.ts
  10. 25 0
      src/debugger/commands/commands/command_message_scene_tree.ts
  11. 5 0
      src/debugger/commands/commands/command_null.ts
  12. 9 0
      src/debugger/commands/commands/command_output.ts
  13. 17 0
      src/debugger/commands/commands/command_stack_dump.ts
  14. 31 0
      src/debugger/commands/commands/command_stack_frame_vars.ts
  15. 0 60
      src/debugger/communication/command.ts
  16. 0 58
      src/debugger/communication/command_builder.ts
  17. 0 147
      src/debugger/communication/godot_commands.ts
  18. 0 454
      src/debugger/communication/server_controller.ts
  19. 1 1
      src/debugger/debug_adapter.ts
  20. 89 0
      src/debugger/debug_runtime.ts
  21. 494 0
      src/debugger/debug_session.ts
  22. 92 85
      src/debugger/debugger_context.ts
  23. 0 588
      src/debugger/godot_debug.ts
  24. 0 177
      src/debugger/godot_debug_runtime.ts
  25. 261 0
      src/debugger/mediator.ts
  26. 209 0
      src/debugger/scene_tree/inspector_provider.ts
  27. 2 2
      src/debugger/scene_tree/scene_tree_provider.ts
  28. 296 0
      src/debugger/server_controller.ts
  29. 0 91
      src/debugger/stringify.ts
  30. 0 316
      src/debugger/variable_scope.ts
  31. 392 0
      src/debugger/variables/variant_decoder.ts
  32. 359 0
      src/debugger/variables/variant_encoder.ts
  33. 332 0
      src/debugger/variables/variants.ts
  34. 0 905
      src/debugger/variant_parser.ts

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

+ 17 - 4
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
 						}
 					}
 				]

+ 0 - 433
src/debugger/SceneTree/inspector_provider.ts

@@ -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 {}

+ 0 - 164
src/debugger/SceneTree/tree_builders.ts

@@ -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 - 0
src/debugger/commands/command.ts

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

+ 162 - 0
src/debugger/commands/command_parser.ts

@@ -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 - 0
src/debugger/commands/commands/command_debug_enter.ts

@@ -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 - 0
src/debugger/commands/commands/command_debug_exit.ts

@@ -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");
+	}
+}

+ 18 - 0
src/debugger/commands/commands/command_message_inspect_object.ts

@@ -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 - 0
src/debugger/commands/commands/command_message_scene_tree.ts

@@ -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 - 0
src/debugger/commands/commands/command_null.ts

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

+ 9 - 0
src/debugger/commands/commands/command_output.ts

@@ -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 - 0
src/debugger/commands/commands/command_stack_dump.ts

@@ -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 - 0
src/debugger/commands/commands/command_stack_frame_vars.ts

@@ -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]);
+	}
+}

+ 0 - 60
src/debugger/communication/command.ts

@@ -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;
-	}
-}

+ 0 - 58
src/debugger/communication/command_builder.ts

@@ -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);
-	}
-}

+ 0 - 147
src/debugger/communication/godot_commands.ts

@@ -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
-			);
-		}
-	}
-}

+ 0 - 454
src/debugger/communication/server_controller.ts

@@ -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 - 1
src/debugger/debug_adapter.ts

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

+ 89 - 0
src/debugger/debug_runtime.ts

@@ -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 - 0
src/debugger/debug_session.ts

@@ -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,
+		};
+	}
+}

+ 92 - 85
src/debugger/debugger_context.ts

@@ -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,61 +93,75 @@ 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 => {
-				let new_parsed_value: any;
-				switch (type) {
-					case "string":
-						new_parsed_value = value;
-						break;
-					case "number":
-						if (is_float) {
-							new_parsed_value = parseFloat(value);
-							if (new_parsed_value === NaN) {
-								return;
+			window
+				.showInputBox({ value: `${property.description}` })
+				.then((value) => {
+					let new_parsed_value: any;
+					switch (type) {
+						case "string":
+							new_parsed_value = value;
+							break;
+						case "number":
+							if (is_float) {
+								new_parsed_value = parseFloat(value);
+								if (new_parsed_value === NaN) {
+									return;
+								}
+							} else {
+								new_parsed_value = parseInt(value);
+								if (new_parsed_value === NaN) {
+									return;
+								}
 							}
-						} else {
-							new_parsed_value = parseInt(value);
-							if (new_parsed_value === NaN) {
+							break;
+						case "boolean":
+							if (
+								value.toLowerCase() === "true" ||
+								value.toLowerCase() === "false"
+							) {
+								new_parsed_value = value.toLowerCase() === "true";
+							} else if (value === "0" || value === "1") {
+								new_parsed_value = value === "1";
+							} else {
 								return;
 							}
+					}
+					if (property.changes_parent) {
+						let parents = [property.parent];
+						let idx = 0;
+						while (parents[idx].changes_parent) {
+							parents.push(parents[idx++].parent);
 						}
-						break;
-					case "boolean":
-						new_parsed_value = value.toLowerCase() === "true";
-						break;
-				}
-				if (property.changes_parent) {
-					let parents = [property.parent];
-					let idx = 0;
-					while (parents[idx].changes_parent) {
-						parents.push(parents[idx++].parent);
+						let changed_value = inspector_provider.get_changed_value(
+							parents,
+							property,
+							new_parsed_value
+						);
+						Mediator.notify("changed_value", [
+							property.object_id,
+							parents[idx].label,
+							changed_value,
+						]);
+					} else {
+						Mediator.notify("changed_value", [
+							property.object_id,
+							property.label,
+							new_parsed_value,
+						]);
 					}
-					let changed_value = inspector_provider.get_changed_value(
-						parents,
-						property,
-						new_parsed_value
-					);
-					factory.session.set_object_property(
-						property.object_id,
-						parents[idx].label,
-						changed_value
-					);
-				} else {
-					factory.session.set_object_property(
-						property.object_id,
-						property.label,
-						new_parsed_value
-					);
-				}
-				factory.session.reinspect_node((name, class_name, properties) => {
-					inspector_provider.fill_tree(
-						name,
-						class_name,
-						factory.session.get_last_id(),
-						properties
-					);
+
+					Mediator.notify("inspect_object", [
+						inspector_provider.get_top_id(),
+						(class_name, properties) => {
+							inspector_provider.fill_tree(
+								inspector_provider.get_top_name(),
+								class_name,
+								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);
 	}
 

+ 0 - 588
src/debugger/godot_debug.ts

@@ -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;
-	}
-}

+ 0 - 177
src/debugger/godot_debug_runtime.ts

@@ -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 - 0
src/debugger/mediator.ts

@@ -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 - 0
src/debugger/scene_tree/inspector_provider.ts

@@ -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 {}

+ 2 - 2
src/debugger/SceneTree/scene_tree_provider.ts → src/debugger/scene_tree/scene_tree_provider.ts

@@ -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 - 0
src/debugger/server_controller.ts

@@ -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;
+	}
+}

+ 0 - 91
src/debugger/stringify.ts

@@ -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;
-}

+ 0 - 316
src/debugger/variable_scope.ts

@@ -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 - 0
src/debugger/variables/variant_decoder.ts

@@ -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 - 0
src/debugger/variables/variant_encoder.ts

@@ -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 - 0
src/debugger/variables/variants.ts

@@ -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";
+	}
+}

+ 0 - 905
src/debugger/variant_parser.ts

@@ -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;
-	}
-}