This change could make code written using the library more tree-shakeable (to be verified by tree-shaking tests), as well as make things like private elements accessible in shaders.
Before
The shader cannot access #buffer, because private elements can only be accessed in the context of a method.
create() {
const foo = () => {
'use gpu';
return this.#buffer.$ * 5;
};
// externals: () => ({ 'this': this })
}
The private element can be accessed easily, as it's just an object with string props of matching names.
After
create() {
const foo = () => {
'use gpu';
return this.#buffer.$ * 5;
};
// externals: { 'this': { '#buffer': { '$': () => this.#buffer.$ } } }
}
Proof of private elements accessing through externals: TypeScript Playground
Algorithm
During external gathering, if an unknown identifier is found, it's not immediately added to the external names pool. Instead of gathering external names, it's a recursive structure of the following type:
type ExternalNameData = Record<string, ExternalNameData>;
It's up to the developer to figure out an algorithm that would allow us to gather such info, but my guess would be to:
- When we've encountered an unrecognized top-level identifier, we iterate through that node's ancestors as well as the top-level node.
- If the current node is not a valid member access (see below), then we store an entry in the map with the value equal to the access path (e.g.
"foo.bar"), signifying that this value is accessed on it's own, not it's children.
- If the current node is a valid member access (see below), and there is no entry for this access with a string value, we proceed. Otherwise we abort.
- We create an entry with the value
{ } only if there's no entry, and continue with the next node in the chain.
Valid member access
We only want to expand member accesses that are statically verifiable, so the access has to be:
- non-computed (so foo.bar, not foo['bar']).
Example
Let's go through the following example and see the map step by step.
Code:
const foo = () => {
'use gpu';
const result = this.#buffer.$ * this.config.multiplier;
const config = Config(this.config);
// ...
};
Initial map:
After processing this.#buffer.$:
{
'this': {
'#buffer': {
'$': 'this.#buffer.$',
},
},
}
After processing this.config.multiplier:
{
'this': {
'#buffer': {
'$': 'this.#buffer.$',
},
'config': {
'multiplier': 'this.config.multiplier',
},
}
}
After processing Config(this.config) (notice we remove the access to 'multiplier', as we need access to the whole config object):
{
'this': {
'#buffer': {
'$': 'this.#buffer.$',
},
'config': 'this.config',
}
}
This change could make code written using the library more tree-shakeable (to be verified by tree-shaking tests), as well as make things like private elements accessible in shaders.
Before
The shader cannot access
#buffer, because private elements can only be accessed in the context of a method.The private element can be accessed easily, as it's just an object with string props of matching names.
After
Proof of private elements accessing through externals: TypeScript Playground
Algorithm
During external gathering, if an unknown identifier is found, it's not immediately added to the external names pool. Instead of gathering external names, it's a recursive structure of the following type:
It's up to the developer to figure out an algorithm that would allow us to gather such info, but my guess would be to:
"foo.bar"), signifying that this value is accessed on it's own, not it's children.{ }only if there's no entry, and continue with the next node in the chain.Valid member access
We only want to expand member accesses that are statically verifiable, so the access has to be:
Example
Let's go through the following example and see the map step by step.
Code:
Initial map:
After processing
this.#buffer.$:After processing
this.config.multiplier:After processing
Config(this.config)(notice we remove the access to 'multiplier', as we need access to the whole config object):