cesium/Documentation/Contributors/CodingGuide/README.md

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1050 lines
41 KiB
Markdown
Raw Permalink Normal View History

2015-12-07 03:22:39 +08:00
# Coding Guide
2018-02-28 05:53:45 +08:00
CesiumJS is one of the largest JavaScript codebases in the world. Since its start, we have maintained a high standard for code quality, which has made the codebase easier to work with for both new and experienced contributors. We hope you find the codebase to be clean and consistent.
2015-12-07 03:22:39 +08:00
2015-12-07 21:45:05 +08:00
In addition to describing typical coding conventions, this guide also covers best practices for design, maintainability, and performance. It is the cumulative advice of many developers after years of production development, research, and experimentation.
2015-12-07 03:22:39 +08:00
2018-02-28 05:53:45 +08:00
This guide applies to CesiumJS and all parts of the Cesium ecosystem written in JavaScript.
2015-12-07 03:22:39 +08:00
:art: The color palette icon indicates a design tip.
2015-12-09 02:49:32 +08:00
:house: The house icon indicates a maintainability tip. The whole guide is, of course, about writing maintainable code.
2015-12-07 03:22:39 +08:00
:speedboat: The speedboat indicates a performance tip.
2015-12-07 21:45:05 +08:00
To some extent, this guide can be summarized as _make new code similar to existing code_.
2015-12-07 03:22:39 +08:00
2022-02-03 04:39:53 +08:00
- [Coding Guide](#coding-guide)
- [Naming](#naming)
- [Formatting](#formatting)
2024-10-05 04:20:02 +08:00
- [Spelling](#spelling)
2022-02-03 04:39:53 +08:00
- [Linting](#linting)
- [Units](#units)
- [Basic Code Construction](#basic-code-construction)
- [Functions](#functions)
- [`options` Parameters](#options-parameters)
- [Default Parameter Values](#default-parameter-values)
- [Throwing Exceptions](#throwing-exceptions)
- [`result` Parameters and Scratch Variables](#result-parameters-and-scratch-variables)
- [Classes](#classes)
- [Constructor Functions](#constructor-functions)
- [`from` Constructors](#from-constructors)
- [`to` Functions](#to-functions)
- [Use Prototype Functions for Fundamental Classes Sparingly](#use-prototype-functions-for-fundamental-classes-sparingly)
- [Static Constants](#static-constants)
- [Private Functions](#private-functions)
- [Property Getter/Setters](#property-gettersetters)
- [Shadowed Property](#shadowed-property)
- [Put the Constructor Function at the Top of the File](#put-the-constructor-function-at-the-top-of-the-file)
- [Design](#design)
- [Deprecation and Breaking Changes](#deprecation-and-breaking-changes)
- [Third-Party Libraries](#third-party-libraries)
- [Widgets](#widgets)
- [Knockout subscriptions](#knockout-subscriptions)
- [GLSL](#glsl)
- [GLSL Naming](#glsl-naming)
- [GLSL Formatting](#glsl-formatting)
- [GLSL Performance](#glsl-performance)
2022-02-03 04:39:53 +08:00
- [Resources](#resources)
2015-12-07 03:22:39 +08:00
## Naming
2015-12-07 03:59:44 +08:00
- Directory names are `PascalCase`, e.g., `Source/Scene`.
- Constructor functions are `PascalCase`, e.g., `Cartesian3`.
2025-03-04 03:03:53 +08:00
- Functions are `camelCase`, e.g., `binarySearch()`, `Cartesian3.equalsEpsilon()`.
- Files end in `.js` and have the same name as the JavaScript identifier, e.g., `Cartesian3.js` and `binarySearch.js`.
2015-12-07 21:45:05 +08:00
- Variables, including class properties, are `camelCase`, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-07 03:59:44 +08:00
```javascript
2015-12-07 21:45:05 +08:00
this.minimumPixelSize = 1.0; // Class property
2015-12-07 03:59:44 +08:00
2022-01-22 00:22:49 +08:00
const bufferViews = gltf.bufferViews; // Local variable
2015-12-07 03:59:44 +08:00
```
2020-04-17 08:31:36 +08:00
2015-12-07 21:45:05 +08:00
- Private (by convention) members start with an underscore, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-07 03:59:44 +08:00
```javascript
this._canvas = canvas;
```
2020-04-17 08:31:36 +08:00
2015-12-07 04:36:07 +08:00
- Constants are in uppercase with underscores, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-07 04:36:07 +08:00
```javascript
Cartesian3.UNIT_X = Object.freeze(new Cartesian3(1.0, 0.0, 0.0));
2015-12-07 04:36:07 +08:00
```
2020-04-17 08:31:36 +08:00
2016-02-09 02:23:57 +08:00
- Avoid abbreviations in public identifiers unless the full name is prohibitively cumbersome and has a widely accepted abbreviation, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-07 04:36:07 +08:00
```javascript
2015-12-16 06:56:34 +08:00
Cartesian3.maximumComponent(); // Not Cartesian3.maxComponent()
2015-12-07 04:36:07 +08:00
2015-12-16 06:56:34 +08:00
Ellipsoid.WGS84; // Not Ellipsoid.WORLD_GEODETIC_SYSTEM_1984
2015-12-07 04:36:07 +08:00
```
2020-04-17 08:31:36 +08:00
- If you do use abbreviations, use the recommended casing and do not capitalize all letters in the abbreviation. e.g.
```javascript
new UrlTemplateImageryProvider(); // Not URLTemplateImageryProvider
resource.url; // Not resource.URL
```
2015-12-07 21:45:05 +08:00
- Prefer short and descriptive names for local variables, e.g., if a function has only one length variable,
2020-04-17 08:31:36 +08:00
2015-12-07 03:59:44 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const primitivesLength = primitives.length;
2015-12-07 03:59:44 +08:00
```
2020-04-17 08:31:36 +08:00
2015-12-07 21:45:05 +08:00
is better written as
2020-04-17 08:31:36 +08:00
2015-12-07 03:59:44 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const length = primitives.length;
2015-12-07 03:59:44 +08:00
```
2020-04-17 08:31:36 +08:00
- When accessing an outer-scope's `this` in a closure, name the variable `that`, e.g.,
2020-04-17 08:31:36 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const that = this;
this._showTouch = createCommand(function () {
that._touch = true;
});
```
2015-12-07 03:59:44 +08:00
2015-12-09 02:49:32 +08:00
A few more naming conventions are introduced below along with their design pattern, e.g., [`options` parameters](#options-parameters), [`result` parameters and scratch variables](#result-parameters-and-scratch-variables), and [`from` constructors](#from-constructors).
2015-12-07 03:22:39 +08:00
2015-12-07 04:36:07 +08:00
## Formatting
- We use [prettier](https://prettier.io/) to automatically re-format all JS code at commit time, so all of the work is done for you. Code is automatically reformatted when you commit.
- For HTML code, keep the existing style. Use double quotes.
- Text files, end with a newline to minimize the noise in diffs.
2016-10-20 23:23:57 +08:00
2024-09-25 04:55:01 +08:00
## Spelling
- We have a basic setup for `cspell` to spellcheck our files. This is currently not enforced but recommended to use and check while programming. This is especially true for JSDoc comments that well end up in our documentation or Readme files
- Run `npm run cspell` to check all files
- Run `npx cspell -c .vscode/cspell.json [file path]` to check a specific file
- If you are using VSCode you can use the [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) extension to highlight misspelled words and add them to our wordlist if they are valid.
2024-10-05 04:20:02 +08:00
- Using cspell is optional while we build up the wordlist but may eventually be required as part of our git hooks and CI. See [this issue](https://github.com/CesiumGS/cesium/issues/11954) for an active status on that.
2024-09-25 04:55:01 +08:00
2017-06-27 22:16:39 +08:00
## Linting
For syntax and style guidelines, we use the ESLint recommended settings as a base and extend it with additional rules (see the [list of all rules](http://eslint.org/docs/rules/)) via a shared config Node module, [eslint-config-cesium](https://www.npmjs.com/package/eslint-config-cesium). This package is maintained as a part of the Cesium repository and is also used throughout the Cesium ecosystem. For an up to date list of which rules are enabled, look in [index.js](https://github.com/CesiumGS/eslint-config-cesium/blob/main/index.js), [browser.js](https://github.com/CesiumGS/eslint-config-cesium/blob/main/browser.js), and [node.js](https://github.com/CesiumGS/eslint-config-cesium/blob/main/node.js). Below are listed some specific rules to keep in mind
2017-06-29 23:00:22 +08:00
**General rules:**
2020-04-17 08:31:36 +08:00
2017-06-29 22:57:51 +08:00
- [block-scoped-var](http://eslint.org/docs/rules/block-scoped-var)
- [no-alert](http://eslint.org/docs/rules/no-alert)
- [no-floating-decimal](http://eslint.org/docs/rules/no-floating-decimal)
- [no-implicit-globals](http://eslint.org/docs/rules/no-implicit-globals)
- [no-loop-func](http://eslint.org/docs/rules/no-loop-func)
- [no-use-before-define](http://eslint.org/docs/rules/no-use-before-define) to prevent using variables and functions before they are defined.
- [no-else-return](http://eslint.org/docs/rules/no-else-return)
- [no-undef-init](http://eslint.org/docs/rules/no-undef-init)
- [no-sequences](http://eslint.org/docs/rules/no-sequences)
- [no-unused-expressions](http://eslint.org/docs/rules/no-unused-expressions)
- [no-trailing-spaces](http://eslint.org/docs/rules/no-trailing-spaces)
- [no-lonely-if](http://eslint.org/docs/rules/no-lonely-if)
- [quotes](http://eslint.org/docs/rules/quotes) to enforce using single-quotes
2017-06-29 23:00:22 +08:00
**Node-specific rules:**
2020-04-17 08:31:36 +08:00
2017-06-29 22:57:51 +08:00
- [global-require](http://eslint.org/docs/rules/global-require)
2023-12-06 02:20:58 +08:00
- [n/no-new-require](https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-new-require.md)
2017-06-29 22:57:51 +08:00
2017-06-29 23:00:22 +08:00
**[Disabling Rules with Inline Comments](http://eslint.org/docs/user-guide/configuring#disabling-rules-with-inline-comments)**
2020-04-17 08:31:36 +08:00
- When disabling linting for one line, use `//eslint-disable-next-line`:
2020-04-17 08:31:36 +08:00
2017-06-27 22:16:39 +08:00
```js
function exit(warningMessage) {
//eslint-disable-next-line no-alert
window.alert("Cannot exit: " + warningMessage);
2017-06-27 22:16:39 +08:00
}
```
- When disabling linting for blocks of code, place `eslint-disable` comments on new lines and as close to the associated code as possible:
2020-04-17 08:31:36 +08:00
```js
/*eslint-disable no-empty*/
try {
lineNumber = parseInt(stack.substring(lineStart + 1, lineEnd1), 10);
} catch (ex) {}
/*eslint-enable no-empty*/
```
2015-12-07 04:36:07 +08:00
## Units
2015-12-15 22:15:23 +08:00
- Cesium uses SI units:
2023-12-06 02:20:58 +08:00
- meters for distances
- radians for angles
- seconds for time durations
2015-12-15 22:15:23 +08:00
- If a function has a parameter with a non-standard unit, such as degrees, put the unit in the function name, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-15 22:15:23 +08:00
```javascript
2023-12-06 02:20:58 +08:00
Cartesian3.fromDegrees(); // Not Cartesin3.fromAngle()
2015-12-15 22:15:23 +08:00
```
2015-12-07 04:36:07 +08:00
2015-12-09 03:41:02 +08:00
## Basic Code Construction
2015-12-15 22:15:23 +08:00
- Cesium uses JavaScript's [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) so each module (file) contains
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
```javascript
2015-12-15 22:15:23 +08:00
"use strict";
```
2020-04-17 08:31:36 +08:00
2015-12-15 22:15:23 +08:00
- :speedboat: To avoid type coercion (implicit type conversion), test for equality with `===` and `!==`, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-15 22:15:23 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const i = 1;
2015-12-09 03:41:02 +08:00
2015-12-15 22:15:23 +08:00
if (i === 1) {
2015-12-09 03:41:02 +08:00
// ...
}
2015-12-15 22:15:23 +08:00
if (i !== 1) {
2015-12-09 03:41:02 +08:00
// ...
}
```
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
- To aid the human reader, append `.0` to whole numbers intended to be floating-point values, e.g., unless `f` is an integer,
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const f = 1;
2015-12-09 03:41:02 +08:00
```
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
is better written as
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const f = 1.0;
2015-12-09 03:41:02 +08:00
```
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
- Declare variables where they are first used. For example,
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
```javascript
2022-01-22 00:22:49 +08:00
let i;
let m;
const models = [
2015-12-09 03:41:02 +08:00
/* ... */
];
2022-01-22 00:22:49 +08:00
const length = models.length;
2015-12-09 03:41:02 +08:00
for (i = 0; i < length; ++i) {
m = models[i];
// Use m
}
```
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
is better written as
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const models = [
2015-12-09 03:41:02 +08:00
/* ... */
];
2022-01-22 00:22:49 +08:00
const length = models.length;
for (let i = 0; i < length; ++i) {
const m = models[i];
2015-12-09 03:41:02 +08:00
// Use m
}
```
2020-04-17 08:31:36 +08:00
2022-01-22 00:22:49 +08:00
- `let` and `const` variables have block-level scope. Do not rely on variable hoisting, i.e., using a variable before it is declared, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
```javascript
console.log(i); // i is undefined here. Never use a variable before it is declared.
2022-01-22 00:22:49 +08:00
let i = 0.0;
2015-12-09 03:41:02 +08:00
```
2020-04-17 08:31:36 +08:00
2023-12-06 02:20:58 +08:00
- A `const` variable is preferred when a value is not updated. This ensures immutability.
2022-01-22 00:22:49 +08:00
2015-12-15 21:30:11 +08:00
- :speedboat: Avoid redundant nested property access. This
2020-04-17 08:31:36 +08:00
2015-12-15 21:30:11 +08:00
```javascript
2019-09-24 07:10:39 +08:00
scene.environmentState.isSkyAtmosphereVisible = true;
scene.environmentState.isSunVisible = true;
scene.environmentState.isMoonVisible = false;
2015-12-15 21:30:11 +08:00
```
2020-04-17 08:31:36 +08:00
2015-12-15 21:30:11 +08:00
is better written as
2020-04-17 08:31:36 +08:00
2015-12-15 21:30:11 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const environmentState = scene.environmentState;
2019-09-24 07:10:39 +08:00
environmentState.isSkyAtmosphereVisible = true;
environmentState.isSunVisible = true;
environmentState.isMoonVisible = false;
2015-12-15 21:30:11 +08:00
```
2020-04-17 08:31:36 +08:00
2015-12-22 22:06:19 +08:00
- Do not create a local variable that is used only once unless it significantly improves readability, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-22 22:06:19 +08:00
```javascript
function radiiEquals(left, right) {
2022-01-22 00:22:49 +08:00
const leftRadius = left.radius;
const rightRadius = right.radius;
2015-12-22 22:06:19 +08:00
return leftRadius === rightRadius;
}
```
2020-04-17 08:31:36 +08:00
2015-12-22 22:06:19 +08:00
is better written as
2020-04-17 08:31:36 +08:00
2015-12-22 22:06:19 +08:00
```javascript
function radiiEquals(left, right) {
2015-12-22 22:10:01 +08:00
return left.radius === right.radius;
2015-12-22 22:06:19 +08:00
}
```
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
- Use `undefined` instead of `null`.
2023-12-06 02:20:58 +08:00
- Test if a variable is defined using Cesium's `defined` function. It checks specifically for `undefined` and `null` values allowing _falsy_ values to be defined, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
```javascript
2023-12-06 02:20:58 +08:00
defined(undefined); // False
defined(null); // False
2015-12-09 03:41:02 +08:00
2023-12-06 02:20:58 +08:00
defined({}); // True
defined(""); // True
defined(0); // True
2015-12-09 03:41:02 +08:00
```
2020-04-17 08:31:36 +08:00
- Use `Object.freeze` function to create enums, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
```javascript
2023-12-06 02:20:58 +08:00
const ModelAnimationState = {
STOPPED: 0,
ANIMATING: 1,
};
2015-12-09 03:41:02 +08:00
2023-12-06 02:20:58 +08:00
return Object.freeze(ModelAnimationState);
2015-12-09 03:41:02 +08:00
```
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
- Use descriptive comments for non-obvious code, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
```javascript
byteOffset += sizeOfUint32; // Add 4 to byteOffset
```
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
is better written as
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
```javascript
byteOffset += sizeOfUint32; // Skip length field
```
2020-04-17 08:31:36 +08:00
2021-07-17 06:54:25 +08:00
- `TODO` comments need to be removed or addressed before the code is merged into main. Used sparingly, `PERFORMANCE_IDEA`, can be handy later when profiling.
- Remove commented out code before merging into main.
- Modern language features may provide handy shortcuts and cleaner syntax, but they should be used with consideration for their performance implications, especially in code that is invoked per-frame.
2015-12-09 03:41:02 +08:00
2015-12-07 04:36:07 +08:00
## Functions
2015-12-07 06:08:16 +08:00
- :art: Functions should be **cohesive**; they should only do one task.
2016-02-09 02:23:57 +08:00
- Statements in a function should be at a similar level of abstraction. If a code block is much lower level than the rest of the statements, it is a good candidate to move to a helper function, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-07 06:08:16 +08:00
```javascript
Cesium3DTileset.prototype.update = function (frameState) {
2022-01-22 00:22:49 +08:00
const tiles = this._processingQueue;
const length = tiles.length;
2015-12-07 06:08:16 +08:00
2022-01-22 00:22:49 +08:00
for (let i = length - 1; i >= 0; --i) {
2015-12-07 06:08:16 +08:00
tiles[i].process(this, frameState);
}
selectTiles(this, frameState);
updateTiles(this, frameState);
};
```
2020-04-17 08:31:36 +08:00
2015-12-07 06:08:16 +08:00
is better written as
2020-04-17 08:31:36 +08:00
2015-12-07 06:08:16 +08:00
```javascript
Cesium3DTileset.prototype.update = function (frameState) {
processTiles(this, frameState);
selectTiles(this, frameState);
updateTiles(this, frameState);
};
2017-04-28 03:46:29 +08:00
function processTiles(tileset, frameState) {
2022-01-22 00:22:49 +08:00
const tiles = tileset._processingQueue;
const length = tiles.length;
2015-12-07 06:08:16 +08:00
2022-01-22 00:22:49 +08:00
for (let i = length - 1; i >= 0; --i) {
2017-04-28 03:46:29 +08:00
tiles[i].process(tileset, frameState);
2015-12-07 06:08:16 +08:00
}
}
```
2020-04-17 08:31:36 +08:00
2015-12-22 22:06:19 +08:00
- Do not use an unnecessary `else` block at the end of a function, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-22 22:06:19 +08:00
```javascript
function getTransform(node) {
if (defined(node.matrix)) {
return Matrix4.fromArray(node.matrix);
} else {
return Matrix4.fromTranslationQuaternionRotationScale(
node.translation,
node.rotation,
2024-10-05 04:20:02 +08:00
node.scale,
2015-12-22 22:06:19 +08:00
);
}
}
```
2020-04-17 08:31:36 +08:00
2015-12-22 22:06:19 +08:00
is better written as
2020-04-17 08:31:36 +08:00
2015-12-22 22:06:19 +08:00
```javascript
function getTransform(node) {
if (defined(node.matrix)) {
return Matrix4.fromArray(node.matrix);
}
2020-04-17 08:31:36 +08:00
2015-12-22 22:06:19 +08:00
return Matrix4.fromTranslationQuaternionRotationScale(
node.translation,
node.rotation,
2024-10-05 04:20:02 +08:00
node.scale,
2015-12-22 22:06:19 +08:00
);
}
```
2020-04-17 08:31:36 +08:00
2015-12-15 22:15:23 +08:00
- :speedboat: Smaller functions are more likely to be optimized by JavaScript engines. Consider this for code that is likely to be a hot spot.
2015-12-07 06:08:16 +08:00
### `options` Parameters
2015-12-09 02:49:32 +08:00
:art: Many Cesium functions take an `options` parameter to support optional parameters, self-documenting code, and forward compatibility. For example, consider:
2020-04-17 08:31:36 +08:00
2015-12-07 06:08:16 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const sphere = new SphereGeometry(10.0, 32, 16, VertexFormat.POSITION_ONLY);
2015-12-07 06:08:16 +08:00
```
2020-04-17 08:31:36 +08:00
2015-12-16 06:56:34 +08:00
It is not clear what the numeric values represent, and the caller needs to know the order of parameters. If this took an `options` parameter, it would look like this:
2020-04-17 08:31:36 +08:00
2015-12-07 06:08:16 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const sphere = new SphereGeometry({
2015-12-07 06:08:16 +08:00
radius: 10.0,
stackPartitions: 32,
slicePartitions: 16,
vertexFormat: VertexFormat.POSITION_ONLY,
2015-12-09 04:01:19 +08:00
});
2015-12-07 06:08:16 +08:00
```
2020-04-17 08:31:36 +08:00
2015-12-15 23:05:52 +08:00
- :speedboat: Using `{ /* ... */ }` creates an object literal, which is a memory allocation. Avoid designing functions that use an `options` parameter if the function is likely to be a hot spot; otherwise, callers will have to use a scratch variable (see [below](#result-parameters-and-scratch-variables)) for performance. Constructor functions for non-math classes are good candidates for `options` parameters since Cesium avoids constructing objects in hot spots. For example,
2020-04-17 08:31:36 +08:00
2015-12-15 22:15:23 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const p = new Cartesian3({
2015-12-15 22:15:23 +08:00
x: 1.0,
y: 2.0,
z: 3.0,
});
```
2020-04-17 08:31:36 +08:00
2016-02-09 02:23:57 +08:00
is a bad design for the `Cartesian3` constructor function since its performance is not as good as that of
2020-04-17 08:31:36 +08:00
2015-12-15 22:15:23 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const p = new Cartesian3(1.0, 2.0, 3.0);
2015-12-15 22:15:23 +08:00
```
2015-12-07 06:08:16 +08:00
### Default Parameter Values
2025-03-04 03:03:53 +08:00
If a _sensible_ default exists for a function parameter or class property, don't require the user to provide it. Use the nullish coalescing operator [`??`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing) to assign a default value. For example, `height` defaults to zero in `Cartesian3.fromRadians`:
2020-04-17 08:31:36 +08:00
2015-12-07 06:08:16 +08:00
```javascript
Cartesian3.fromRadians = function (longitude, latitude, height) {
2025-03-04 03:03:53 +08:00
height = height ?? 0.0;
2015-12-07 06:08:16 +08:00
// ...
};
```
2020-04-17 08:31:36 +08:00
2025-03-04 03:03:53 +08:00
- :speedboat: `??` operator can also be used with a memory allocations in the right hand side, as it will be allocated only if the left hand side is either `null` or `undefined`, and therefore has no negative impact on performances, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-07 06:08:16 +08:00
```javascript
2025-03-04 03:03:53 +08:00
this._mapProjection = options.mapProjection ?? new GeographicProjection();
2015-12-07 06:08:16 +08:00
```
2020-04-17 08:31:36 +08:00
- If an `options` parameter is optional and never modified afterwards, use `Frozen.EMPTY_OBJECT` or `Frozen.EMPTY_ARRAY`, this could prevent from unnecessary memory allocations in code critical path, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-07 06:08:16 +08:00
```javascript
2025-03-04 03:03:53 +08:00
function BaseLayerPickerViewModel(options) {
options = options ?? Frozen.EMPTY_OBJECT;
2020-04-17 08:31:36 +08:00
2025-03-04 03:03:53 +08:00
const globe = options.globe;
const imageryProviderViewModels =
options.imageryProviderViewModels ?? Frozen.EMPTY_ARRAY;
2015-12-15 22:15:23 +08:00
// ...
}
```
2015-12-07 06:08:16 +08:00
2015-12-16 06:56:34 +08:00
Some common sensible defaults are
2020-04-17 08:31:36 +08:00
2015-12-07 06:08:16 +08:00
- `height`: `0.0`
- `ellipsoid`: `Ellipsoid.WGS84`
- `show`: `true`
### Throwing Exceptions
2023-12-06 02:20:58 +08:00
Use the functions of Cesium's [Check](https://github.com/CesiumGS/cesium/blob/main/Source/Core/Check.js) class to throw a `DeveloperError` when the user has a coding error. The most common errors are parameters that are missing, have the wrong type or are out of range.
2017-01-05 07:02:49 +08:00
- For example, to check that a parameter is defined and is an object:
2020-04-17 08:31:36 +08:00
2015-12-09 02:49:32 +08:00
```javascript
Cartesian3.maximumComponent = function (cartesian) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("cartesian", cartesian);
2015-12-09 02:49:32 +08:00
//>>includeEnd('debug');
return Math.max(cartesian.x, cartesian.y, cartesian.z);
};
```
- For more complicated parameter checks, manually check the parameter and then throw a `DeveloperError`. Example:
2020-04-17 08:31:36 +08:00
```javascript
2017-01-02 17:59:22 +08:00
Cartesian3.unpackArray = function (array, result) {
//>>includeStart('debug', pragmas.debug);
Check.defined("array", array);
Check.typeOf.number.greaterThanOrEquals("array.length", array.length, 3);
2017-01-02 17:59:22 +08:00
if (array.length % 3 !== 0) {
throw new DeveloperError("array length must be a multiple of 3.");
}
//>>includeEnd('debug');
// ...
};
```
2015-12-16 06:56:34 +08:00
- To check for `DeveloperError`, surround code in `includeStart`/`includeEnd` comments, as shown above, so developer error checks can be optimized out of release builds. Do not include required side effects inside `includeStart`/`includeEnd`, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-09 02:49:32 +08:00
```javascript
Cartesian3.maximumComponent = function (cartesian) {
//>>includeStart('debug', pragmas.debug);
2022-01-22 00:22:49 +08:00
const c = cartesian;
Check.typeOf.object("cartesian", cartesian);
2015-12-09 02:49:32 +08:00
//>>includeEnd('debug');
2015-12-15 22:15:23 +08:00
// Works in debug. Fails in release since c is optimized out!
return Math.max(c.x, c.y, c.z);
2015-12-09 02:49:32 +08:00
};
```
2020-04-17 08:31:36 +08:00
2015-12-09 02:49:32 +08:00
- Throw Cesium's `RuntimeError` for an error that will not be known until runtime. Unlike developer errors, runtime error checks are not optimized out of release builds.
2020-04-17 08:31:36 +08:00
2015-12-09 02:49:32 +08:00
```javascript
if (typeof WebGLRenderingContext === "undefined") {
throw new RuntimeError("The browser does not support WebGL.");
}
```
2020-04-17 08:31:36 +08:00
2015-12-16 06:56:34 +08:00
- :art: Exceptions are exceptional. Avoid throwing exceptions, e.g., if a polyline is only provided one position, instead of two or more, instead of throwing an exception just don't render it.
2015-12-07 06:08:16 +08:00
### `result` Parameters and Scratch Variables
2015-12-07 21:45:05 +08:00
:speedboat: In JavaScript, user-defined classes such as `Cartesian3` are reference types and are therefore allocated on the heap. Frequently allocating these types causes a significant performance problem because it creates GC pressure, which causes the Garbage Collector to run longer and more frequently.
2020-04-17 08:31:36 +08:00
2015-12-07 06:08:16 +08:00
Cesium uses required `result` parameters to avoid implicit memory allocation. For example,
```javascript
2022-01-22 00:22:49 +08:00
const sum = Cartesian3.add(v0, v1);
2015-12-07 06:08:16 +08:00
```
2020-04-17 08:31:36 +08:00
2015-12-07 06:08:16 +08:00
would have to implicitly allocate a new `Cartesian3` object for the returned sum. Instead, `Cartesian3.add` requires a `result` parameter:
2020-04-17 08:31:36 +08:00
2015-12-07 06:08:16 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const result = new Cartesian3();
const sum = Cartesian3.add(v0, v1, result); // Result and sum reference the same object
2015-12-07 06:08:16 +08:00
```
2020-04-17 08:31:36 +08:00
2015-12-09 03:25:07 +08:00
This makes allocations explicit to the caller, which allows the caller to, for example, reuse the result object in a file-scoped scratch variable:
2020-04-17 08:31:36 +08:00
2015-12-07 06:08:16 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const scratchDistance = new Cartesian3();
2015-12-07 06:08:16 +08:00
Cartesian3.distance = function (left, right) {
Cartesian3.subtract(left, right, scratchDistance);
return Cartesian3.magnitude(scratchDistance);
};
```
2020-04-17 08:31:36 +08:00
2015-12-07 06:08:16 +08:00
The code is not as clean, but the performance improvement is often dramatic.
2015-12-07 21:45:05 +08:00
As described below, `from` constructors also use optional `result` parameters.
2015-12-07 04:36:07 +08:00
2018-02-23 03:00:28 +08:00
Because result parameters aren't always required or returned, don't strictly rely on the result parameter you passed in to be modified. For example:
2020-04-17 08:31:36 +08:00
2018-02-23 03:00:28 +08:00
```js
Cartesian3.add(v0, v1, result);
Cartesian3.add(result, v2, result);
```
2020-04-17 08:31:36 +08:00
2018-02-23 03:00:28 +08:00
is better written as
2020-04-17 08:31:36 +08:00
2018-02-23 03:00:28 +08:00
```js
result = Cartesian3.add(v0, v1, result);
2018-02-23 03:00:28 +08:00
result = Cartesian3.add(result, v2, result);
```
2015-12-07 04:36:07 +08:00
## Classes
2015-12-16 06:56:34 +08:00
- :art: Classes should be **cohesive**. A class should represent one abstraction.
- :art: Classes should be **loosely coupled**. Two classes should not be entangled and rely on each other's implementation details; they should communicate through well-defined interfaces.
2015-12-07 07:25:16 +08:00
### Constructor Functions
- Create a class by creating a constructor function:
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
```javascript
2015-12-15 21:28:05 +08:00
function Cartesian3(x, y, z) {
2025-03-04 03:03:53 +08:00
this.x = x ?? 0.0;
this.y = y ?? 0.0;
this.z = z ?? 0.0;
2015-12-07 07:25:16 +08:00
}
```
2020-04-17 08:31:36 +08:00
2015-12-07 21:45:05 +08:00
- Create an instance of a class (an _object_) by calling the constructor function with `new`:
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const p = new Cartesian3(1.0, 2.0, 3.0);
2015-12-07 07:25:16 +08:00
```
2020-04-17 08:31:36 +08:00
2015-12-16 06:56:34 +08:00
- :speedboat: Assign to all the property members of a class in the constructor function. This allows JavaScript engines to use a hidden class and avoid entering dictionary mode. Assign `undefined` if no initial value makes sense. Do not add properties to an object, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const p = new Cartesian3(1.0, 2.0, 3.0);
2015-12-09 03:25:07 +08:00
p.w = 4.0; // Adds the w property to p, slows down property access since the object enters dictionary mode
```
2020-04-17 08:31:36 +08:00
2015-12-09 03:25:07 +08:00
- :speedboat: For the same reason, do not change the type of a property, e.g., assign a string to a number, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-09 03:25:07 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const p = new Cartesian3(1.0, 2.0, 3.0);
2015-12-09 03:25:07 +08:00
p.x = "Cesium"; // Changes x to a string, slows down property access
2015-12-07 07:25:16 +08:00
```
2021-03-29 22:35:52 +08:00
- In a constructor function, consider properties as write once; do not write to them or read them multiple times. Create a local variable if they need to be read. For example:
Instead of
2020-04-17 08:31:36 +08:00
2018-05-22 02:19:45 +08:00
```javascript
this._x = 2;
this._xSquared = this._x * this._x;
```
2018-05-22 02:19:45 +08:00
prefer
2020-04-17 08:31:36 +08:00
2018-05-22 02:19:45 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const x = 2;
2018-05-22 02:19:45 +08:00
this._x = x;
this._xSquared = x * x;
```
2015-12-07 07:25:16 +08:00
### `from` Constructors
2015-12-09 03:25:07 +08:00
:art: Constructor functions should take the basic components of the class as parameters. For example, `Cartesian3` takes `x`, `y`, and `z`.
2020-04-17 08:31:36 +08:00
2015-12-09 03:25:07 +08:00
It is often convenient to construct objects from other parameters. Since JavaScript doesn't have function overloading, Cesium uses static
2015-12-07 07:25:16 +08:00
functions prefixed with `from` to construct objects in this way. For example:
```javascript
2022-01-22 00:22:49 +08:00
const p = Cartesian3.fromRadians(-2.007, 0.645); // Construct a Cartesian3 object using longitude and latitude
2015-12-07 07:25:16 +08:00
```
2020-04-17 08:31:36 +08:00
2015-12-09 03:25:07 +08:00
These are implemented with an optional `result` parameter, which allows callers to pass in a scratch variable:
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
```javascript
Cartesian3.fromRadians = function (longitude, latitude, height, result) {
// Compute x, y, z using longitude, latitude, height
if (!defined(result)) {
result = new Cartesian3();
}
result.x = x;
result.y = y;
result.z = z;
return result;
};
```
2020-04-17 08:31:36 +08:00
2015-12-07 21:45:05 +08:00
Since calling a `from` constructor should not require an existing object, the function is assigned to `Cartesian3.fromRadians`, not `Cartesian3.prototype.fromRadians`.
2015-12-07 07:25:16 +08:00
### `to` Functions
Functions that start with `to` return a new type of object, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
```javascript
Cartesian3.prototype.toString = function () {
2023-12-06 02:20:58 +08:00
return `(${this.x}, ${this.y}, ${this.z})`;
2015-12-07 07:25:16 +08:00
};
```
2015-12-09 03:25:07 +08:00
### Use Prototype Functions for Fundamental Classes Sparingly
2015-12-07 07:25:16 +08:00
2015-12-09 03:41:02 +08:00
:art: Fundamental math classes such as `Cartesian3`, `Quaternion`, `Matrix4`, and `JulianDate` use prototype functions sparingly. For example, `Cartesian3` does not have a prototype `add` function like this:
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const v2 = v0.add(v1, result);
2015-12-07 07:25:16 +08:00
```
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
Instead, this is written as
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const v2 = Cartesian3.add(v0, v1, result);
2015-12-07 07:25:16 +08:00
```
2020-04-17 08:31:36 +08:00
2015-12-16 06:56:34 +08:00
The only exceptions are
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
- `clone`
- `equals`
- `equalsEpsilon`
- `toString`
These prototype functions generally delegate to the non-prototype (static) version, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
```javascript
Cartesian3.equals = function (left, right) {
2015-12-15 23:05:52 +08:00
return (
left === right ||
(defined(left) &&
defined(right) &&
left.x === right.x &&
left.y === right.y &&
left.z === right.z)
);
2015-12-07 07:25:16 +08:00
};
Cartesian3.prototype.equals = function (right) {
return Cartesian3.equals(this, right);
};
```
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
The prototype versions have the benefit of being able to be used polymorphically.
2015-12-15 22:26:41 +08:00
### Static Constants
2015-12-07 07:25:16 +08:00
To create a static constant related to a class, use `Object.freeze`:
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
```javascript
Cartesian3.ZERO = Object.freeze(new Cartesian3(0.0, 0.0, 0.0));
2015-12-07 07:25:16 +08:00
```
### Private Functions
Like private properties, private functions start with an `_`. In practice, these are rarely used. Instead, for better encapsulation, a file-scoped function that takes `this` as the first parameter is used. For example,
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
```javascript
Cesium3DTileset.prototype.update = function(frameState) {
this._processTiles(frameState);
// ...
};
2017-04-28 03:46:29 +08:00
Cesium3DTileset.prototype._processTiles(tileset, frameState) {
2022-01-22 00:22:49 +08:00
const tiles = this._processingQueue;
const length = tiles.length;
2015-12-07 07:25:16 +08:00
2022-01-22 00:22:49 +08:00
for (let i = length - 1; i >= 0; --i) {
2017-04-28 03:46:29 +08:00
tiles[i].process(tileset, frameState);
2015-12-07 07:25:16 +08:00
}
}
```
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
is better written as
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
```javascript
Cesium3DTileset.prototype.update = function (frameState) {
processTiles(this, frameState);
// ...
};
2017-04-28 03:46:29 +08:00
function processTiles(tileset, frameState) {
2022-01-22 00:22:49 +08:00
const tiles = tileset._processingQueue;
const length = tiles.length;
2015-12-07 07:25:16 +08:00
2022-01-22 00:22:49 +08:00
for (let i = length - 1; i >= 0; --i) {
2017-04-28 03:46:29 +08:00
tiles[i].process(tileset, frameState);
2015-12-07 07:25:16 +08:00
}
}
```
### Property Getter/Setters
2015-12-09 03:25:07 +08:00
Public properties that can be read or written without extra processing can simply be assigned in the constructor function, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
```javascript
2015-12-15 21:28:05 +08:00
function Model(options) {
2025-03-04 03:03:53 +08:00
this.show = options.show ?? true;
2015-12-07 07:25:16 +08:00
}
```
Read-only properties can be created with a private property and a getter using `Object.defineProperties` function, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
```javascript
2015-12-15 21:28:05 +08:00
function Cesium3DTileset(options) {
2015-12-07 07:25:16 +08:00
this._url = options.url;
}
Object.defineProperties(Cesium3DTileset.prototype, {
2015-12-07 07:25:16 +08:00
url: {
get: function () {
return this._url;
},
},
});
```
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
Getters can perform any needed computation to return the property, but the performance expectation is that they execute quickly.
Setters can also perform computation before assigning to a private property, set a flag to delay computation, or both, for example:
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
```javascript
Object.defineProperties(UniformState.prototype, {
2015-12-07 07:25:16 +08:00
viewport: {
get: function () {
return this._viewport;
},
set: function (viewport) {
if (!BoundingRectangle.equals(viewport, this._viewport)) {
BoundingRectangle.clone(viewport, this._viewport);
2020-04-17 08:31:36 +08:00
2022-01-22 00:22:49 +08:00
const v = this._viewport;
const vc = this._viewportCartesian4;
2015-12-07 07:25:16 +08:00
vc.x = v.x;
vc.y = v.y;
vc.z = v.width;
vc.w = v.height;
2020-04-17 08:31:36 +08:00
2015-12-07 07:25:16 +08:00
this._viewportDirty = true;
}
},
},
});
```
2015-12-07 04:36:07 +08:00
2015-12-09 03:25:07 +08:00
- :speedboat: Calling the getter/setter function is slower than direct property access so functions internal to a class can use the private property directly when appropriate.
2015-12-07 21:45:05 +08:00
2015-12-09 03:25:07 +08:00
### Shadowed Property
2015-12-16 06:56:34 +08:00
When the overhead of getter/setter functions is prohibitive or reference-type semantics are desired, e.g., the ability to pass a property as a `result` parameter so its properties can be modified, consider combining a public property with a private shadowed property, e.g.,
2020-04-17 08:31:36 +08:00
2015-12-09 03:25:07 +08:00
```javascript
2015-12-15 21:28:05 +08:00
function Model(options) {
2025-03-04 03:03:53 +08:00
this.modelMatrix = Matrix4.clone(options.modelMatrix ?? Matrix4.IDENTITY);
2015-12-09 03:25:07 +08:00
this._modelMatrix = Matrix4.clone(this.modelMatrix);
}
Model.prototype.update = function (frameState) {
if (!Matrix4.equals(this._modelMatrix, this.modelMatrix)) {
2015-12-09 03:41:02 +08:00
// clone() is a deep copy. Not this._modelMatrix = this._modelMatrix
Matrix4.clone(this.modelMatrix, this._modelMatrix);
2015-12-09 03:25:07 +08:00
// Do slow operations that need to happen when the model matrix changes
}
};
```
2015-12-07 21:45:05 +08:00
### Put the Constructor Function at the Top of the File
2015-12-09 03:41:02 +08:00
It is convenient for the constructor function to be at the top of the file even if it requires that helper functions rely on **hoisting**, for example, `Cesium3DTileset.js`,
2020-04-17 08:31:36 +08:00
```javascript
2017-04-28 03:46:29 +08:00
function loadTileset(tileset, tilesJson, done) {
// ...
}
2015-12-15 21:28:05 +08:00
function Cesium3DTileset(options) {
2020-04-17 08:31:36 +08:00
// ...
2017-04-28 03:46:29 +08:00
loadTileset(this, options.url, function (data) {
// ...
});
}
```
2020-04-17 08:31:36 +08:00
is better written as
2020-04-17 08:31:36 +08:00
```javascript
2015-12-15 21:28:05 +08:00
function Cesium3DTileset(options) {
2020-04-17 08:31:36 +08:00
// ...
2017-04-28 03:46:29 +08:00
loadTileset(this, options.url, function (data) {
// ...
});
}
2017-04-28 03:46:29 +08:00
function loadTileset(tileset, tilesJson, done) {
// ...
}
```
2020-04-17 08:31:36 +08:00
2017-04-28 03:46:29 +08:00
even though it relies on implicitly hoisting the `loadTileset` function to the top of the file.
## Design
2015-12-16 06:56:34 +08:00
- :house: Make a class or function part of the Cesium API only if it will likely be useful to end users; avoid making an implementation detail part of the public API. When something is public, it makes the Cesium API bigger and harder to learn, is harder to change later, and requires more documentation work.
- :art: Put new classes and functions in the right part of the Cesium stack (directory). From the bottom up:
2021-07-17 06:54:25 +08:00
- `Source/Core` - Number crunching. Pure math such as [`Cartesian3`](https://github.com/CesiumGS/cesium/blob/main/Source/Core/Cartesian3.js). Pure geometry such as [`CylinderGeometry`](https://github.com/CesiumGS/cesium/blob/main/Source/Core/CylinderGeometry.js). Fundamental algorithms such as [`mergeSort`](https://github.com/CesiumGS/cesium/blob/main/Source/Core/mergeSort.js). Request helper functions such as [`loadArrayBuffer`](https://github.com/CesiumGS/cesium/blob/main/Source/Core/loadArrayBuffer.js).
- `Source/Renderer` - WebGL abstractions such as [`ShaderProgram`](https://github.com/CesiumGS/cesium/blob/main/Source/Renderer/ShaderProgram.js) and WebGL-specific utilities such as [`ShaderCache`](https://github.com/CesiumGS/cesium/blob/main/Source/Renderer/ShaderCache.js). Identifiers in this directory are not part of the public Cesium API.
- `Source/Scene` - The graphics engine, including primitives such as [Model](https://github.com/CesiumGS/cesium/blob/main/Source/Scene/Model.js). Code in this directory often depends on `Renderer`.
- `Source/DataSources` - Entity API, such as [`Entity`](https://github.com/CesiumGS/cesium/blob/main/Source/DataSources/Entity.js), and data sources such as [`CzmlDataSource`](https://github.com/CesiumGS/cesium/blob/main/Source/DataSources/CzmlDataSource.js).
- `Source/Widgets` - Widgets such as the main Cesium [`Viewer`](https://github.com/CesiumGS/cesium/blob/main/Source/Widgets/Viewer/Viewer.js).
2021-07-17 06:54:25 +08:00
It is usually obvious what directory a file belongs in. When it isn't, the decision is usually between `Core` and another directory. Put the file in `Core` if it is pure number crunching or a utility that is expected to be generally useful to Cesium, e.g., [`Matrix4`](https://github.com/CesiumGS/cesium/blob/main/Source/Core/Matrix4.js) belongs in `Core` since many parts of the Cesium stack use 4x4 matrices; on the other hand, [`BoundingSphereState`](https://github.com/CesiumGS/cesium/blob/main/Source/DataSources/BoundingSphereState.js) is in `DataSources` because it is specific to data sources.
2022-05-31 21:50:32 +08:00
![CesiumJS Design](1.jpg)
2015-12-15 22:15:23 +08:00
Modules (files) should only reference modules in the same level or a lower level of the stack. For example, a module in `Scene` can use modules in `Scene`, `Renderer`, and `Core`, but not in `DataSources` or `Widgets`.
2020-04-17 08:31:36 +08:00
2015-12-09 03:41:02 +08:00
- WebGL resources need to be explicitly deleted so classes that contain them (and classes that contain these classes, and so on) have `destroy` and `isDestroyed` functions, e.g.,
2020-04-17 08:31:36 +08:00
```javascript
2022-01-22 00:22:49 +08:00
const primitive = new Primitive(/* ... */);
expect(content.isDestroyed()).toEqual(false);
primitive.destroy();
expect(content.isDestroyed()).toEqual(true);
```
2020-04-17 08:31:36 +08:00
A `destroy` function is implemented with Cesium's `destroyObject` function, e.g.,
2020-04-17 08:31:36 +08:00
```javascript
SkyBox.prototype.destroy = function () {
this._vertexArray = this._vertexArray && this._vertexArray.destroy();
return destroyObject(this);
};
```
2020-04-17 08:31:36 +08:00
2015-12-15 22:15:23 +08:00
- Only `destroy` objects that you create; external objects given to a class should be destroyed by their owner, not the class.
### Deprecation and Breaking Changes
From release to release, we strive to keep the public Cesium API stable but also maintain mobility for speedy development and to take the API in the right direction. As such, we sparingly deprecate and then remove or replace parts of the public API.
A `@private` API is considered a Cesium implementation detail and can be broken immediately without deprecation.
2017-11-30 06:25:27 +08:00
An `@experimental` API is subject to breaking changes in future Cesium releases without deprecation. It allows for new experimental features, for instance implementing draft formats.
A public identifier (class, function, property) should be deprecated before being removed. To do so:
2020-04-17 08:31:36 +08:00
- Decide on which future version the deprecated API should be removed. This is on a case-by-case basis depending on how badly it impacts users and Cesium development. Most deprecated APIs will removed in 3-6 releases. This can be discussed in the pull request if needed.
2021-07-17 06:54:25 +08:00
- Use [`deprecationWarning`](https://github.com/CesiumGS/cesium/blob/main/Source/Core/deprecationWarning.js) to warn users that the API is deprecated and what proactive changes they can take, e.g.,
```javascript
function Foo() {
deprecationWarning(
"Foo",
2024-10-05 04:20:02 +08:00
"Foo was deprecated in CesiumJS 1.01. It will be removed in 1.03. Use newFoo instead.",
);
// ...
}
```
2020-04-17 08:31:36 +08:00
2015-12-18 22:29:03 +08:00
- Add the [`@deprecated`](http://usejsdoc.org/tags-deprecated.html) doc tag.
2015-12-19 05:11:23 +08:00
- Remove all use of the deprecated API inside Cesium except for unit tests that specifically test the deprecated API.
2021-07-17 06:54:25 +08:00
- Mention the deprecation in the `Deprecated` section of [`CHANGES.md`](https://github.com/CesiumGS/cesium/blob/main/CHANGES.md). Include what Cesium version it will be removed in.
- Create an [issue](https://github.com/CesiumGS/cesium/issues) to remove the API with the appropriate `remove in [version]` label.
- Upon removal of the API, add a mention of it in the `Breaking Changes` section of [`CHANGES.md`](https://github.com/CesiumGS/cesium/blob/main/CHANGES.md).
## Third-Party Libraries
2020-04-09 04:41:17 +08:00
:house: Cesium uses third-party libraries sparingly. If you want to add a new one, please start a thread on the [Cesium community forum](https://community.cesium.com/) ([example discussion](https://community.cesium.com/t/do-we-like-using-third-party-libraries/745)). The library should
2020-04-17 08:31:36 +08:00
- Have a compatible license such as MIT, BSD, or Apache 2.0.
2015-12-16 06:56:34 +08:00
- Provide capabilities that Cesium truly needs and that the team doesn't have the time and/or expertise to develop.
- Be lightweight, tested, maintained, and reasonably widely used.
2015-12-15 22:15:23 +08:00
- Not pollute the global namespace.
2015-12-09 03:41:02 +08:00
- Provide enough value to justify adding a third-party library whose integration needs to be maintained and has the potential to slightly count against Cesium when some users evaluate it (generally, fewer third-parties is better).
2022-05-05 02:06:29 +08:00
When adding or updating a third-party library:
- Ensure [LICENSE.md](../../../LICENSE.md) is updated with the library's name and full copyright notice.
- If a library is shipped as part of the CesiumJS release, it should be included in the generated [`ThirdParty.json`](../../../ThirdParty.json).
1. Update [`ThirdParty.extra.json`](../../../ThirdParty.extra.json) with the package `name`. If it is an npm module included in [`package.json`](../../../package.json), use the exact package name.
2. If the library is _not_ an npm module included in `package.json`, provide the `license`, `version`, and `url` fields. Otherwise, this information can be detected using `package.json`.
3. If there is a special case regarding the license, such as choosing to use a single license from a list of multiple available ones, providing the `license` field will override information detected using `package.json`. The `notes` field should also be provided in the case explaining the exception.
2022-05-05 04:13:18 +08:00
4. Run `npm run build-third-party` and commit the resulting `ThirdParty.json`
2022-05-05 02:06:29 +08:00
2016-11-03 04:00:58 +08:00
## Widgets
Cesium includes a handful of standard widgets that are used in the Viewer, including animation and timeline controls, a base layer picker, and a geocoder. These widgets are all built using [Knockout](http://knockoutjs.com/)) for automatic UI refreshing. Knockout uses a Model View ViewModel (MVVM) design pattern. You can learn more about this design pattern in [Understanding MVVM - A Guide For JavaScript Developers](https://addyosmani.com/blog/understanding-mvvm-a-guide-for-javascript-developers/)
2016-11-03 04:03:19 +08:00
To learn about using the Knockout library, see the [Get started](http://knockoutjs.com/) section of their home page. They also have a great [interactive tutorial](http://learn.knockoutjs.com/) with step by step instructions.
2016-11-03 04:00:58 +08:00
2021-07-17 06:54:25 +08:00
Cesium also uses the [Knockout-ES5](http://blog.stevensanderson.com/2013/05/20/knockout-es5-a-plugin-to-simplify-your-syntax/) plugin to simplify knockout syntax. This lets us use knockout observables the same way we use other variables. Call `knockout.track` to create the observables. Here is an example from [BaseLayerPickerViewModel](https://github.com/CesiumGS/cesium/blob/main/Source/Widgets/BaseLayerPicker/BaseLayerPickerViewModel.js#L73) that makes observables for `tooltip`, `showInstructions` and `_touch` properties.
2016-11-03 23:32:51 +08:00
2016-11-03 04:00:58 +08:00
```javascript
knockout.track(this, ["tooltip", "showInstructions", "_touch"]);
```
2016-11-03 23:32:51 +08:00
### Knockout subscriptions
2016-11-03 04:00:58 +08:00
2021-07-17 06:54:25 +08:00
Use a knockout subscription only when you are unable to accomplish what you need to do with a standard binding. For [example](https://github.com/CesiumGS/cesium/blob/main/Source/Widgets/Viewer/Viewer.js#L588), the `Viewer` subscribes to `FullscreenButtonViewModel.isFullscreenEnabled` because it needs to change the width of the timeline widget when that value changes. This cannot be done with binding because the value from `FullscreenButtonViewModel` is affecting a value not contained within that widget.
2016-11-03 04:00:58 +08:00
2021-07-17 06:54:25 +08:00
Cesium includes a [`subscribeAndEvaluate`](https://github.com/CesiumGS/cesium/blob/main/Source/Widgets/subscribeAndEvaluate.js) helper function for subscribing to knockout observable.
2016-11-03 04:00:58 +08:00
2021-07-17 06:54:25 +08:00
When using a subscription, always be sure to [dispose the subscription](https://github.com/CesiumGS/cesium/blob/main/Source/Widgets/Viewer/Viewer.js#L1413) when the viewmodel is no longer using it. Otherwise the listener will continue to be notified for the lifetime of the observable.
2016-11-03 04:00:58 +08:00
2022-05-31 21:50:32 +08:00
```javascript
2016-11-03 04:00:58 +08:00
fullscreenSubscription = subscribeAndEvaluate(fullscreenButton.viewModel, 'isFullscreenEnabled', function(isFullscreenEnabled) { ... });
// ...then later...
fullscreenSubscription.dispose();
2016-11-03 04:00:58 +08:00
```
2015-12-07 03:22:39 +08:00
## GLSL
### GLSL Naming
2015-12-07 03:22:39 +08:00
- GLSL files end with `.glsl` and are in the [Shaders](https://github.com/CesiumGS/cesium/tree/main/packages/engine/Source/Shaders) directory.
2015-12-07 03:22:39 +08:00
- Files for vertex shaders have a `VS` suffix; fragment shaders have an `FS` suffix. For example: `BillboardCollectionVS.glsl` and `BillboardCollectionFS.glsl`.
- Generally, identifiers, such as functions and variables, use `camelCase`.
- Cesium built-in identifiers start with `czm_`, for example, [`czm_material`](https://github.com/CesiumGS/cesium/blob/main/packages/engine/Source/Shaders/Builtin/Structs/material.glsl). Files have the same name without the `czm_` prefix, e.g., `material.glsl`.
2022-12-16 07:11:07 +08:00
- Use `czm_textureCube` when sampling a cube map instead of `texture`. This is to preserve backwards compatibility with WebGL 1.
2015-12-07 04:36:07 +08:00
- Varyings start with `v_`, e.g.,
```javascript
in vec2 v_textureCoordinates;
```
2015-12-07 04:36:07 +08:00
- Uniforms start with `u_`, e.g.,
```javascript
uniform sampler2D u_atlas;
```
2015-12-09 04:25:01 +08:00
- An `EC` suffix indicates the point or vector is in eye coordinates, e.g.,
```glsl
varying vec3 v_positionEC;
// ...
v_positionEC = (czm_modelViewRelativeToEye * p).xyz;
```
- When [GPU RTE](https://help.agi.com/AGIComponents/html/BlogPrecisionsPrecisions.htm) is used, `High` and `Low` suffixes define the high and low bits, respectively, e.g.,
```glsl
attribute vec3 position3DHigh;
attribute vec3 position3DLow;
```
2015-12-07 04:36:07 +08:00
- 2D texture coordinates are `s` and `t`, not `u` and `v`, e.g.,
```glsl
attribute vec2 st;
```
2020-04-17 08:31:36 +08:00
### GLSL Formatting
2015-12-07 03:22:39 +08:00
- Use the same formatting as JavaScript, except put `{` on a new line, e.g.,
```glsl
struct czm_ray
{
vec3 origin;
vec3 direction;
};
```
2020-04-17 08:31:36 +08:00
### GLSL Performance
2015-12-07 03:22:39 +08:00
2015-12-16 06:56:34 +08:00
- :speedboat: Compute expensive values as infrequently as possible, e.g., prefer computing a value in JavaScript and passing it in a uniform instead of redundantly computing the same value per-vertex. Likewise, prefer to compute a value per-vertex and pass a varying, instead of computing per-fragment when possible.
2015-12-07 03:22:39 +08:00
- :speedboat: Use `discard` sparingly since it disables early-z GPU optimizations.
#### Branching in shaders
Conditional logic may have a performance impact when executing shader code. GPUs rely on many parallel calculations being executable at once, and branching code can disrupt that parallelism— executing expensive instructions for one vertex or fragment in one thread may block the execution of the others.
- Avoid executing non-trivial code in `if-else` statements.
- Branching based on the value of uniform, e.g. `if (czm_orthographicIn3D == 1.0)`, or variables consistent across all vertices or fragments shouldn't cause a bottleneck.
- :speedboat: Use `czm_branchFreeTernary` to avoid branching. For example:
```glsl
if (sphericalLatLong.y >= czm_pi) {
sphericalLatLong.y = sphericalLatLong.y - czm_twoPi;
}
```
could be better written as
```glsl
sphericalLatLong.y = czm_branchFreeTernary(sphericalLatLong.y < czm_pi, sphericalLatLong.y, sphericalLatLong.y - czm_twoPi);
```
2015-12-07 03:22:39 +08:00
## Resources
2015-12-15 23:05:52 +08:00
See Section 4.1 to 4.3 of [Getting Serious with JavaScript](http://webglinsights.github.io/downloads/WebGL-Insights-Chapter-4.pdf) by Cesium contributors Matthew Amato and Kevin Ring in _WebGL Insights_ for deeper coverage of modules and performance.
2015-12-07 03:22:39 +08:00
Watch [From Console to Chrome](https://www.youtube.com/watch?v=XAqIpGU8ZZk) by Lilli Thompson for even deeper performance coverage.