排序算法JS版本

简介

评价一个算法的好坏主要有以下几个特征

  1. 稳定性:

    稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;

    不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;

  2. 复杂度

    时间复杂度:算法执行所耗费的时间

    空间复杂度:运行算法所需的内存的大小

    详细见https://blog.csdn.net/booirror/article/details/7707551/

    排序算法还分内排序和外排序,内排序是所有操作都在内存上的排序,外排序指的是由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行

冒泡排序

算法描述

1.相邻两个元素比较,小的到前面,大的到后面

2.从开始第一对到结尾的最后一对,这样确认最后的元素应该会是最大的数(冒泡)

3.重复冒泡,确认第二大的元素,第三大的元素。。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
function bubbleSort(arr) {
var len = arr.length;
for (var i = 0; i < len; i++) {
for (var j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j+1]) { //相邻元素两两对比
var temp = arr[j+1]; //元素交换
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}

改进冒泡排序: 设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
function bubbleSort(arr) {
var len = arr.length;
for (var i = 0; i < len; i++) {
for (var j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) { //相邻元素两两对比
var temp = arr[j + 1]; //元素交换
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}

平均情况:T(n) = O(n2)

#选择排序

表现最稳定的算法之一,无论什么数据进去都是O(n²)的时间复杂度。所以适合数据规模较小的排序

工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

<1>.初始状态:无序区为R[1..n],有序区为空;

<2>.第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;

<3>.n-1趟结束,数组有序化了。

##算法分析

最佳情况:T(n) = O(n2)

最差情况:T(n) = O(n2)

平均情况:T(n) = O(n2)

#插入排序

通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

<1>.从第一个元素开始,该元素可以认为已经被排序;

<2>.取出下一个元素,在已经排序的元素序列中从后向前扫描;

<3>.如果该元素(已排序)大于新元素,将该元素移到下一位置;

<4>.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;

<5>.将新元素插入到该位置后;

<6>.重复步骤2~5。

因为有已排序序列,所以在寻找未排序数据的位置的时候可以辅助使用二分查找法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function binaryInsertionSort(array) {
if (Object.prototype.toString.call(array).slice(8, -1) === 'Array') {
console.time('二分插入排序耗时:');

for (var i = 1; i < array.length; i++) {
var key = array[i], left = 0, right = i - 1;
while (left <= right) {
var middle = parseInt((left + right) / 2);
if (key < array[middle]) {
right = middle - 1;
} else {
left = middle + 1;
}
}
for (var j = i - 1; j >= left; j--) {
array[j + 1] = array[j];
}
array[left] = key;
}
console.timeEnd('二分插入排序耗时:');

return array;
} else {
return 'array is not an Array!';
}
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(binaryInsertionSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]

算法分析

最佳情况:T(n) = O(n)

最差情况:T(n) = O(n2)

平均情况:T(n) = O(n2)

希尔排序

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:

<1>. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;

<2>.按增量序列个数k,对序列进行k 趟排序;

<3>.每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function shellSort(arr) {
var len = arr.length,
temp,
gap = 1;
console.time('希尔排序耗时:');
while(gap < len/5) { //动态定义间隔序列
gap =gap*5+1;
}
for (gap; gap > 0; gap = Math.floor(gap/5)) {
for (var i = gap; i < len; i++) {
temp = arr[i];
for (var j = i-gap; j >= 0 && arr[j] > temp; j-=gap) {
arr[j+gap] = arr[j];
}
arr[j+gap] = temp;
}
}
console.timeEnd('希尔排序耗时:');
return arr;
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(shellSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]

算法分析

最佳情况:T(n) = O(nlog2 n)

最坏情况:T(n) = O(nlog2 n)

平均情况:T(n) =O(nlog n)

归并排序

和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(n log n)的时间复杂度。代价是需要额外的内存空间。

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

<1>.把长度为n的输入序列分成两个长度为n/2的子序列;

<2>.对这两个子序列分别采用归并排序;

<3>.将两个排序好的子序列合并成一个最终的排序序列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// shift() 方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。
function mergeSort(arr) { //采用自上而下的递归方法
var len = arr.length;
if(len < 2) {
return arr;
}
var middle = Math.floor(len / 2),
left = arr.slice(0, middle),
right = arr.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}

function merge(left, right)
{
var result = [];
console.time('归并排序耗时');
while (left.length && right.length) {
if (left[0] <= right[0]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}

while (left.length)
result.push(left.shift());

while (right.length)
result.push(right.shift());
console.timeEnd('归并排序耗时');
return result;
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(mergeSort(arr));

##算法分析

  • 最佳情况:T(n) = O(n)
  • 最差情况:T(n) = O(nlogn)
  • 平均情况:T(n) = O(nlogn)

快速排序

处理大数据最快的排序算法之一

基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

<1>.从数列中挑出一个元素,称为 “基准”(pivot);

<2>.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

<3>.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

游戏开发协议-Protobuf

简介

Protocol Buffers就是一种google定义的结构化数据格式,用于数据的序列化和反序列化。由于它直接对二进制源数据进行操作,所以它相对于xml来说,足够的小,快以及简单,而且又与语言、平台无关,所以兼容性也有不错的表现。目前很适合做数据存储或 网络通讯间的数据传输。

操作

  1. 首先需要激活protoc命令,使用brew install protobuf安装

  2. 定义.proto文件“login.proto”

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    syntax = "proto3";

    package msg;



    option java_package = "com.example.msg";

    option java_outer_classname = "LoginMsg";



    message Login{

    string username = 1;

    int32 pw = 2;

    }
  3. 编译protoc -I=. --java_out=. login.proto

    生成了LoginMsg.java

  4. 对LoginMsg进行操作

    通过.proto定义生成的LoginMsg.java,已经整合了对LoginMsg的序列化和反序列化相关代码,我们对login这个消息的reader和writer时只需要通过对该class进行操作即可。比如要把loginMsg写入到流里面发送出去,只需要对loginMsg进行赋值然后writer,对象就被序列化为二进制数据写出,或者接收端读取LoginMsg时,调用其ParserbyReader,就可以基于二进制流反序列化为LoginMsg对象。

    write:

    read:

    只需要对上述的stream改造为为socket就可以基于tcp进行消息传输了。

    LoginMsg内容

    Login

    消息结构对象的主体,主要存储数据,同时继承GeneratedMessageV3,内部封装对象的序列化和反序列化,writeTo序列化,paser反序列化。

    LoginOrBuilder

    接口,用来连接Login和Builder,提供类型信息以及对外提供field get方法。

    Builder

    消息对象构建器,对外封装field set方法。

    Descriptor

    消息对象元数据的描述信息,一般用不到,如果你有动态解析的需求可以通过此来处理

    Parser

    解析器,为消息反序列化提供服务

结构关系如下:

MessageLite/Message接口是所有message的抽象接口,message可以基于Parser从字节流数据中构建对象,也可以通过Builder创建的对象序列化后写入字节流数据到IO管道,MessageLite和Message内部都定义了自己的Builder类,继承自MessageLiteOrBuilder以及MessageOrBuiler,并定义了MessageLite/Message和它们各自Builder类的共同接口。

调用时序

。。。

内容搬运https://www.jianshu.com/p/9cb9fb05431a

https://www.jianshu.com/p/ec39f79c0412

JavaScript版本

1
2
3
4
5
6
7
8
syntax = "proto3";
message Login{

string username = 1;

int32 pw = 2;

}

执行protoc -I=. --js_out=. login.proto即可以产生js版本的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
/**

\* @fileoverview

\* @enhanceable

\* @suppress {messageConventions} JS Compiler reports an error if a variable or

\* field starts with 'MSG_' and isn't a translatable message.

\* @public

*/

// GENERATED CODE -- DO NOT EDIT!



goog.provide('proto.Login');



goog.require('jspb.BinaryReader');

goog.require('jspb.BinaryWriter');

goog.require('jspb.Message');





/**

\* Generated by JsPbCodeGenerator.

\* @param {Array=} opt_data Optional initial data array, typically from a

\* server response, or constructed directly in Javascript. The array is used

\* in place and becomes part of the constructed object. It is not cloned.

\* If no data is provided, the constructed object will be empty, but still

\* valid.

\* @extends {jspb.Message}

\* @constructor

*/

proto.Login = function(opt_data) {

jspb.Message.initialize(this, opt_data, 0, -1, null, null);

};

goog.inherits(proto.Login, jspb.Message);

if (goog.DEBUG && !COMPILED) {

proto.Login.displayName = 'proto.Login';

}





if (jspb.Message.GENERATE_TO_OBJECT) {

/**

\* Creates an object representation of this proto suitable for use in Soy templates.

\* Field names that are reserved in JavaScript and will be renamed to pb_name.

\* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.

\* For the list of reserved names please see:

\* com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.

\* @param {boolean=} opt_includeInstance Whether to include the JSPB instance

\* for transitional soy proto support: http://goto/soy-param-migration

\* @return {!Object}

*/

proto.Login.prototype.toObject = function(opt_includeInstance) {

return proto.Login.toObject(opt_includeInstance, this);

};





/**

\* Static version of the {@see toObject} method.

\* @param {boolean|undefined} includeInstance Whether to include the JSPB

\* instance for transitional soy proto support:

\* http://goto/soy-param-migration

\* @param {!proto.Login} msg The msg instance to transform.

\* @return {!Object}

\* @suppress {unusedLocalVariables} f is only used for nested messages

*/

proto.Login.toObject = function(includeInstance, msg) {

var f, obj = {

username: jspb.Message.getFieldWithDefault(msg, 1, ""),

pw: jspb.Message.getFieldWithDefault(msg, 2, 0)

};



if (includeInstance) {

obj.$jspbMessageInstance = msg;

}

return obj;

};

}





/**

\* Deserializes binary data (in protobuf wire format).

\* @param {jspb.ByteSource} bytes The bytes to deserialize.

\* @return {!proto.Login}

*/

proto.Login.deserializeBinary = function(bytes) {

var reader = new jspb.BinaryReader(bytes);

var msg = new proto.Login;

return proto.Login.deserializeBinaryFromReader(msg, reader);

};





/**

\* Deserializes binary data (in protobuf wire format) from the

\* given reader into the given message object.

\* @param {!proto.Login} msg The message object to deserialize into.

\* @param {!jspb.BinaryReader} reader The BinaryReader to use.

\* @return {!proto.Login}

*/

proto.Login.deserializeBinaryFromReader = function(msg, reader) {

while (reader.nextField()) {

if (reader.isEndGroup()) {

break;

}

var field = reader.getFieldNumber();

switch (field) {

case 1:

var value = /** @type {string} */ (reader.readString());

msg.setUsername(value);

break;

case 2:

var value = /** @type {number} */ (reader.readInt32());

msg.setPw(value);

break;

default:

reader.skipField();

break;

}

}

return msg;

};





/**

\* Serializes the message to binary data (in protobuf wire format).

\* @return {!Uint8Array}

*/

proto.Login.prototype.serializeBinary = function() {

var writer = new jspb.BinaryWriter();

proto.Login.serializeBinaryToWriter(this, writer);

return writer.getResultBuffer();

};





/**

\* Serializes the given message to binary data (in protobuf wire

\* format), writing to the given BinaryWriter.

\* @param {!proto.Login} message

\* @param {!jspb.BinaryWriter} writer

\* @suppress {unusedLocalVariables} f is only used for nested messages

*/

proto.Login.serializeBinaryToWriter = function(message, writer) {

var f = undefined;

f = message.getUsername();

if (f.length > 0) {

writer.writeString(

1,

f

);

}

f = message.getPw();

if (f !== 0) {

writer.writeInt32(

2,

f

);

}

};





/**

\* optional string username = 1;

\* @return {string}

*/

proto.Login.prototype.getUsername = function() {

return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));

};





/** @param {string} value */

proto.Login.prototype.setUsername = function(value) {

jspb.Message.setProto3StringField(this, 1, value);

};





/**

\* optional int32 pw = 2;

\* @return {number}

*/

proto.Login.prototype.getPw = function() {

return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0));

};





/** @param {number} value */

proto.Login.prototype.setPw = function(value) {

jspb.Message.setProto3IntField(this, 2, value);

};

代码结构要简单的多,主要调用的是jspb库。

很容易看出来,fieldNumber很重要,所以在前后端更新协议的时候,往后面加,而不是在中间插

message的二进制结构

每个消息字段读取的时候,都会先调用一次readTag或者writeTag。tag等于就是这个value信息的描述或者定义,告知解析器当前fields是什么类型字段,以及读取的顺序,有了这个信息,解析器就知道一个field在流中的开始位置和结束位置,如此一个field解码成功,并且与字段顺序无关。

tag的构成

(fieldNumber << 3) | wireType

Protobuf的优点

fieldNumber 为每个field定义一个编号,其一保证不重复,其二保证其在流中的位置。如若当前数据流中有某个字段,而解析方没有相关的解析代码,解析放会直接skip 这个field,而且读数据的position也会后移,保证后续读取不出问题。

cautions

proto文件要使用UTF-8

Cocos2d-x绘制系统

旧的绘制系统

每个元素的绘制逻辑均分布于每个元素内部的draw方法中,并且紧密依赖于UI树的遍历。不易拓展优化。eg:依赖UI树的遍历顺序导致无法在多个层级之间调整绘制顺序,各个绘制逻辑分布在每个元素内部不利于针对绘制进行优化(如自动批绘制)

新的绘制系统

新的绘制系统将绘制部分从UI树的遍历中分离出来

特点

  1. 每个UI元素的类型更多的是根据它在应用程序中的特征而不是绘制方式不同划分的(多个不同类型的UI元素可能拥有相同的绘制方式)之前的每个UI元素拥有自己的绘制逻辑
  2. 采用应用程序级别的视口裁剪。即一个UI元素在场景中的坐标位于视窗区域之外时,就不会发送绘制命令到绘制栈上,避免了OpenGL在图元装配阶段的工作
  3. 采用自动批绘制技术:如果在一个场景中有很多的元素都使用同一张纹理,同一个着色器程序,理论上可以只调用一次绘制命令。自动批绘制需要相关的绘制命令在执行顺序上相邻。可以减少OpenGL的Draw Calls
  4. 更简单的实现绘制的自定义

流程

生成绘制命令

通过UI树的遍历,给每个元素生成一个绘制命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
if (_texture == nullptr)
{
return;
}

#if CC_USE_CULLING
// Don't calculate the culling if the transform was not updated
auto visitingCamera = Camera::getVisitingCamera();
auto defaultCamera = Camera::getDefaultCamera();
if (visitingCamera == nullptr) {
_insideBounds = true;
}
else if (visitingCamera == defaultCamera) {
_insideBounds = ((flags & FLAGS_TRANSFORM_DIRTY) || visitingCamera->isViewProjectionUpdated()) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;
}
else
{
// XXX: this always return true since
_insideBounds = renderer->checkVisibility(transform, _contentSize);
}

if(_insideBounds)
#endif
{
_trianglesCommand.init(_globalZOrder,
_texture,
getGLProgramState(),
_blendFunc,
_polyInfo.triangles,
transform,
flags);

renderer->addCommand(&_trianglesCommand);

#if CC_SPRITE_DEBUG_DRAW
_debugDrawNode->clear();
auto count = _polyInfo.triangles.indexCount/3;
auto indices = _polyInfo.triangles.indices;
auto verts = _polyInfo.triangles.verts;
for(ssize_t i = 0; i < count; i++)
{
//draw 3 lines
Vec3 from =verts[indices[i*3]].vertices;
Vec3 to = verts[indices[i*3+1]].vertices;
_debugDrawNode->drawLine(Vec2(from.x, from.y), Vec2(to.x,to.y), Color4F::WHITE);

from =verts[indices[i*3+1]].vertices;
to = verts[indices[i*3+2]].vertices;
_debugDrawNode->drawLine(Vec2(from.x, from.y), Vec2(to.x,to.y), Color4F::WHITE);

from =verts[indices[i*3+2]].vertices;
to = verts[indices[i*3]].vertices;
_debugDrawNode->drawLine(Vec2(from.x, from.y), Vec2(to.x,to.y), Color4F::WHITE);
}
#endif //CC_SPRITE_DEBUG_DRAW
}
}

RenderCommand表示一个绘制类型,定义了怎样绘制一个UI元素。一般情况下每个UI元素会关联0个过着一个RenderCommand并在draw方法中将会致命了发送给renderer。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class CC_DLL RenderCommand

{

public:

/**Enum the type of render command. */

enum class Type

{

/** Reserved type.*/

UNKNOWN_COMMAND,

/** Quad command, used for draw quad.*/

QUAD_COMMAND,

/**Custom command, used for calling callback for rendering.*/

CUSTOM_COMMAND,

/**Batch command, used for draw batches in texture atlas.*/

BATCH_COMMAND,

/**Group command, which can group command in a tree hierarchy.*/

GROUP_COMMAND,

/**Mesh command, used to draw 3D meshes.*/

MESH_COMMAND,

/**Primitive command, used to draw primitives such as lines, points and triangles.*/

PRIMITIVE_COMMAND,

/**Triangles command, used to draw triangles.*/

TRIANGLES_COMMAND

};

GROUP_COMMAND用来包装多个RenderCommand的集合,GroupCommand中的每一个RenderCommand都不会参与全局排序,它可以用来实现子元素裁剪,绘制子元素到纹理

TrianglesCommand继承RenderCommand。class CC_DLL TrianglesCommand : public RenderCommand

http://www.cocos2d-x.org/reference/native-cpp/V3.5/d9/d0d/classcocos2d_1_1_triangles_command.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
TrianglesCommand _trianglesCommand; 

_trianglesCommand.init(_globalZOrder,
_texture,
getGLProgramState(),
_blendFunc,
_polyInfo.triangles,
transform,
flags);
renderer->addCommand(&_trianglesCommand);
//float globalOrder, 全局zorder,决定渲染先后顺序
//GLuint textureID, 纹理id,只有一个,没有纹理混合的功能
//GLProgramState* glProgramState, 着色程序及其参数
//BlendFunc blendType, 颜色混合函数
//const Triangles& triangles,三角形顶点数据,包括顶点坐标、颜色、纹理坐标
//const Mat4& mv, 变换矩阵
//uint32_t flags,标志位

只发送绘制命令,将每个元素的绘制部分从UI树的遍历过程中抽取出来统一处理,这样可以灵活的调整不同UI层级之间的元素绘制顺序,和特点3

绘制命令排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
RenderQueue 

/**

RenderCommand will be divided into Queue Groups.

*/

enum QUEUE_GROUP

{

/**Objects with globalZ smaller than 0.*/

GLOBALZ_NEG = 0,

/**Opaque 3D objects with 0 globalZ.*/

OPAQUE_3D = 1,

/**Transparent 3D objects with 0 globalZ.*/

TRANSPARENT_3D = 2,

/**2D objects with 0 globalZ.*/

GLOBALZ_ZERO = 3,

/**Objects with globalZ bigger than 0.*/

GLOBALZ_POS = 4,

QUEUE_COUNT = 5,

};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
void Renderer::render()
{
//Uncomment this once everything is rendered by new renderer
//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

//TODO: setup camera or MVP
_isRendering = true;

if (_glViewAssigned)
{
//Process render commands
//1. Sort render commands based on ID
for (auto &renderqueue : _renderGroups)
{
renderqueue.sort();
}
visitRenderQueue(_renderGroups[0]);
}
clean();
_isRendering = false;
}



void Renderer::visitRenderQueue(RenderQueue& queue)

{

queue.saveRenderState();



//

//Process Global-Z < 0 Objects

//

const auto& zNegQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_NEG);

if (zNegQueue.size() > 0)

{

if(_isDepthTestFor2D)

{

glEnable(GL_DEPTH_TEST);

glDepthMask(true);

glEnable(GL_BLEND);

RenderState::StateBlock::_defaultState->setDepthTest(true);

RenderState::StateBlock::_defaultState->setDepthWrite(true);

RenderState::StateBlock::_defaultState->setBlend(true);

}

else

{

glDisable(GL_DEPTH_TEST);

glDepthMask(false);

glEnable(GL_BLEND);

RenderState::StateBlock::_defaultState->setDepthTest(false);

RenderState::StateBlock::_defaultState->setDepthWrite(false);

RenderState::StateBlock::_defaultState->setBlend(true);

}

glDisable(GL_CULL_FACE);

RenderState::StateBlock::_defaultState->setCullFace(false);



for (const auto& zNegNext : zNegQueue)

{

processRenderCommand(zNegNext);

}

flush();

}



//

//Process Opaque Object

//

const auto& opaqueQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::OPAQUE_3D);

if (opaqueQueue.size() > 0)

{

//Clear depth to achieve layered rendering

glEnable(GL_DEPTH_TEST);

glDepthMask(true);

glDisable(GL_BLEND);

glEnable(GL_CULL_FACE);

RenderState::StateBlock::_defaultState->setDepthTest(true);

RenderState::StateBlock::_defaultState->setDepthWrite(true);

RenderState::StateBlock::_defaultState->setBlend(false);

RenderState::StateBlock::_defaultState->setCullFace(true);

for (const auto& opaqueNext : opaqueQueue)

{

processRenderCommand(opaqueNext);

}

flush();

}



//

//Process 3D Transparent object

//

const auto& transQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::TRANSPARENT_3D);

if (transQueue.size() > 0)

{

glEnable(GL_DEPTH_TEST);

glDepthMask(false);

glEnable(GL_BLEND);

glEnable(GL_CULL_FACE);

RenderState::StateBlock::_defaultState->setDepthTest(true);

RenderState::StateBlock::_defaultState->setDepthWrite(false);

RenderState::StateBlock::_defaultState->setBlend(true);

RenderState::StateBlock::_defaultState->setCullFace(true);

for (const auto& transNext : transQueue)

{

processRenderCommand(transNext);

}

flush();

}



//

//Process Global-Z = 0 Queue

//

const auto& zZeroQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_ZERO);

if (zZeroQueue.size() > 0)

{

if(_isDepthTestFor2D)

{

glEnable(GL_DEPTH_TEST);

glDepthMask(true);

glEnable(GL_BLEND);

RenderState::StateBlock::_defaultState->setDepthTest(true);

RenderState::StateBlock::_defaultState->setDepthWrite(true);

RenderState::StateBlock::_defaultState->setBlend(true);

}

else

{

glDisable(GL_DEPTH_TEST);

glDepthMask(false);

glEnable(GL_BLEND);

RenderState::StateBlock::_defaultState->setDepthTest(false);

RenderState::StateBlock::_defaultState->setDepthWrite(false);

RenderState::StateBlock::_defaultState->setBlend(true);

}

glDisable(GL_CULL_FACE);

RenderState::StateBlock::_defaultState->setCullFace(false);



for (const auto& zZeroNext : zZeroQueue)

{

processRenderCommand(zZeroNext);

}

flush();

}



//

//Process Global-Z > 0 Queue

//

const auto& zPosQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_POS);

if (zPosQueue.size() > 0)

{

if(_isDepthTestFor2D)

{

glEnable(GL_DEPTH_TEST);

glDepthMask(true);

glEnable(GL_BLEND);



RenderState::StateBlock::_defaultState->setDepthTest(true);

RenderState::StateBlock::_defaultState->setDepthWrite(true);

RenderState::StateBlock::_defaultState->setBlend(true);

}

else

{

glDisable(GL_DEPTH_TEST);

glDepthMask(false);

glEnable(GL_BLEND);



RenderState::StateBlock::_defaultState->setDepthTest(false);

RenderState::StateBlock::_defaultState->setDepthWrite(false);

RenderState::StateBlock::_defaultState->setBlend(true);

}

glDisable(GL_CULL_FACE);

RenderState::StateBlock::_defaultState->setCullFace(false);



for (const auto& zPosNext : zPosQueue)

{

processRenderCommand(zPosNext);

}

flush();

}



queue.restoreRenderState();

}

在UI全部遍历完成之后,执行命令之前,对栈上的绘制命令进行排序,然后按照新的顺序执行它们

绘制顺序首先由globalZOrder决定,然后才是按照元素的遍历顺序

Render实际上维护着一个RenderQueue的数组,每个RenderQueue对应一组RenderCommand或者一个GroupCommand,这些RenderQueue不是简单的线性关系,而是通过GroupCommand构成的树状的关系

####执行绘制

对于一般的RenderCommand按顺序执行,对于相邻使用相同纹理的QuadCommand,将会组合成一个。也就是自动批绘制

GroupCommand

GroupCommand通常不包括具体的GL绘制命令,它只指向一个RenderQueue。当渲染系统绘制一个GroupCommand时,它将找到对应的RenderQueue,然后执行其中的RenderCommand,

把一组RenderCommand加入GroupCommand指向的RenderQueue中,则可以实现对这组RenderCommand的独立绘制,执行顺序不会受其它RenderCommand的影响

Cocos2dx-JS学习01

1
2
3
4
5
6
<body>
<script src="res/loading.js"></script>
<canvas id="gameCanvas" width="480" height="720"></canvas>
<script src="frameworks/cocos2d-html5/CCBoot.js"></script>
<script cocos src="main.js"></script>
</body>

首先加载的是CCBoot.js文件

CCBoot.js

执行主循环

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Run main loop of director
*/
mainLoop: function () {
if (this._purgeDirectorInNextLoop) {
this._purgeDirectorInNextLoop = false;
this.purgeDirector();
}
else if (!this.invalid) {
this.drawScene();
}
},

_purgeDirectorInNextLoop:下一帧是否需要清除自身,一般在cc.director.end()赋值为true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
* Draw the scene. This method is called every frame. Don't call it manually.
*/
drawScene: function () {
var renderer = cc.renderer;

// calculate "global" dt
this.calculateDeltaTime();

//tick before glClear: issue #533
if (!this._paused) {
this._scheduler.update(this._deltaTime);
cc.eventManager.dispatchEvent(this._eventAfterUpdate);
}


//首先执行调度器中的任务,然后触发cc.director.EVENT_AFTER_UPDATE事件,检查交互事件,有的话就分发处理




/* to avoid flickr, nextScene MUST be here: after tick and before draw.
XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */
if (this._nextScene) {
this.setNextScene();
}
//如果有场景切换,便切换

//渲染当前scene
// draw the scene
if (this._runningScene) {
if (renderer.childrenOrderDirty) {
cc.renderer.clearRenderCommands();
cc.renderer.assignedZ = 0;
this._runningScene._renderCmd._curLevel = 0; //level start from 0;
this._runningScene.visit();
renderer.resetFlag();
}
else if (renderer.transformDirty()) {
renderer.transform();
}
}

renderer.clear();

// draw the notifications node
if (this._notificationNode)
this._notificationNode.visit();
//遍历节点,更新节点的空间转换矩阵,发送指令给渲染器
cc.eventManager.dispatchEvent(this._eventAfterVisit);
cc.g_NumberOfDraws = 0;

renderer.rendering(cc._renderContext);
this._totalFrames++;
//排序,绘制
cc.eventManager.dispatchEvent(this._eventAfterDraw);
cc.eventManager.frameUpdateListeners();
//更新帧率
this._calculateMPF();
},

关于绘制管理

关于内存管理(引用计数法)

调度器

cc.Node有相关API

1
2
3
4
node.scheduleUpdate()
node.update = function (dt) {

}

默认调度器:使用scheduleUpdate,必须重写update函数,dt是时间间隔

自定义调度器:

1
node.schedule(callback,inteval,repeate,delay,key)

回调函数(必须),时间间隔(默认0),重复次数(默认cc.REPEAT_FOREVER),延时时间(默认0),唯一标示符

卸载调度器:

unschedule和unscheduleAllCallbacks

场景管理

使用栈的结构

在退出的相关生命周期函数中,应该将this._super()函数放在最后调用,遵循“先进后出“

A->B

SceneA的onEixtTransitionDidStart方法开始前SceneB的ctor执行,所以不要在ctor中做开销大的工作

精灵

每个精灵一般关联一张纹理(Texture2D对象)

四种方法创建

JS隐式转换

1
2
3
4
5
6
7
var log = console.log.bind(this)
log([1,2,3] == "1,2,3")//true
log([] == "")//true
log("" == '')//true
log("" == 0)//true
log(true == 1)//true
log(null == undefined)//true
1
2
3
4
5
6
7
8
log([] == false)//true
log(![] == false)//true
log([] == false)//true
log(![] == false)//true
log(!'' == true)//true
log(!NaN == true)//true
log(!undefined == true)//true
log(!null == true)//true

![]直接转换为布尔值再取反,转换为布尔值时,空字符串(‘’),NaN,0,null,undefined这几个外返回的都是true.

1
2
3
log(undefined == null) //true undefined和null 比较返回true,二者和其他值比较返回false
log(Number(null)) //0
log(Number(undefined)) //NaN

关于NaN:http://www.w3school.com.cn/js/jsref_nan_number.asp

Cocos2d中的removeFromParent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
void Node::removeFromParent()

{

this->removeFromParentAndCleanup(true);

}

void Node::removeFromParentAndCleanup(bool cleanup)

{

if (_parent != nullptr)

{

_parent->removeChild(this,cleanup);

}

}

/* "remove" logic MUST only be on this method

\* If a class want's to extend the 'removeChild' behavior it only needs

\* to override this method

*/

void Node::removeChild(Node* child, bool cleanup /* = true */)

{

// explicit nil handling

if (_children.empty())

{

return;

}

ssize_t index = _children.getIndex(child);

if( index != CC_INVALID_INDEX )

this->detachChild( child, index, cleanup );

}





ssize_t getIndex(T object) const

{

auto iter = std::find(_data.begin(), _data.end(), object);

if (iter != _data.end())

return iter - _data.begin();

return -1;

}



void Node::detachChild(Node *child, ssize_t childIndex, bool doCleanup)
{
// IMPORTANT:
// -1st do onExit
// -2nd cleanup
if (_running)
{
child->onExitTransitionDidStart();
child->onExit();
}

// If you don't do cleanup, the child's actions will not get removed and the
// its scheduledSelectors_ dict will not get released!
if (doCleanup)
{
child->cleanup();
}

#if CC_ENABLE_GC_FOR_NATIVE_OBJECTS
auto sEngine = ScriptEngineManager::getInstance()->getScriptEngine();
if (sEngine)
{
sEngine->releaseScriptObject(this, child);
}
#endif // CC_ENABLE_GC_FOR_NATIVE_OBJECTS
// set parent nil at the end
child->setParent(nullptr);

_children.erase(childIndex);
}



Node* Node::getChildByName(const std::string& name) const
{
CCASSERT(!name.empty(), "Invalid name");

std::hash<std::string> h;
size_t hash = h(name);

for (const auto& child : _children)
{
// Different strings may have the same hash code, but can use it to compare first for speed
if(child->_hashOfName == hash && child->_name.compare(name) == 0)
return child;
}
return nullptr;
}

需要remove的时候搜索到找到在Vector<Node*> _children; 中的index,然后erase掉

WebSocket

WebSocket与HTTP的异同

HTTP只能由客户端发起,做不到服务器主动推送消息(单向请求)。当服务器有连续的状态变化的时候,客户端想要获知只能采用轮询的方式(效率低,浪费资源)。

当需要在浏览器做一个在线多人游戏的时候,浏览器通过JavaScript启动一个定时器,然后以固定的间隔给服务器发请求,询问服务器有没有新消息。这个机制的缺点一是实时性不够,二是频繁的请求会给服务器带来极大的压力。所以需要一个新的协议来建立无限制的全双工通信

连接

WebSocket利用了HTTP协议来建立连接。握手阶段采用 HTTP 协议

连接必须由浏览器发起,因为请求协议是一个标准的HTTP请求,格式如下:

1
2
3
4
5
6
7
GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13
  1. GET请求的地址不是类似/path/,而是以ws://开头的地址;
  2. 请求头Upgrade: websocketConnection: Upgrade表示这个连接将要被转换为WebSocket连接;
  3. Sec-WebSocket-Key是用于标识这个连接,并非用于加密数据;
  4. Sec-WebSocket-Version指定了WebSocket的协议版本。

浏览器的response如下

1
2
3
4
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string

状态码101的意思是

HTTP状态码

特点

建立在 TCP 协议之上,服务器端的实现比较容易

与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

数据格式比较轻量,性能开销小,通信高效。

可以发送文本,也可以发送二进制数据。

没有同源限制,客户端可以与任意服务器通信

协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

API

WebSocket MDN

简单例子

可以使用WebSocketd简单搭建一个服务器

WebSocketd可以使用brew下载

JS02作用域和执行上下文

#JS使用词法作用域

词法作用域:函数的作用域在函数定义的时候确定

动态作用域:函数的作用域在函数调用的时候才确定

1
2
3
4
5
6
7
8
9
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();

结果是1

接下来看接下来两段代码

1
2
3
4
5
6
7
8
9
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();

结果是local scope

1
2
3
4
5
6
7
8
9
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();

结果是local scope

JavaScript 函数的执行用到了作用域链,这个作用域链是在函数定义的时候创建的。嵌套的函数 f() 定义在这个作用域链里,其中的变量 scope 一定是局部变量,不管何时何地执行函数 f(),这种绑定在执行 f() 时依然有效。

#通过执行上下文来跟踪代码

JS中有两种执行上下文,全局执行上下文和函数执行上下文,执行上下文只有一个,当JavaScript程序开始执行时就已经创建了全局上下文,在每次调用函数时创建一个新的函数执行上下文

使用词法环境跟踪变量的作用域

词法环境(作用域)是JS引擎内部用来跟踪标识符与特定变量之间的映射关系

每个执行上下文都有一个与之关联的词法环境,词法环境中包含了在上下文中定义的标识符的映射表

无论何时创建函数,都会创建一个与之相关联的词法环境,并存储在名为[[Environment]]的内部属性上

JS01原型和原型链

#三个关系

1
2
3
4
function Ninja() {
}
console.log(Ninja) //[Function: Ninja]
console.log(Ninja.prototype) //Ninja {}

方法具有prototype属性,方法的prototype属性是一个对象

1
2
3
4
5
6
7
var ninja = new Ninja()
console.log(ninja.__proto__) //Ninja {}
console.log(ninja.prototype) //undefined
console.log(Ninja.prototype === ninja.__proto__) //true
console.log(Object.getPrototypeOf(ninja) === Ninja.prototype)//true
//Object.setPrototypeOf(a,b)方法将 b 对象设置为 a 对象的原型
console.log(Ninja === Ninja.prototype.constructor) // true

构造函数创建的对象(通过 new 操作符调用函数意味着作为构造器调用)具有__proto__属性,而且Ninja.prototypeninja.__proto__是同一个对象

#实例属性和原型属性

1
2
3
4
5
6
7
8
9
function Ninja() {
}
Ninja.prototype.name = 'tony'
const ninja = new Ninja()
console.log(ninja)
ninja.name = 'lu'
console.log(ninja.name)
delete ninja.name
console.log(ninja.name)

当读取实例的属性时,如果找不到,就会查找与对象原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。

实例中可以查找到的属性就不会查找原型。

实例会隐藏原型中与实例方法重名的方法。

并不是继承

继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。

原型的原型

#JavaScript动态特性的副作用

对象与函数原型之间引用关系是在对象创建时建立的,如果原型在代码过程中被篡改,新创建的对象将引用新的原型,原来旧的对象保持着原有的原型的引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var log = console.log.bind(this)
function Ninja() {
this.swung = true
}
const ninja1 = new Ninja()
Ninja.prototype.swingSword = function () {
return this.swung
}
log(ninja1.__proto__)//Ninja { swingSword: [Function] }
Ninja.prototype = {
pierce : function () {
return true
}
}
log(ninja1.__proto__) //Ninja { swingSword: [Function] }
const ninja2 = new Ninja()
log(ninja2.__proto__) //{ pierce: [Function: pierce] }

函数(function)对象的原型链

在JavaScript中,函数是一个特殊的对象,所有函数都是构造函数Function的实例,所以原型链会有所不同

1
2
3
4
5
6
7
8
9
var log = console.log.bind(this)
function Ninja() {
}
log(Ninja)//[Function: Ninja]
log(typeof Function)//function
log(Ninja.__proto__)//[Function] 对象的__proto__是对象,不可能是方法
log(Ninja.__proto__ === Function.prototype)//true
log(Function.__proto__)//[Function]
log(Function.prototype)//[Function]

#Cautions

Tips

  1. 因为所有实例对象都可以访问 constructor 属性所以可以使用 const ninja2 = new ninja1.constructor()来初始化

  2. instanceof 操作符的实质是:检查操作符右边的函数的原型是否存在于操作符左边的对象的原型链上

##还有很多细节可继续阅读《JavaScript忍者秘籍》(第二版)第七章的内容补充

位操作

异或

异或运算又名半加运算,相同为0,不同为1。

Tips

  1. 一个比特与对它取反的值做异或,结果总是1。因此a^(~a)的结果是1s(一串1)
  2. x&(~0<<i)将x的最左边i位清零
  3. x^0s = x x^1s = ~x

#常见位操作

获取

1
2
3
var getBit = function (num,i) {
return (num&(1<<i))
}

置位

1
2
3
var setBit = function (num,i) {
return (num|(1<<i))
}

清零

1
2
3
4
5
6
7
8
9
10
11
var clearBit = function (num,i) {
return (num&(~(1<<i)))
}
//将num最高位至i位(含)清零
var clearBitsMSBthoughti = function (num, i) {
return num & ((1 << i) - 1)
}
//将i位至0位(含)清零
var clearBitsIthought0 = function (num, i) {
return num (~((1 << i) - 1))
}

更新

1
2
3
4
var updateBit = function (num, i, v) {
var mask = ~(1 << i)
return (num & mask) | (v << i)
}