Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Tensor types, for accuracy #15053

Merged
merged 5 commits into from May 7, 2024
Merged

Conversation

dr-vortex
Copy link
Contributor

@dr-vortex dr-vortex commented May 1, 2024

This PR changes types on the Tensor API to be more accurate. The changes are described in more detail below.

Note: Class is used in place of some class in the Tensor API

1. this vs Class

In the example, T extends this and DeepImmutable<this> are inaccurate. The method does not need the arguments to be this, only that they are Class. When I was originally creating the Tensor API, I used this out of convenience. Doing so has caused several problems.

Using this instead of Class makes the type more strict. This is not needed with the Tensor API classes because they are compatible wth Class, not just this. The PR changes this to Class where this is not needed. Note that the return types of functions which return the value this are the type this.

A brief example:

class Primate {
	/**
	 * Say hi to another primate;
	*/
	sayHi(other: this): void;
}

class Human extends Primate { /* ... */ }

const sue = new Primate();
const ian = new Human();

ian.sayHi(sue); // error, `sue` is not a `Human`, but we want humans to be able to say hi to other primates

2. Introduction of I, the "interface" type, for compatibility.

Recently, I've been working with shaders in GLSL. I've been amazed by the developer experience with operator overloading and thought something similar would be a huge boost for the Tensor API classes.

I noticed a significant part of the vector math functions are of the form

public operationToRef<T extends Class>(other: DeepImmutable<Class>, result: T): T {
	result.x = this.x [[operator]] other.x;
	result.y = this.y [[operator]] other.y;
	// ...
	return result;
}

My line of thinking:

  1. All of the classes have a simple interface type (i.e. IClassLike)
  2. This interface type has coordinate members
  3. The method only modifies the coordinate members
  4. It is trivial to use the interface instead of the class for types
  5. Complex types that implement the interface can be used transparently.

For example, with Vector4:

public addInPlace(other: DeepImmutable<IVector4Like>): this {
	this.x += other.x;
	this.y += other.y;
	this.z += other.z;
	this.w += other.w;
	return this;
}

//similar for add, addToRef, subtract, etc.

Which can then be used like vec2.addInPlace(vec4). Note that this will not work for vec3.*(vec4) since the Vector3 functions use the internal _x/_y/_z, which are not used by Vector2 and Vector4.

In addition, when adding a vector of lower dimension, like vec4.add(vec2), some coordinates may be set to NaN. This is reflected in the types. For example, vec4.* will only take an IVector4Like, so IVector2Like will give an error. While adding || 0 to the end of many operations would fix this simple issue, I have not done so due to performance concerns.

3. Overengineered: (value.constructor as Constructor<typeof Class, T>)

(value.constructor as Constructor<typeof Class, T>) is an incredibly verbose statement, which unnecessarily complicates things. Using Class instead makes the code much more readable, and provides a minor performance boost, since there is a single variable access (i.e. Class) instead of a variable access followed by a property lookup, which also involves the value prototype.

@bjsplat
Copy link
Collaborator

bjsplat commented May 1, 2024

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

@bjsplat
Copy link
Collaborator

bjsplat commented May 1, 2024

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

Updated Vector3, Quaternion, and Matrix static types
@bjsplat
Copy link
Collaborator

bjsplat commented May 1, 2024

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

@bjsplat
Copy link
Collaborator

bjsplat commented May 1, 2024

@bjsplat
Copy link
Collaborator

bjsplat commented May 1, 2024

@bjsplat
Copy link
Collaborator

bjsplat commented May 1, 2024

Visualization tests for WebGPU (Experimental)
Important - these might fail sporadically. This is an optional test.

https://babylonsnapshots.z22.web.core.windows.net/refs/pull/15053/merge/testResults/webgpuplaywright/index.html

@sebavan sebavan requested a review from RaananW May 1, 2024 23:29
@RaananW
Copy link
Member

RaananW commented May 2, 2024

Wouldn't moving to Class instead of this prevent users from extending the class correctly?
The reasoning behind using this.constructor (for example) was not to have fixed Class references in many functions. This fails:

https://www.typescriptlang.org/play/?preserveConstEnums=true#code/MYGwhgzhAEBqCmwAuB7ATgZmgbwFDWjABMiAKAfRSQAt40AuORVTASmkYWXSzwILTwkAVzQA7aGPgB3Jt0ylWAbnzQAvrg25QkGFyzwAHknhiie5jxyqp0gLJDqKMuz78A9O8koAtCgAOqhpawChiEEjQwNAAvJIychiKKqHhkbYA8lKxUQB0xGS2iYrKuJlSubYONM7JQA

Otherwise - I find the PR fine. Might be a bit overcomplicating things with the extra generic, but the API is already quite complex, adding a bit of structure won't be a bad thing.

@dr-vortex
Copy link
Contributor Author

dr-vortex commented May 2, 2024

Wouldn't moving to Class instead of this prevent users from extending the class correctly?

Potentially. If users want to extend Vector3, it is easy to wrap the methods. For example:

public add(other: Vec3): Vec3 {
	return super.addToRef(other, new Vec3());
}

The reasoning behind using this.constructor (for example) was not to have fixed Class references in many functions.

Yes, though I feel it unnecessarily complicates the code. I think realistically most users would not want to extend one of the math classes.

@RaananW RaananW merged commit 03092cc into BabylonJS:master May 7, 2024
11 checks passed
@dr-vortex dr-vortex deleted the tensor branch May 7, 2024 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants