Skip to content

Question: Allocation overhead for native type wrapping #63

@knervous

Description

@knervous

Hello, amazing library, I am using the web export and this has increased development speed tremendously, thank you for your work.

I am curious on thoughts/best practices around native type marshalling - I've noticed in debug output that in cases where native types are created on the stack and returned they will trigger the gc.alloc debug command, example is in Vector3 methods:

Vector3 Vector3::cross(const Vector3 &p_with) const {
	Vector3 ret(
			(y * p_with.z) - (z * p_with.y),
			(z * p_with.x) - (x * p_with.z),
			(x * p_with.y) - (y * p_with.x));

	return ret;
}

Will result in an allocation. I have been implementing TS extensions to allow for mutating the same object with the same logic for many of these utility functions like add, scale, etc. and keeping static instances for utilities. Also for getters on a node3d like node.global_position.x the global_position will allocate a Vector3 which seems like an expensive operation. Same with _input events for mouse/keyboard - they will be wrapped and allocated.

Do you think this is something that doesn't require optimization? I am trying to be as strict as possible in WASM environment, creating a wrapper for marshalling simple types without copies. Was curious your thoughts on this approach?

export class Extensions {
    static cachedPosition: Vector3 = new Vector3(0, 0, 0);
    static cachedRotation: Vector3 = new Vector3(0, 0, 0);
    static cachedPositionNode: Node | null;
    static root: Node | null;

    static Dispose() {
        Extensions.cachedPositionNode = null;
    }

    static SetRoot(node: Node) {
        Extensions.root = node;
    }

    private static get positionNode() {
        if (!Extensions.cachedPositionNode) {
            Extensions.cachedPositionNode = Extensions.root?.get_node('/root/Zone/GDBridge') as Node;
        }
        return Extensions.cachedPositionNode;
    }

    static Eval(node: Node3D, code: string) {
        return Extensions.positionNode?.call('eval_plain', code, node);
    }

    static GetPosition(node: Node3D): Vector3 {
        this.cachedPosition.x = Extensions.positionNode.call('eval_plain', 'node.global_position.x', node);
        this.cachedPosition.y = Extensions.positionNode.call('eval_plain', 'node.global_position.y', node);
        this.cachedPosition.z = Extensions.positionNode.call('eval_plain', 'node.global_position.z', node);

        return this.cachedPosition;
    }

    static GetBasisZ(node: Node3D): [number, number, number] {  
        const x = Extensions.positionNode.call('eval_plain', 'node.transform.basis.z.x', node);
        const y = Extensions.positionNode.call('eval_plain', 'node.transform.basis.z.y', node);
        const z = Extensions.positionNode.call('eval_plain', 'node.transform.basis.z.z', node);
        return [x,y,z]
    }
}

and GDBridge:

extends Node

var _expr_cache := {}

func eval_plain(expr_str: String, node: Node3D) -> Variant:
	var expression: Expression
	if _expr_cache.has(expr_str):
		expression = _expr_cache[expr_str]
	else:
		expression = Expression.new()
		var err = expression.parse(expr_str, ["node"])
		if err != OK:
			push_error("Parse error in expression: %s" % expr_str)
			return null
		_expr_cache[expr_str] = expression

	var result = expression.execute([node])
	if expression.has_execute_failed():
		push_error("Execution error in expression: %s" % expr_str)
		return null
	return result

func _input(event: InputEvent) -> void:
	if get_viewport().gui_get_focus_owner():
		return
	if event is InputEventMouseButton:
		var node = get_node("/root/Zone")
		node.call("input", event.button_index);
		if event.button_index == MOUSE_BUTTON_RIGHT:
			Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED if event.pressed else Input.MOUSE_MODE_VISIBLE)
	elif event is InputEventPanGesture:
		var node = get_node("/root/Zone")
		node.call("input_pan", event.delta_y);
	elif event is InputEventMouseMotion:
		var node = get_node("/root/Zone")
		node.call("input_mouse_motion", event.relative.x, event.relative.y);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions