-
Notifications
You must be signed in to change notification settings - Fork 1.4k
RFC: Migrate API constants to enum-like API #7589
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
Comments
Seems like we already started in this direction: Lines 703 to 720 in d96c675
One question arises for conflicts like we have |
Using constants vs strings directly, has minimal performance impact, and mainly should be used as an organizational tool, just for auto-complete really and docs. So how docs look with Enums? |
Looks great! Just the order probably needs adjustment, to ensure classes - are the first. Will there be name collisions between enums and classes? |
Yeah, I agree enums should come after classes. TypeDoc let's you configure the order - this is the default. If there's a name collision, we can tweak names. The main problem I'm seeing is that when you write JSDocs and have a function that takes a
Instead you have to define an additional type:
Then you can do:
It's quite ugly. |
It's probably either that or:
|
Exactly. 😬 I think this is a clear area where TypeScript really delivers on something JavaScript struggles with. |
It is more IDE issue rather than language issue. |
True, a lot of the pain is coming from varying levels of JSDoc support in IDEs. |
So we would simply have:
(still the question around collisions, I found at least |
Yes, and a huge quantity of inconveniences and limitations in other places. Also, why properties of enum are from Capital letter instead of camelCase? |
@kungfooman Wow, cool! I think that could work. We can handle name collisions. For example, we could do @Maksims We can pick the convention for capitalization. What about:
I like this because:
|
Hmm. @kungfooman, let's say I define
If I type a function param as Can't we get |
This works:
This generates the following types:
|
that looks great! |
I'm OK with going with Pascal case. @kungfooman, seems like you agree with @mvaligursky? What do you think @Maksims? |
Yep, I'm also leaning more to the serene/non-screaming I tried to replicate your TypeDoc issue, but strangely enough it works on my side. I just added this to /**
* Mouse buttons.
*
* @readonly
* @enum {ValueOf<typeof MouseButton>}
* @category Input
*/
export const MouseButton = Object.freeze({
/** No buttons pressed. */
NONE: -1,
/** The left mouse button. */
LEFT: 0,
/** The middle mouse button. */
MIDDLE: 1,
/** The right mouse button. */
RIGHT: 2
});
/**
* @param {MouseButton} mb - The mouse button.
*/
export function test(mb) {
console.log(mb);
}
test(MouseButton.MIDDLE);
/**
* @param {MouseButton} mb - The mouse button.
*/
export class TestClass {
/**
* @param {MouseButton} mb - The mouse button.
*/
test(mb) {
console.log(mb);
}
}
export const testClass = new TestClass();
test(MouseButton.MIDDLE); Typedoc output: So I'm a bit puzzled about that. In any case, if I can reproduce, it seems just like small fix (probably as TypeDoc plugin or a little regex rewrite). Do you mind making a draft PR with what you have already, so I may get a 100% reproduction? |
@kungfooman Check this out: #7630 |
I believe enums should differentiate from Classes (PascalCase), as we have exclusively been using PascalCase for classes only. For object properties, camelCase is used.
I personally believe that 1st option is weird and not common in JS world, while 2nd option is confusing as it contradicts with property naming convention (camelCase). Will not mention other popular engine, but their naming convention for constants is mixture of different conventions and looks messy. |
Just checked Babylon. They do 2. One of the key reasons for this is because it's a TypeScript coding convention and Babylon is, of course, written in TS. Just look at the official TS docs that use this style. As you have probably noticed, the PlayCanvas codebase has been gradually migrating to a very TS-like structure over the past few years. First we did classes, then modules, then class fields. And we've been incrementally tightening up the types (using JSDoc) over an extended period. I think making our enums appear familiar to TS-devs is a positive step. And should it ever be the case the the engine does switch to TS, well, this would be one other thing that trivially carries over. I totally understand you're not personally a fan of TS, but a sizeable proportion of PlayCanvas devs do use TS, so all of this should be taken into consideration. I also just took a quick look at Three. It just does top-level constants like ours except using Pascal case. It's not a massive problem for them to have top-level constants because they have a lot fewer than we do, so we benefit a lot more from grouping them into enums objects, simplifying our top-level API surface area. I think it's important to remember that Intellisense should be a big help here. Autocompletion (plus AI copilots) should make this something you don't have to think too hard about. |
ES6 had a proposal freeze on classes when TypeScript wasn't even released (2011 vs TS release 2012). So what you look at as "TS-like structure" is plain old JavaScript? I like the practical JS coding side: Every UI developer sooner or latest has to iterate enums. Currently there is rather ugly code like this: <Select
label='Tonemap'
type='string'
options={['None', 'Linear', 'Neutral', 'Filmic', 'Hejl', 'ACES', 'ACES2'].map(v => ({ v, t: v }))}
value={props.observerData.camera.tonemapping}
setProperty={(value: number) => props.setProperty('camera.tonemapping', value)} /> setTonemapping(tonemapping: string) {
const mapping: Record<string, number> = {
None: TONEMAP_NONE,
Linear: TONEMAP_LINEAR,
Neutral: TONEMAP_NEUTRAL,
Filmic: TONEMAP_FILMIC,
Hejl: TONEMAP_HEJL,
ACES: TONEMAP_ACES,
ACES2: TONEMAP_ACES2
};
this.camera.camera.toneMapping = mapping.hasOwnProperty(tonemapping) ? mapping[tonemapping] : TONEMAP_ACES;
this.renderNextFrame();
} You can take those values straight via |
I have created a spreadsheet of proposed renaming: https://docs.google.com/spreadsheets/d/1EsIGuGBr4Btlkwqx-q3EdVThkdYTofl_IhRT0eI1BgM/edit?gid=0#gid=0 Anyone should have comment access so please log your suggestions. So it looks like we be replacing 619 constants for 94 distinct new enum types. Quite a dramatic reduction in top-level API symbols. |
Well, obviously the engine is just "plain old JavaScript". But it is now written in a form of JavaScript that maps extremely closely to the equivalent TypeScript code. For the most part, the only difference now is that JSDoc types would drop down to inline types. This was absolutely not the case a few years ago. |
Mostly looks fantastic, but I added few suggestions. |
Just to point it out: if we would follow standard JS style: We could even further decrease the top-level API symbols by dropping the "AssetType" and the supposed "ShaderPassId" (that didn't make it in the spreadsheet yet or do you want to merge it into the class?). I like the clear/pure/distinct/practical enum object approach most still.
Yes, JSDoc was definitely an unruly mess with many different ways and limitations, so |
True, we could move constants into relevant classes. We did that with events already, but I think it makes more sense for events since it documents the events fired by a specific class. For the other constants, I agree with you that a distinct enum would be better.
Agreed! |
I would not, that would make a circular references much larger problem. |
Please check out their docs well, as cherry picking is not reasonable here.
For Threejs it is a mixture also:
So lets please not cherry pick specific examples, and look at their source code as a whole.
I totally disagree here. As when you are reading someones code, you want to have good sense on what is what. It is important to pick one convention and stick to it. And currently constants use UPPER_CASE. We can help existing users as well as new users by using that convention as it is unique and specific for constants. Not mixed with Classes or anything else. |
Since we are already refactoring I would also like to throw in another idea: Currently some enums follow this naming convention: "BODYTYPE_",
"DEVICETYPE_",
"ELEMENTTYPE_",
"SKYTYPE_",
"SPRITETYPE_",
"SSAOTYPE_",
"TEXTURETYPE_",
"XRTYPE_" You can find more via: Object.keys(pc)
.filter(_ => _.includes("TYPE"))
.map(_ => _.split('_')) Due to name collisions with existing classes, we need B-b-b-bbut what if we would simply add
If we think this // Could also be pc.Enums e.g.
pc.enums.Body.Static;
// Compared to old:
pc.BODYTYPE_STATIC; For top-level API surface area minimisation, this would be the absolute best so far. But of course the question remains: what are we even maximizing for. |
We could. But then we could add Class suffix to every class as well. I think the collisions are rare, we don't need to particularly consider naming to avoid them. In some cases, we'll use Type, but it would read strange if we had it for all enums, for example To me it seems pretty clear that |
Is I would avoid adding "Type" to things, as it makes it too long. |
Uppercase @Maksims I agree that |
Uh oh!
There was an error while loading. Please reload this page.
The API currently has a huge list of hundreds of capitalized constants that kind of play the part enums (which JS doesn't technically support natively). For example:
https://api.playcanvas.com/engine/variables/ADDRESS_CLAMP_TO_EDGE.html
Let's look at this specific group:
We could rewrite that to:
So, for properties and functions that took one of those constants and was typed as
@type {string}
, we could update to@type {TextureProjection}
. This would result is a much more nicely structured API reference manual, with related constants grouped on the same page.The text was updated successfully, but these errors were encountered: