Skip to content

Conversation

@bigmistqke
Copy link

@bigmistqke bigmistqke commented Sep 25, 2025

Continuing from issue #6316 in which I proposed for MapLibre's gl-matrix types to reflect more their actual types. The original usecase for this type enhancement was a bug in an earlier version of chrome where a Float64Array from MapLibre that was typed as a Float32Array was passed to WebGL, which caused an error as earlier WebGL versions only accepted a Float32Array.

Introduction

Earlier versions of gl-matrix had as type Tuple | Float32Array.
MapLibre uses Float64Array in several spots, but these were cast to Float32Array to appease the squiggly typelords.
The most recent version of gl-matrix fixed this issue slightly by widening its types to Tuple | IndexedCollection<number> where IndexedCollection extends Iterable<number>.

This removed the need for casting to Float32Array, but there is still room for improvement:

  • gl-matrix does not make use of any generics, this means that all of its methods return vec3 and the like, widening the types of its input.
  • .create() and a handful of other methods create a default array-type which can be set with import {glMatrix} from 'gl-matrix'; glMatrix.ARRAY_TYPE = .... They also return vec3 and the like.
  • The types use Tuple | ... because it wants to communicate that only certain indices are valid and typescript has no alternative for a tuple for TypedArrays.

Changes

I patched gl-matrix in the following ways (see https://github.com/bigmistqke/gl-matrix/tree/feat-improve-typings):

  1. Added a type-utility TypedArrayTuple to simulate tuple-like typings for TypedArrays and other Iterables, see ts playground
  2. methods use generics and return Vec3Result<T> and the like, Vec3Result is
    • Vec3Tuple if T is an array and
    • TypedArrayTuple<Kind, Vec3Tuple['length']> if it's not
  3. Have the default array type be overwritable via module augmentation:
    • by default it is Float32Array (although this is not 100% correct as it falls back to Array if Float32Array is not available in the runtime the code is executed)
    • but if you do declare module 'gl-matrix' { interface Overrides { arrayType: Float64Array; }} it is typed as Float64Array

This allowed me to remove a bunch of type-casts and highlight methods/properties that have multiple types throughout their lifetimes.

Conclusion

I am not completely sure of how I refactored the base types from

declare type vec3 = IndexedCollection | Vec3Tuple;

to

declare type vec3<T extends IndexedCollection | Vec3Tuple = IndexedCollection | Vec3Tuple> = T;

which isn't super useful as we do lose some type-information when doing position: vec3<Float64Array> = new Float64Array(3) as this just becomes Float64Array. I could either

  • narrow vec3<Float64Array> to TypedArrayTuple<Float64Array, 3>, but this would cause a bunch of type-errors in existing code and the only solution would be to cast as typescript natively does not capture its length
  • revert the base type refactor and don't wrap the property-types with vec3 and the like: position: Float64Array = new Float64Array(3)

Ideally I want to upstream this to gl-matrix, but I first wanted to get some feedback from you guys.

}

private get currentTransform(): ITransform {
private get currentTransform(): VerticalPerspectiveTransform | MercatorTransform {
Copy link
Collaborator

@HarelM HarelM Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this changed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will revert this. It's a more narrow type, but it's also a bit irrelevant to this PR.

public get inverseProjectionMatrix(): mat4<Float64Array> { return this.currentTransform.inverseProjectionMatrix; }

public get cameraPosition(): vec3 { return this.currentTransform.cameraPosition; }
public get cameraPosition(): vec3<Float64Array | Vec3Tuple> { return this.currentTransform.cameraPosition; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like an odd typescript definition...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree, this looks bug prone. All the 'ugly' types are values that switch type during their lifetime, which the previous typings had hidden away from us.

}

calculateFogMatrix(unwrappedTileID: UnwrappedTileID): mat4 {
calculateFogMatrix(unwrappedTileID: UnwrappedTileID): mat4<Float64Array> | mat4<Float32Array> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks a bad return value... Is it really the case here?

return Frustum.fromInvProjectionMatrix(this._invViewProjMatrix, this.worldSize);
}
getClippingPlane(): vec4 | null {
getClippingPlane(): null {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing the return value here was meant for the interface implementation...?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will revert this!

@HarelM
Copy link
Collaborator

HarelM commented Sep 25, 2025

Is be more interested to see the typescript charges in gl-matrix that solves all these issues.
It might be that simply adding some overloads will solve some of the issues here, wouldn't it?
I also find the word tuple confusing, coming from a c# backgrond where tuple is a pair with two items in it...

@bigmistqke
Copy link
Author

Is be more interested to see the typescript charges in gl-matrix that solves all these issues.

The patch is generated from a build of the last commit of https://github.com/bigmistqke/gl-matrix/tree/feat-improve-typings

It might be that simply adding some overloads will solve some of the issues here, wouldn't it?

I am not completely following here.

I also find the word tuple confusing, coming from a c# backgrond where tuple is a pair with two items in it...

I agree, it's a horrible name, but sadly it is what typescript officially calls it: https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types

@HarelM
Copy link
Collaborator

HarelM commented Sep 26, 2025

It might be that simply adding some overloads will solve some of the issues here, wouldn't it?

This is in regards to the possible solution of the gl-matrix types. Maybe allowing the matrix operators to receive more general types by adding an overload to the methods definition can solve some of the issues here.

@birkskyum
Copy link
Member

I also find the word tuple confusing, coming from a c# backgrond where tuple is a pair with two items in it...

I think C# also can hold n-tuples - not just 2-tuples? https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-tuples

"You can define tuples with an arbitrary large number of elements:"

@HarelM
Copy link
Collaborator

HarelM commented Sep 28, 2025

True, but I think the original implementation was only item1 and item2 fields, but I might remember it wrong as it was a long time ago.
In any case, this is a bit besides the point...

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.

3 participants