-
-
Notifications
You must be signed in to change notification settings - Fork 152
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
Custom extension support cookbook #958
Comments
Agreed, the documentation covers the API broadly but is lacking in tutorials or cookbooks at the moment. Related topics: #808, #864.
This example is written in JavaScript, not TypeScript. You're free to write extensions in either language. Like all TypeScript syntax, the IEmitter interface is purely a compile-time concept — it does not exist at runtime, and does not need to be provided if implementing the extension in JavaScript. I generally use JavaScript in examples to keep things simpler for readers, but perhaps it has the opposite effect here. Editors like VSCode offer excellent type hints with TypeScript, which I've found helpful in this project.
This is probably beyond what an extension cookbook can cover, though perhaps Concepts should cover it. The TextureInfo class represents data from the glTF specification's own "TextureInfo" and "Sampler" property types. A Material (or Material extension) holds references to Texture and TextureInfo pairs, instead of the Material -> TextureInfo -> Texture nesting of the glTF specification. |
A somewhat "higher-level" question about custom extensions (that could also be mentioned briefly in an extended documentation section about extensions in general): Iff someone implements support for a custom extension, what is the preferred way of dealing with that? Should/could such an implementation eventually become a PR into the |
Discussed briefly under "roadmap" in the contributing guide, and agree it would make sense in a cookbook too —
Some implementations for NEEDLE_, OMI_, WIRED_, and unratified KHR_ extensions exist on NPM. I've been meaning to keep a list of those, but for now I usually search npm. It's very easy to pull these extensions from NPM into a script-based pipeline with glTF Transform. Using them in the glTF Transform CLI is more experimental. |
It's probably difficult to establish a guideline that covers the criteria that are relevant for the "case-by-case" decision. Maybe also because there's not even a clear process about how extensions end up in the Khronos repo to begin with. A first, very drafty state of the implementation of |
No red flags that I see, a few quick thoughts:
The reason for glTF-Transform/packages/extensions/src/ext-mesh-gpu-instancing/instanced-mesh.ts Lines 15 to 24 in b85c534
|
Definitely subjective, but mostly comes down to (1) does this feel like something I can easily maintain, and (2) do I have a positive impression of the extension itself. I tend to view each implementation of an EXT_ extension as an "upvote" for that thing eventually becoming a KHR_ extension, so I would not necessarily implement something I view as niche, harmful, or not ready for use. |
Carefully avoiding to further talk about your impression about an extension that 'you' proposed ;-), and focussing on the I expected ~"something like that". There are technical subtleties of property/field initializations, particularly when inheritance comes into play. I mainly know that from other languages (e.g. ones where adding the From a naive, high-level, perspective, and looking at the code part that you quoted and the redundancy in
I had to wonder whether it wasn't possible to avoid one of them. Specifically, I wondered why it was necessary to store these things as properties in the first place. Declaring ( FWIW, I played with three versions of such classes:
There's that special case of the The output of the following test is
from the code //============================================================================
// Without init
abstract class ExtensionPropertyWithoutInit {
public static EXTENSION_NAME: string;
public abstract readonly extensionName: string;
constructor() {
console.log(
"Static name in base without init is " +
ExtensionPropertyWithoutInit.EXTENSION_NAME
);
}
}
class ExamplePropertyWithoutInit extends ExtensionPropertyWithoutInit {
static override EXTENSION_NAME = "EXAMPLE_PROPERTY_WITHOUT_INIT";
public declare extensionName: string;
constructor() {
super();
console.log(
"Static name in derived without init is " +
ExamplePropertyWithoutInit.EXTENSION_NAME
);
console.log(
"Instance name without init is " + this.extensionName + " (that's wrong)"
);
}
}
//============================================================================
// With init
abstract class ExtensionPropertyWithInit {
public static EXTENSION_NAME: string;
public abstract readonly extensionName: string;
constructor() {
this.init();
console.log(
"Static name in base with init is " +
ExtensionPropertyWithInit.EXTENSION_NAME
);
}
abstract init(): void;
}
class ExamplePropertyWithInit extends ExtensionPropertyWithInit {
static override EXTENSION_NAME = "EXAMPLE_PROPERTY_WITH_INIT";
public declare extensionName: string;
constructor() {
super();
console.log(
"Static name in derived with init is " +
ExamplePropertyWithInit.EXTENSION_NAME
);
console.log(
"Instance name with init is " + this.extensionName + " (that's right)"
);
}
init() {
this.extensionName = "ExamplePropertyWithInit";
}
}
//============================================================================
// With getter
abstract class ExtensionPropertyWithGetter {
public static readonly EXTENSION_NAME: string;
constructor() {
console.log(
"Static name in base with getter is " +
ExtensionPropertyWithGetter.EXTENSION_NAME
);
}
abstract getExtensionName(): string;
}
class ExamplePropertyWithGetter extends ExtensionPropertyWithGetter {
static override readonly EXTENSION_NAME = "EXAMPLE_PROPERTY_WITH_GETTER";
constructor() {
super();
console.log(
"Static name in derived with getter is " +
ExamplePropertyWithGetter.EXTENSION_NAME
);
console.log(
"Instance name with getter is " +
this.getExtensionName() +
" (that's also right)"
);
}
override getExtensionName(): string {
return "ExamplePropertyWithGetter";
}
}
function run() {
console.log("Without init:");
new ExamplePropertyWithoutInit();
console.log("With init");
new ExamplePropertyWithInit();
console.log("With getter");
new ExamplePropertyWithGetter();
}
run(); But...
|
The important thing really is that none of them affect the compiled code – only whether your compiler shows you warnings, or does not.
I have certainly spent time trying. :) I'd hoped that public class fields might work, but they have the same behavior. I'm still hoping something like that might exist someday, or that typed JavaScript type annotations might have cleaner semantics. I agree that methods would also solve the problem, but it doesn't necessarily feel better to me. Semantically I would prefer that these be properties, anything else could raise the same questions you're asking of Getters (the language feature, not a method) would also be an option: class B extends A {
get type () { return 'B' }
}
const b = new B();
console.log( b.type ); Possibly an extension could do this now, without defining init, and it would just work? Just unsure if this would satisfy TypeScript without more changes upstream. |
Well I don't think it is harmful, at least. 😅 On complexity and broader appeal I am not sure yet, probably a question of "when" not "if". |
From a quick test, ...
seems to work as well, but all that with a few uncertainties about the exact behavior. (Completely subjective: I don't like these |
Haha yes. I would consider it bad practice for a getter to do anything other than returning a value, or perhaps lazy initialization of that value. Most common use case I've found is that the getter simply returns a value, the setter sets a value and also marks a dirty flag on the instance or something like that. Anything more is a bit too magical for me. :) |
This refers to the documentation and implementation of custom extensions, as described in the "Custom Extensions" section at https://gltf-transform.donmccurdy.com/extensions . This is not directly (or not only) a technical "feature request", so here is only a short summary of the "Feature Request" template points:
The context is that I tried to implement basic support for the
EXT_mesh_features
extension. And even though this extension is (in its current form) structurally relatively simple, it was not sooo easy to implement support for that.Referring to the "Custom Extensions" section on the website, created from this MD file: The "Emitter" example there is not really complete. The
ExtensionProperty
does not have a type parameter (so there should be anIEmitter
interface definition). There are some issues with the properties and static properties of theExtensionProperty
class. (The details might be related to my cluelessness about TypeScript, technical details aboutdeclare
andoverride
and class initialization and whatnot, but ... for example, I wondered what thatinit
method does, actually...). And beyond that, certain concepts may be obvious for someone who created them, and has implemented several extensions, and knows the underlying mechanisms ofproperty-graph
for the same reasons. But for others, "understanding" certain concepts just by looking at the examples may be difficult. A specific example is that of the role of theTextureInfo
that is associated with aTexture
. Yeah, it is used in many examples, but ... what is this about, exactly?As a hopefully more constructive note: I started implementing that extension. And there are some preliminary notes that I dropped into my local files. These may end up in a similar form in the implementation, eventually. But they are written in a somewhat generic form, and thus, could count as some sort of "cookbook"/"tutorial":
For the
...Def
interfaces that define the JSON structure:For the
I...
interfaces that define the structure of the "model" classes (but that are not exposed!)For the actual "model" classes that are visible to the user:
Further comments or hints could refer to the actual
Extension
class. This could be things that are "simple" in hindight - for example, point out that it hascreate...
methods for the "model" classes, and how they are used in theread
method. But it may also refer to information about where and why to callcontext.setTextureInfo
, or details, like what exactly is happening in a line likefrom one example implementation.
On a more technical side:
It would be nice if the redundancy between the
Something
/ISomething
/SomethingDef
definitions could be reduced. Or if something like the line about thetexture
above could be hidden behind someconst texture = context.getTextureFor(textureInfoDef)
or so. But I due to a lack of background knowledge, I sometimes have to assume that there is no easier solution for certain things, so this is just a vague "Feature Wish" and not a "Feature Request" for now.The text was updated successfully, but these errors were encountered: