Description
Babylon.js has many existing classes representing primitives such as vectors and colors, with member function operations such as scaleToRef (vector_b = vector_a * scalar) and addInPlace (vector_a += vector_b). I experimented a bit with applying shared structs to these primitives. Since shared structs don't support attaching any sort of code, I had to work around the existence of these operations and found none of the possible solutions particularly satisfying:
- Convert the mutator operations to static. This would of course require updating all call sites, and for heavily used classes like vectors and colors, there would be many. It also sacrifices type safety: where before we knew that
this
was, for example, a color, with a static method we have no such guarantee. This is especially unfortunate given that the origin trial does not yet have language-level support for shared structs, which means Typescript can't even do static checking at compile time. - Instead of converting the color class itself into a shared struct, keep it as a non-shared object but use a shared struct as backing storage under the hood. This works well for a given class in isolation but falls apart when objects are composed together. For example a Particle object might have several colors and vectors as members. In order to process that Particle on a worker thread, we would need some way to extract and pass over a graph of shared structs that underpin a corresponding graph of non-shared objects. That implies a degree of classes having familiarity with each others' internals that I found uncomfortable.
- Introduce a means of serializing non-shared classes into shared structs and deserializing them on a worker thread. That may or may not be faster than passing non-shared objects via postMessage or flattening to SharedArrayBuffer, but it certainly isn't going to realize the full potential of sharing across threads.
My intuition after this exercise is that in order for shared structs to apply cleanly to existing codebases, we'll need some way of attaching functions to them. I recognize that there are challenges to doing this, and for what it's worth I think it would be okay if a function attached to a shared struct doesn't have the full set of capabilities that a function attached to a non-shared object has. It's already the case that shared structs are limited - they are of fixed shape - and having limited-capability member functions would follow that precedent. I think the only piece that's needed would be for a member function on a shared struct to be treated as a static function with an implicit this
parameter.