Question: Allocation overhead for native type wrapping
- Dominant language
- C
- Stars
- 765
- Forks
- 46
- PR merge metrics
- No merged PRs in 30d
Description
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:
```cpp
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?
```ts
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:
```godot
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);
```
Contributor guide
Assessment
This issue has not been assessed yet.