Browse Source

Add debugger support for Typed Dictionaries (#764)

Add decoding support for Typed Dictionaries
David Kincaid 8 months ago
parent
commit
489db36e85

+ 33 - 28
src/debugger/godot4/variables/variant_decoder.ts

@@ -24,9 +24,8 @@ import {
 	ENCODE_FLAG_64,
 	ENCODE_FLAG_OBJECT_AS_ID,
 	ENCODE_FLAG_TYPED_ARRAY_MASK,
-	ENCODE_FLAG_TYPED_ARRAY_BUILTIN,
-	ENCODE_FLAG_TYPED_ARRAY_CLASS_NAME,
-	ENCODE_FLAG_TYPED_ARRAY_SCRIPT,
+	ENCODE_FLAG_TYPED_DICT_MASK,
+	ContainerTypeFlags,
 	RID,
 	Callable,
 	Signal,
@@ -145,15 +144,9 @@ export class VariantDecoder {
 			case GDScriptTypes.SIGNAL:
 				return this.decode_Signal(model);
 			case GDScriptTypes.DICTIONARY:
-				return this.decode_Dictionary(model);
+				return this.decode_Dictionary(model, type);
 			case GDScriptTypes.ARRAY:
-				if (type & ENCODE_FLAG_TYPED_ARRAY_MASK) {
-					const with_script_name = (type & ENCODE_FLAG_TYPED_ARRAY_CLASS_NAME) != 0;
-					const with_script_path = (type & ENCODE_FLAG_TYPED_ARRAY_SCRIPT) != 0;
-					return this.decode_TypedArray(model, with_script_name || with_script_path);
-				} else {
-					return this.decode_Array(model);
-				}
+				return this.decode_Array(model, type);
 			case GDScriptTypes.PACKED_BYTE_ARRAY:
 				return this.decode_PackedByteArray(model);
 			case GDScriptTypes.PACKED_INT32_ARRAY:
@@ -215,6 +208,15 @@ export class VariantDecoder {
 		return output;
 	}
 
+	private decode_ContainerTypeFlag(model: BufferModel, type: GDScriptTypes, bitOffset: number) {
+		const shiftedType = (type >> bitOffset) & 0b11;
+		if (shiftedType === ContainerTypeFlags.BUILTIN) {
+			return this.decode_UInt32(model);
+		} else {
+			return this.decode_String(model);
+		}
+	}
+
 	private decode_AABBf(model: BufferModel) {
 		return new AABB(this.decode_Vector3f(model), this.decode_Vector3f(model));
 	}
@@ -223,25 +225,16 @@ export class VariantDecoder {
 		return new AABB(this.decode_Vector3d(model), this.decode_Vector3d(model));
 	}
 
-	private decode_Array(model: BufferModel) {
-		const output: Array<any> = [];
-
-		const count = this.decode_UInt32(model);
+	private decode_Array(model: BufferModel, type: GDScriptTypes) {
+		const output = [];
 
-		for (let i = 0; i < count; i++) {
-			const value = this.decode_variant(model);
-			output.push(value);
+		let arrayType = null;
+		if (type & ENCODE_FLAG_TYPED_ARRAY_MASK) {
+			arrayType = this.decode_ContainerTypeFlag(model, type, 16);
 		}
-
-		return output;
-	}
-
-	private decode_TypedArray(model: BufferModel, with_scripts: boolean) {
-		const output: Array<any> = [];
-
 		// TODO: the type information is currently discarded
 		// it needs to be decoded and then packed into the output somehow
-		const type = with_scripts ? this.decode_String(model) : this.decode_UInt32(model);
+
 		const count = this.decode_UInt32(model);
 
 		for (let i = 0; i < count; i++) {
@@ -275,8 +268,20 @@ export class VariantDecoder {
 		return new Color(rgb.x, rgb.y, rgb.z, a);
 	}
 
-	private decode_Dictionary(model: BufferModel) {
-		const output = new Map<any, any>();
+	private decode_Dictionary(model: BufferModel, type: GDScriptTypes) {
+		const output = new Map();
+
+		let keyType = null;
+		let valueType = null;
+		if (type & ENCODE_FLAG_TYPED_DICT_MASK) {
+			keyType = this.decode_ContainerTypeFlag(model, type, 16);
+			valueType = this.decode_ContainerTypeFlag(model, type, 18);
+
+			// console.log("type:", (type >> 16) & 0b11, "keyType:", keyType);
+			// console.log("type:", type >> 18, "valueType:", valueType);
+		}
+		// TODO: the type information is currently discarded
+		// it needs to be decoded and then packed into the output somehow
 
 		const count = this.decode_UInt32(model);
 		for (let i = 0; i < count; i++) {

+ 8 - 4
src/debugger/godot4/variables/variants.ts

@@ -54,10 +54,14 @@ export enum GDScriptTypes {
 
 export const ENCODE_FLAG_64 = 1 << 16;
 export const ENCODE_FLAG_OBJECT_AS_ID = 1 << 16;
-export const ENCODE_FLAG_TYPED_ARRAY_MASK = 3 << 16;
-export const ENCODE_FLAG_TYPED_ARRAY_BUILTIN = 1 << 16;
-export const ENCODE_FLAG_TYPED_ARRAY_CLASS_NAME = 2 << 16;
-export const ENCODE_FLAG_TYPED_ARRAY_SCRIPT = 3 << 16;
+export const ENCODE_FLAG_TYPED_ARRAY_MASK = 0b11 << 16;
+export const ENCODE_FLAG_TYPED_DICT_MASK = 0b1111 << 16;
+
+export enum ContainerTypeFlags {
+	BUILTIN = 1,
+	CLASS_NAME = 2,
+	SCRIPT = 3,
+}
 
 export interface BufferModel {
 	buffer: Buffer;

+ 33 - 65
src/debugger/inspector_provider.ts

@@ -1,23 +1,13 @@
-import {
-	TreeDataProvider,
-	EventEmitter,
-	Event,
-	ProviderResult,
-	TreeItem,
-	TreeItemCollapsibleState,
-} from "vscode";
+import { TreeDataProvider, EventEmitter, Event, ProviderResult, TreeItem, TreeItemCollapsibleState } from "vscode";
 import { GodotVariable, RawObject, ObjectId } from "./debug_runtime";
 
 export class InspectorProvider implements TreeDataProvider<RemoteProperty> {
-	private _on_did_change_tree_data: EventEmitter<
+	private _on_did_change_tree_data: EventEmitter<RemoteProperty | undefined> = new 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 readonly onDidChangeTreeData: Event<RemoteProperty> | undefined = this._on_did_change_tree_data.event;
 
 	public clean_up() {
 		if (this.tree) {
@@ -26,12 +16,7 @@ export class InspectorProvider implements TreeDataProvider<RemoteProperty> {
 		}
 	}
 
-	public fill_tree(
-		element_name: string,
-		class_name: string,
-		object_id: number,
-		variable: GodotVariable
-	) {
+	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;
@@ -39,9 +24,7 @@ export class InspectorProvider implements TreeDataProvider<RemoteProperty> {
 		this._on_did_change_tree_data.fire(undefined);
 	}
 
-	public getChildren(
-		element?: RemoteProperty
-	): ProviderResult<RemoteProperty[]> {
+	public getChildren(element?: RemoteProperty): ProviderResult<RemoteProperty[]> {
 		if (!this.tree) {
 			return Promise.resolve([]);
 		}
@@ -57,15 +40,11 @@ export class InspectorProvider implements TreeDataProvider<RemoteProperty> {
 		return element;
 	}
 
-	public get_changed_value(
-		parents: RemoteProperty[],
-		property: RemoteProperty,
-		new_parsed_value: any
-	) {
+	public get_changed_value(parents: RemoteProperty[], property: RemoteProperty, new_parsed_value: any) {
 		const idx = parents.length - 1;
 		const value = parents[idx].value;
 		if (Array.isArray(value)) {
-			const idx = parseInt(property.label);
+			const idx = Number.parseInt(property.label);
 			if (idx < value.length) {
 				value[idx] = new_parsed_value;
 			}
@@ -104,13 +83,9 @@ export class InspectorProvider implements TreeDataProvider<RemoteProperty> {
 			if (Number.isInteger(value)) {
 				rendered_value = `${value}`;
 			} else {
-				rendered_value = `${parseFloat(value.toFixed(5))}`;
+				rendered_value = `${Number.parseFloat(value.toFixed(5))}`;
 			}
-		} else if (
-			typeof value === "bigint" ||
-			typeof value === "boolean" ||
-			typeof value === "string"
-		) {
+		} else if (typeof value === "bigint" || typeof value === "boolean" || typeof value === "string") {
 			rendered_value = `${value}`;
 		} else if (typeof value === "undefined") {
 			rendered_value = "null";
@@ -131,25 +106,21 @@ export class InspectorProvider implements TreeDataProvider<RemoteProperty> {
 		let child_props: RemoteProperty[] = [];
 
 		if (value) {
-			const 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) => {
-							const name =
-								typeof va["rendered_value"] === "function"
-									? va.rendered_value()
-									: `${va}`;
-							const map_value = value.get(va);
-
-							return { name: name, value: map_value };
-					})
-					: [];
+			let sub_variables = [];
+			if (typeof value.sub_values === "function" && value instanceof ObjectId === false) {
+				sub_variables = value.sub_values();
+			} else if (Array.isArray(value)) {
+				sub_variables = value.map((va, i) => {
+					return { name: `${i}`, value: va };
+				});
+			} else if (value instanceof Map) {
+				sub_variables = Array.from(value.keys()).map((va) => {
+					const name = typeof va.rendered_value === "function" ? va.rendered_value() : `${va}`;
+					const map_value = value.get(va);
+					return { name: name, value: map_value };
+				});
+			}
+
 			child_props = sub_variables?.map((va) => {
 				return this.parse_variable(va, object_id);
 			});
@@ -160,14 +131,12 @@ export class InspectorProvider implements TreeDataProvider<RemoteProperty> {
 			value,
 			object_id,
 			child_props,
-			child_props.length === 0
-				? TreeItemCollapsibleState.None
-				: TreeItemCollapsibleState.Collapsed
+			child_props.length === 0 ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed,
 		);
 		out_prop.description = rendered_value;
-		out_prop.properties.forEach((prop) => {
+		for (const prop of out_prop.properties) {
 			prop.parent = out_prop;
-		});
+		}
 		out_prop.description = rendered_value;
 
 		if (value instanceof ObjectId) {
@@ -180,11 +149,10 @@ export class InspectorProvider implements TreeDataProvider<RemoteProperty> {
 			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));
+		} else if (Array.isArray(value) || (value instanceof Map && value instanceof RawObject === false)) {
+			for (const prop of out_prop.properties) {
+				prop.parent = out_prop;
+			}
 		}
 
 		return out_prop;
@@ -200,7 +168,7 @@ export class RemoteProperty extends TreeItem {
 		public value: any,
 		public object_id: number,
 		public properties: RemoteProperty[],
-		public collapsibleState?: TreeItemCollapsibleState
+		public collapsibleState?: TreeItemCollapsibleState,
 	) {
 		super(label, collapsibleState);
 	}