Cesium编码指南(翻译)
Cesium编码指南
CesiumJS是世界上众多大型JavaScript库之一。 自开始之初,我们便将代码维持在高质量水平,不管是新手还是老司机都能够轻松使用。希望你能感受到我们的代码库是整洁且一致的。
除了阐述典型的代码约定,这份指南也包含有设计、可维护性、性能方面的最佳实践。这些建议来自于有多年经验的老司机们。
这份指南适用于CesiumJS本身及其使用JavaScript开发的生态系统。
🎨 调色板图标表示一种设计技巧。
🏠 房子图标表示是一种可维护性技巧。当然,整本指南就是关于写出可维护的代码。
🚤 快艇图标表示一种性能方面的技巧
在一定程度上,这份指南是比较精炼的,通过代码改进前后的对比。
命名
- 文件夹使用
帕斯卡方式(每个单词首字母大写)命名, 例如:Source/Scene。 - 构造函数使用
帕斯卡方式命名, 例如:Cartesian3。 - 函数使用
驼峰(首字个单词的首字母小写,其余单词首字母大写)方式命名, 例如:defaultValue(),Cartesian3.equalsEpsilon()。 - 以
.js结尾的文件命名与其功能特点保持一致,例如:Cartesian3.jsanddefaultValue.js。 - 变量、类的属性名称使用
驼峰方式命名,例如:
this.minimumPixelSize = 1.0; // 类属性
var bufferViews = gltf.bufferViews; // 变量
- 私有(Private)成员约定以下划线开头,例如:
this._canvas = canvas;
- 常量,字母全部大写且以下划线分隔,例如:
Cartesian3.UNIT_X = Object.freeze(new Cartesian3(1.0, 0.0, 0.0));
- 公有成员避免使用缩写,除非全称十分冗长且有被广泛接受的缩写可使用,比如:
Cartesian3.maximumComponent(); // Not Cartesian3.maxComponent()
Ellipsoid.WGS84; // Not Ellipsoid.WORLD_GEODETIC_SYSTEM_1984
- 局部变量使用短小且有意义的名称,比如:一个函数只有一个长度变量,
var primitivesLength = primitives.length;
更好的写法:
var length = primitives.length;
- 当要在闭包中访问外层作用域的
this时,声明一个变量that,通过that访问,比如:
var that = this;
this._showTouch = createCommand(function () {
that._touch = true;
});
还有一些命名约定将在设计模块中介绍,如:选项 参数, result 参数和引用变量, and from 构造函数.
排版
- 对于JS代码我们已使用prettier在代码提交时自动排版,所以你不必操心。
- 对于HTML代码,保持原有的样式,使用双引号。
- 文本文件已换行符结束。
编码检查
对于语法和样式规范,我们使用ESLint,在ESLint推荐设置的基础上做了一些扩展,并发布了一个node包:eslint-config-cesium。这个包作为Cesium官方仓库的一部分在Cesium的整个生态中使用。了解哪些规则是被允许的,请查看:index.js, browser.js 和 node.js。
常用规则:
- block-scoped-var
- no-alert
- no-floating-decimal
- no-implicit-globals
- no-loop-func
- no-use-before-define 防止在定义变量和函数之前使用它们。
- no-else-return
- no-undef-init
- no-sequences
- no-unused-expressions
- no-trailing-spaces
- no-lonely-if
- quotes 强制使用单引号
- no-sequences
- no-unused-expressions
特殊规则:
- 当只对一行不应用规则时, 使用:
//eslint-disable-next-line:
function exit(warningMessage) {
//eslint-disable-next-line no-alert
window.alert("Cannot exit: " + warningMessage);
}
- 当对某块代码不应用规则时,使用
eslint-disable对代码块进行包裹:
/*eslint-disable no-empty*/
try {
lineNumber = parseInt(stack.substring(lineStart + 1, lineEnd1), 10);
} catch (ex) {}
/*eslint-enable no-empty*/
单位
- Cesium使用国际标准单位:
- 米作为长度单位,
- 度数以弧度表示,
- 时间的基础单位为秒。
- 如果一个函数的参数不是标准单位,比如:角度,使用对应的函数进行转换。例如:
Cartesian3.fromDegrees = function (
longitude,
latitude,
height,
ellipsoid,
result
) {
/* ... */
};
构建基础代码
- Cesium使用JavaScript的严格模式,所以,每个模块文件中都包含有:
"use strict";
- 🚤 为了避免隐式的类型转换,判断是否相等时使用
===或!==, 例如:
var i = 1;
if (i === 1) {
// ...
}
if (i !== 1) {
// ...
}
- 为了便于阅读,在表示浮点数的后面追加
.0,如下方的代码,除非f为整型,否则使用浮点型。
var f = 1;
更好的写法:
var f = 1.0;
- 在变量第一次使用的地方声明它。例如:
var i;
var m;
var models = [
/* ... */
];
var length = models.length;
for (i = 0; i < length; ++i) {
m = models[i];
// Use m
}
更好的写法:
var models = [
/* ... */
];
var length = models.length;
for (var i = 0; i < length; ++i) {
var m = models[i];
// Use m
}
- 变量属于函数作用域,而不是块作用域。不要依赖变量提升,如,在未声明前使用:
console.log(i); //i的值为undefined,永远不要在变量未声明前就使用它。
var i = 0.0;
- 🚤 避免使用过长的属性访问。
scene.environmentState.isSkyAtmosphereVisible = true;
scene.environmentState.isSunVisible = true;
scene.environmentState.isMoonVisible = false;
更好的写法:
var environmentState = scene.environmentState;
environmentState.isSkyAtmosphereVisible = true;
environmentState.isSunVisible = true;
environmentState.isMoonVisible = false;
- 不要创建只使用一次的变量,除非它能显著提高可读性。
function radiiEquals(left, right) {
var leftRadius = left.radius;
var rightRadius = right.radius;
return leftRadius === rightRadius;
}
更好的写法:
function radiiEquals(left, right) {
return left.radius === right.radius;
}
- 使用
undefined代替null。 - 判断一个变量是否定义,使用Cesium的
defined函数,如:
var v = undefined;
if (defined(v)) {
// False
}
var u = {};
if (defined(u)) {
// True
}
- 使用
Object.freeze函数创建枚举,如:
var ModelAnimationState = {
STOPPED : 0,
ANIMATING : 1
};
return Object.freeze(ModelAnimationState);
});
- 为让人困惑的代码添加能表明意思的,有意义的注释,如:
byteOffset += sizeOfUint32; // Add 4 to byteOffset
更好的写法:
byteOffset += sizeOfUint32; // Skip length field
TODO注释需要在合并到主分支(master)前移除掉。保守使用PERFORMANCE_IDEA,在代码分析时做优化会更好。- 在合并到主分支(master)之前移除注释掉的代码。
函数
- 🎨 一个函数应只做一件事。
- 函数中的语句应属于同一级别的抽象。如果一个代码块比其它语句级别更低,则最好把它提取出来。比如:
Cesium3DTileset.prototype.update = function (frameState) {
var tiles = this._processingQueue;
var length = tiles.length;
for (var i = length - 1; i >= 0; --i) {
tiles[i].process(this, frameState);
}
selectTiles(this, frameState);
updateTiles(this, frameState);
};
更好的写法:
Cesium3DTileset.prototype.update = function (frameState) {
processTiles(this, frameState);
selectTiles(this, frameState);
updateTiles(this, frameState);
};
function processTiles(tileset, frameState) {
var tiles = tileset._processingQueue;
var length = tiles.length;
for (var i = length - 1; i >= 0; --i) {
tiles[i].process(tileset, frameState);
}
}
- 不必在函数的末尾使用
else。如:
function getTransform(node) {
if (defined(node.matrix)) {
return Matrix4.fromArray(node.matrix);
} else {
return Matrix4.fromTranslationQuaternionRotationScale(
node.translation,
node.rotation,
node.scale
);
}
}
更好的写法:
function getTransform(node) {
if (defined(node.matrix)) {
return Matrix4.fromArray(node.matrix);
}
return Matrix4.fromTranslationQuaternionRotationScale(
node.translation,
node.rotation,
node.scale
);
}
- 🚤 更小的函数更能被JavaScript引擎优化。考虑哪些更有可能成为热点代码(hot spot)。
options 参数
🎨 许多的Cesium函数使用 options 参数来实现可选参数,能自释义和向前兼容。比如,考虑如下代码:
var sphere = new SphereGeometry(10.0, 32, 16, VertexFormat.POSITION_ONLY);
数字参数表述的意思并不清楚,而且调用者需要知道参数的传入顺序。如果使用 options 参数, 它会是这样的:
var sphere = new SphereGeometry({
radius: 10.0,
stackPartitions: 32,
slicePartitions: 16,
vertexFormat: VertexFormat.POSITION_ONLY,
});
- 🚤 使用
{ /* ... */ }创建的字面量对象,将会占用一定的内存空间,所以对有可能成为热点代码的函数应该避免使用options参数,否则调用者为了提高性能不得不使用引用变量(见下方)。对非数学类相关的构造函数,可以考虑使用options参数,因为Cesium避免使用在热点代码中构造对象,比如:
var p = new Cartesian3({
x: 1.0,
y: 2.0,
z: 3.0,
});
下面的使用方式性能上要优于上面的使用方式
var p = new Cartesian3(1.0, 2.0, 3.0);
参数默认值
为函数参数或类属性值提供默认值是十分合理的。使用Cesium的 defaultValue 指定一个默认值,例如:设置 Cartesian3.fromRadians的 height 的默认值为0.0:
Cartesian3.fromRadians = function (longitude, latitude, height) {
height = defaultValue(height, 0.0);
// ...
};
- 🚤 如果会导致函数调用或内存分配,就不要使用
defaultValue, 比如:
this._mapProjection = defaultValue(
options.mapProjection,
new GeographicProjection()
);
更好的写法:
this._mapProjection = defined(options.mapProjection)
? options.mapProjection
: new GeographicProjection();
- 如果一个
options参数是可选的,可使用defaultValue.EMPTY_OBJECT, 如:
function DebugModelMatrixPrimitive(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this.length = defaultValue(options.length, 10000000.0);
this.width = defaultValue(options.width, 2.0);
// ...
}
一些合理的默认值:
height:0.0ellipsoid:Ellipsoid.WGS84show:true
抛出异常
当编码者有编码错误时,使用Cesium的 Check 类来抛出 DeveloperError。大多数错误要么是参数缺失要么是类型错误或超出范围。
- 例如,检查参数是否定义且是一个object对象:
Cartesian3.maximumComponent = function (cartesian) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("cartesian", cartesian);
//>>includeEnd('debug');
return Math.max(cartesian.x, cartesian.y, cartesian.z);
};
- 对于复杂的参数进行手动检查,不通过就抛出
DeveloperError错误,比如:
Cartesian3.unpackArray = function (array, result) {
//>>includeStart('debug', pragmas.debug);
Check.defined("array", array);
Check.typeOf.number.greaterThanOrEquals("array.length", array.length, 3);
if (array.length % 3 !== 0) {
throw new DeveloperError("array length must be a multiple of 3.");
}
//>>includeEnd('debug');
// ...
};
- 在进行
DeveloperError检查时,请像上面的代码那样使用includeStart/includeEnd注释包裹代码,因为在编译发布包时会对这些代码进行优化。不用在includeStart/includeEnd中添加有副作用的代码,比如:
Cartesian3.maximumComponent = function (cartesian) {
//>>includeStart('debug', pragmas.debug);
var c = cartesian;
Check.typeOf.object("cartesian", cartesian);
//>>includeEnd('debug');
// 在编译之后,c 会被优化掉!
return Math.max(c.x, c.y, c.z);
};
- 抛出Cesium的
RuntimeError错误时,只有在运行时才知道,所以不能像developer errors那样在编译发布时进行优化。
if (typeof WebGLRenderingContext === "undefined") {
throw new RuntimeError("The browser does not support WebGL.");
}
- 🎨 应尽量避免抛出异常,因为异常表示的是“意外”。比如:一个polyline只提供了一个位置点而不是两个或多个,不必抛出异常,不渲染它就好。
result 参数和引用变量
🚤 在JavaScript中,引用类型在堆上分配内存;用户定义的一些类,比如:Cartesian3 便是引用类型。频繁的分配这些类型会造成显著的性能问题,因为这会造成频繁且耗时长的垃圾回收。
Cesium使用 result 参数来避免隐式的内存分配,例如:
var sum = Cartesian3.add(v0, v1);
上面的代码将会隐式的声明一个 Cartesian3 对象返回。好的方式,给 Cartesian3.add 传入一个 result 参数:
var result = new Cartesian3();
var sum = Cartesian3.add(v0, v1, result); // Result and sum reference the same object
这样显式的将内存分配给了调用函数,就可以在作用域内反复使用result对象:
var scratchDistance = new Cartesian3();
Cartesian3.distance = function (left, right) {
Cartesian3.subtract(left, right, scratchDistance);
return Cartesian3.magnitude(scratchDistance);
};
这段代码虽然不是很整洁,但对性能提升是显著的。
下面将会看到的 from 构造函数也使用了可选的 result 参数。
因为 result 参数并不总是需要传入或返回,所以不要直接依赖你传入的result参数进行修改,例如:
Cartesian3.add(v0, v1, result);
Cartesian3.add(result, v2, result);
更好的写法:
result = Cartesian3.add(v0, v1, result);
result = Cartesian3.add(result, v2, result);
类
- 🎨 一个类只抽象一件事。
- 🎨 类应该是松耦合的。两个类之间不应该依赖于彼此的实现细节,而应该通过定义好的接口。
构造函数
- 通过构造函数创建类:
function Cartesian3(x, y, z) {
this.x = defaultValue(x, 0.0);
this.y = defaultValue(y, 0.0);
this.z = defaultValue(z, 0.0);
}
- 通过
new关键词创建类的实例:
var p = new Cartesian3(1.0, 2.0, 3.0);
- 🚤 在构造函数中声明类的所有属性成员,这样可以让JavaScript引擎利用隐藏类而不是字典模式。如果初始值无意义可赋值为
undefined。不要在实例上在添加其它属性,如:
var p = new Cartesian3(1.0, 2.0, 3.0);
p.w = 4.0; // 给p添加w属性会降低属性的检索速度,因为会进入字典模式。
- 🚤 基于同样的原因,也不要更改属性值的类型,例如将字符串类型改为数字类型。比如:
var p = new Cartesian3(1.0, 2.0, 3.0);
p.x = "Cesium"; //将x改为字符串类型也会降低属性的检索速度。
-
在一个构造函数中,建议只对属性进行一次赋值操作而不要频繁的进行读写操作。必要时可声明一个本地变量,如:
比较差的写法:
this._x = 2; this._xSquared = this._x * this._x;更好的写法:
var x = 2; this._x = x; this._xSquared = x * x;
from 构造函数
🎨 构造函数一般有参数来作为类的基本组成部分,例如:Cartesian3 有 x、y、和 z.
用其它参数来构造对象通常是很方便的。因为JavaScript没有函数重载功能,Cesium使用一个带有 from 前缀的静态函数来构造对象,如:
var p = Cartesian3.fromRadians(-2.007, 0.645); // 使用经度和纬度来构建一个Cartesian3对象
此构造函数实现有一个可选的 result 参数,可以让调用者传入一个引用变量,如:
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;
};
因调用 from 构造函数并不依赖于一个已有的实例对象,所以函数是这样声明的 Cartesian3.fromRadians,而不是 Cartesian3.prototype.fromRadians.
to 函数
函数名称以 to 开头,返回一个新类型的对象,如:
Cartesian3.prototype.toString = function () {
return "(" + this.x + ", " + this.y + ", " + this.z + ")";
};
基础类中少使用原型
🎨 在基础的数学运算类中,比如: Cartesian3、 Quaternion、 Matrix4、 和 JulianDate 很少使用原型。例如 Cartesian3 没有在原型上添加 add 函数,如下:
var v2 = v0.add(v1, result);
不是上面那种方式,而是下面这种:
var v2 = Cartesian3.add(v0, v1, result);
下方这些函数除外:
cloneequalsequalsEpsilontoString
这些原型上的函数一般通过调用对应的静态函数实现,如:
Cartesian3.equals = function (left, right) {
return (
left === right ||
(defined(left) &&
defined(right) &&
left.x === right.x &&
left.y === right.y &&
left.z === right.z)
);
};
Cartesian3.prototype.equals = function (right) {
return Cartesian3.equals(this, right);
};
使用原型的优势在于可以实现多态。
静态常量
使用 Object.freeze 来创建类的静态常量:
Cartesian3.ZERO = Object.freeze(new Cartesian3(0.0, 0.0, 0.0));
私有函数
与私有属性一样,私有函数的名称以 _ 开头。为了实现更好的封装性,文件作用域内的函数把 this 作为第一个参数,如:
Cesium3DTileset.prototype.update = function(frameState) {
this._processTiles(frameState);
// ...
};
Cesium3DTileset.prototype._processTiles(tileset, frameState) {
var tiles = this._processingQueue;
var length = tiles.length;
for (var i = length - 1; i >= 0; --i) {
tiles[i].process(tileset, frameState);
}
}
更好的写法:
Cesium3DTileset.prototype.update = function (frameState) {
processTiles(this, frameState);
// ...
};
function processTiles(tileset, frameState) {
var tiles = tileset._processingQueue;
var length = tiles.length;
for (var i = length - 1; i >= 0; --i) {
tiles[i].process(tileset, frameState);
}
}
属性的Getter/Setters
公共属性能进行读写操作,并不需要额外的处理,只需在构造函数中声明即可,如:
function Model(options) {
this.show = defaultValue(options.show, true);
}
只读属性可以借助私有属性并使用 Object.defineProperties 函数来创建,如下所示:
function Cesium3DTileset(options) {
this._url = options.url;
}
Object.defineProperties(Cesium3DTileset.prototype, {
url: {
get: function () {
return this._url;
},
},
});
Getters可以执行任何计算来返回值,但不能是一些性能消耗高的计算。
Setters也可以在给私有变量赋值前进行一些计算,可通过一个标记来推迟计算,如:
Object.defineProperties(UniformState.prototype, {
viewport: {
get: function () {
return this._viewport;
},
set: function (viewport) {
if (!BoundingRectangle.equals(viewport, this._viewport)) {
BoundingRectangle.clone(viewport, this._viewport);
var v = this._viewport;
var vc = this._viewportCartesian4;
vc.x = v.x;
vc.y = v.y;
vc.z = v.width;
vc.w = v.height;
this._viewportDirty = true;
}
},
},
});
- 🚤 通过调用 getter/setter 函数比直接进行属性访问要慢些,所以类的内部函数可以在适当的时候直接访问对应的私有属性。
隐藏属性
当使用getter/setter开销过大或需要引用类型时,如,将属性作为 result 参数传递,这样它的属性就是可修改的,可以考虑将公有属性合并到一个私有隐藏属性上。(就是说,当公共属性的值为一个引用类型,而类的内部需要此值,为防止修改,就在类的内部存放一个隐藏的私有属性。)如:
function Model(options) {
this.modelMatrix = Matrix4.clone(
defaultValue(options.modelMatrix, Matrix4.IDENTITY)
);
this._modelMatrix = Matrix4.clone(this.modelMatrix);
}
Model.prototype.update = function (frameState) {
if (!Matrix4.equals(this._modelMatrix, this.modelMatrix)) {
// clone() is a deep copy. Not this._modelMatrix = this._modelMatrix
Matrix4.clone(this.modelMatrix, this._modelMatrix);
// Do slow operations that need to happen when the model matrix changes
}
};
将构造函数放到文件的顶部
即使需要依赖帮助函数的 变量提升 ,将构造函数置于文件顶部也是更好的选择。如: Cesium3DTileset.js,
function loadTileset(tileset, tilesJson, done) {
// ...
}
function Cesium3DTileset(options) {
// ...
loadTileset(this, options.url, function (data) {
// ...
});
}
更好的写法:
function Cesium3DTileset(options) {
// ...
loadTileset(this, options.url, function (data) {
// ...
});
}
function loadTileset(tileset, tilesJson, done) {
// ...
}
即使依赖 loadTileset 的隐式变量提升。
设计
- 🏠 仅在确实对用户使用有益时,才可为Cesium API添加类或函数;避免在公共API中有实现的细节。不断膨胀的API会有更多的文档需要编写且让Cesium变得庞杂,不易学习。
- 🎨 新添加的类或函数须放到对应的文件夹中。从下到上依次是:
Source/Core- 数值计算。 纯数学运算,如:Cartesian3。 纯几何数据,如:CylinderGeometry。一些基础算法,如:mergeSort。 请求资源资源类帮助函数,如:loadArrayBuffer。Source/Renderer- WebGL抽象,如:ShaderProgram和WebGL特有的工具,如:ShaderCache。这个文件夹内的东西并不会作为Ceisum的公开API。Source/Scene- 图形引擎,包括primitives,如:Model。这个文件夹中的代码常常与Renderer有依赖关系。Source/DataSources- Entity API, 如:Entity, 和 数据源,如:CzmlDataSource。Source/Widgets- 小部件。如:Viewer。
将文件放到哪个文件夹下通常是明显的,但有时会纠结是放到 Core 还是其它文件夹下。当是纯数学运算或是一个Cesium常使用的工具型函数或类就放到 Core 文件夹下,如:Matrix4 就放在Core 文件夹下,因为许多地方都会用到4x4矩阵。另外, BoundingSphereState 放在 DataSources 文件夹下,因为它属于数据源。

模块应只引用同层级或更低层级的模块。例如:Scene 中的模块可以使用Scene, Renderer, 和 Core中的模块,不使用DataSources 或 Widgets中的模块。
- WebGL资源需要明确的被删除,所以类中包含有
destroy和isDestroyed函数,例如:
var primitive = new Primitive(/* ... */);
expect(content.isDestroyed()).toEqual(false);
primitive.destroy();
expect(content.isDestroyed()).toEqual(true);
destroy 函数借助Cesium的 destroyObject 来实现, 如:
SkyBox.prototype.destroy = function () {
this._vertexArray = this._vertexArray && this._vertexArray.destroy();
return destroyObject(this);
};
- 应该由你来调用
destroy销毁对象,而不是由类内部来做。
弃用和破坏性更改
随着版本的不断迭代,我们努力保持公开API的稳定性与可维护性并朝着正确的方向发展,所以我们谨慎的弃用、移除和替换一部分公开API。
一个 @private API 的调整不会对使用者造成困扰,所以对它的调整是不会有顾虑的。
一个 @experimental API 在将来的Cesium版本中可能会没有顾虑的进行破坏性更改。比如,添加一些实验性特性。
一个公开的API,应先弃用后移除。具体流程如下:
- 充分考虑好需弃用的API及其将在哪个版本被移除。大多数被弃用的API在未来的1-3个版本中被移除,当然是可协商的。
- 使用
deprecationWarning来提醒使用者,这个API已被弃用及其可使用的替换方案。如:
function Foo() {
deprecationWarning(
"Foo",
"Foo was deprecated in Cesium 1.01. It will be removed in 1.03. Use newFoo instead."
);
// ...
}
- 在API文档中添加
@deprecated标志。 - 移除Cesium中所有已弃用的API,单元测试中用于测试弃用API的部分除外。
- 在
CHANGES.md文件的Deprecated章节,阐明被弃用的API及将会在哪个版本被移除。 - 为移除的API创建带有
remove in [version]标签的issue。
第三方库
🏠 Cesium对使用第三方库是谨慎的。如果你想添加一个新的第三方库,请先在 Cesium community forum (example discussion)中发起一个讨论贴。此第三方库应该符合以下要求:
- 有与Cesium兼容的开源协议,如: MIT, BSD, 或 Apache 2.0。
- 提供有Cesium真实需要且没有时间或没有对应的专业知识去开发的能力。
- 轻量级,可测试,在维护且广泛使用。
- 不对全局命名空间造成污染。
- 提供足够的价值来证明添加一个第三方库的必要性,且在使用者对其进行评估时说明有没有可能对Cesium造成一些影响(通常,更少的第三方库是更好的选择)。
小部件
在Cesium的Viewer中包含有一些符合标准的小部件:动画、时间线控制、图层选择器、地理编码器。这些小部件均使用Knockout) 构建,为了能自动刷新UI。Knockout使用MVVM设计模式,你可以从 Understanding MVVM - A Guide For JavaScript Developers 中了解这种设计模式。
可以从Knockout官网的 Get started 章节学习如何使用。他们还有一个很棒的手把手教学的交互教程: interactive tutorial。
为了简化knockout语法,Cesium也有使用 Knockout-ES5 插件。让我们可以像使用其它变量一样使用knockout的observables。 调用 knockout.track 创建observables。 这有一个例子:BaseLayerPickerViewModel, 它为 tooltip, showInstructions 和 _touch 属性创建了observables。
knockout.track(this, ["tooltip", "showInstructions", "_touch"]);
Knockout subscriptions
只用在你使用标准绑定不能实现时才使用knockout的订阅(subscription)。例如, Viewer 订阅了 FullscreenButtonViewModel.isFullscreenEnabled,因为当这个值改变的时候也需要改变时间线小部件。这并不能通过绑定实现,因为 FullscreenButtonViewModel 影响的值并不包含在时间线小部件中。
Cesium有一个 subscribeAndEvaluate 帮助函数,能够订阅 knockout 中 observable 的更改。
当使用订阅时,当视图模型不在使用时一定要记得去 dispose the subscription。否则监听器将在observable的整个生明周期内一直监听者。
fullscreenSubscription = subscribeAndEvaluate(fullscreenButton.viewModel, 'isFullscreenEnabled', function(isFullscreenEnabled) { ... });
// ...then later...
fullscreenSubscription.dispose();
GLSL
命名
- GLSL文件以
.glsl结尾,且放在 Shaders 文件夹中。 - 顶点着色器文件名有一个
VS后缀; 片段着色器有一个FS后缀。例如:BillboardCollectionVS.glsl和BillboardCollectionFS.glsl. - 一般来说,标识符,如函数名和变量名,以
驼峰方式命名。 - Cesium内置的标识符以
czm_开头,如:czm_material。拥有相同名称的文件不需要czm_前缀,如:material.glsl。 - Varyings变量以
v_开头,如:
varying vec2 v_textureCoordinates;
- Uniforms变量以
u_开头,如:
uniform sampler2D u_atlas;
EC后缀表示这个点或向量在视点坐标中,如:
varying vec3 v_positionEC;
// ...
v_positionEC = (czm_modelViewRelativeToEye * p).xyz;
- 当 GPU RTE 被使用时,
High和Low后缀分别表示大比特位和小比特位。如:
attribute vec3 position3DHigh;
attribute vec3 position3DLow;
- 2D 纹理坐标是
s和t, 而不是u和v。 如:
attribute vec2 st;
排版
- 与JavaScript的排版规则一致,除了
{新起一行。如:
struct czm_ray
{
vec3 origin;
vec3 direction;
};
性能
- 🚤 避免昂贵的计算开销。如:最好将值在JavaScript中计算好然后传给uniform变量,而不是在每个顶点中重复计算。同样的,尽可能在顶点着色器中计算值,而不是在片段着色器中。
- 🚤 谨慎使用
discard,因为它禁止了 early-z GPU 优化.
资源
See Section 4.1 to 4.3 of Getting Serious with JavaScript by Cesium contributors Matthew Amato and Kevin Ring in WebGL Insights for deeper coverage of modules and performance.
Watch From Console to Chrome by Lilli Thompson for even deeper performance coverage.
最后
- 原文地址:https://github.com/CesiumGS/cesium/blob/master/Documentation/Contributors/CodingGuide/README.md
- 转载请指明出处
- 不当之处,欢迎交流指正。
浙公网安备 33010602011771号