-
Notifications
You must be signed in to change notification settings - Fork 52
/
Copy pathECS.ts
1101 lines (971 loc) · 34.9 KB
/
ECS.ts
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
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// 重构原则:如无必要,勿增实体。
export module ecs {
export interface IComp {
canRecycle: boolean;
ent: Entity;
reset(): void;
}
export interface CompCtor<T> {
new(): T;
tid: number;
compName: string;
}
/**
* 组件里面只放数据可能在实际写代码的时候比较麻烦。如果是单纯对组件内的数据操作可以在组件里面写方法。
*/
export abstract class Comp implements IComp {
/**
* 组件的类型id,-1表示未给该组件分配id
*/
static tid: number = -1;
static compName: string;
/**
* 拥有该组件的实体
*/
ent!: Entity;
/**
* 是否可回收组件对象,默认情况下都是可回收的。
* 如果该组件对象是由ecs系统外部创建的,则不可回收,需要用户自己手动进行回收。
*/
canRecycle: boolean = true;
/**
* 组件被回收时会调用这个接口。可以在这里重置数据,或者解除引用。
*
* **不要偷懒,除非你能确定并保证组件在复用时,里面的数据是先赋值然后再使用。**
*/
abstract reset(): void;
}
//#region 类型声明
type CompAddOrRemove = (entity: Entity) => void;
export type CompType<T> = CompCtor<T> | number;
//#endregion
//#region 注册组件
/**
* 组件缓存池
*/
let compPools: Map<number, IComp[]> = new Map();
/**
* 组件类型id
*/
let compTid = 0;
/**
* 组件构造函数
*/
let compCtors: (CompCtor<any> | number)[] = [];
/**
* 每个组件的添加和删除的动作都要派送到“关心”它们的group上。goup对当前拥有或者之前(删除前)拥有该组件的实体进行组件规则判断。判断该实体是否满足group
* 所期望的组件组合。
*/
let compAddOrRemove: Map<number, CompAddOrRemove[]> = new Map();
let tags: Map<number, string> = new Map();
/**
* 注册组件到ecs系统中
* @param compName 由于js打包会改变类名,所以这里必须手动传入组件的名称。
* @param canNew 标识是否可以new对象。想继承自Cocos Creator的组件就不能去new,需要写成@ecs.register('name', false)
*/
export function register<T>(compName: string, canNew: boolean = true) {
return function (ctor: CompCtor<T>) {
if (ctor.tid === -1) {
ctor.tid = compTid++;
ctor.compName = compName;
if (canNew) {
compCtors.push(ctor);
compPools.set(ctor.tid, []);
}
else {
compCtors.push(null);
}
compAddOrRemove.set(ctor.tid, []);
}
else {
throw new Error(`重复注册组件: ${compName}.`);
}
}
}
/**
* 添加tag
*
* eg.
* @registerTag()
* class Tag {
* static A: number;
* static B: number
* }
* @returns
*/
export function registerTag() {
return function (_class: any) {
let tid = compTid;
for (let k in _class) {
tid = compTid++;
_class[k] = tid;
compCtors.push(tid);
compPools.set(tid, []);
compAddOrRemove.set(tid, []);
tags.set(tid, k);
}
}
}
//#endregion
//#region context
/**
* 实体对象缓存池
*/
let entityPool: Entity[] = [];
/**
* 通过实体id查找实体对象
*/
let eid2Entity: Map<number, Entity> = new Map();
/**
* 缓存的group
*
* key是组件的筛选规则,一个筛选规则对应一个group
*/
let groups: Map<number, Group> = new Map();
/**
* 实体自增id
*/
let eid = 1;
/**
* 创建实体
*/
export function createEntity<E extends Entity = Entity>(): E {
let entity = entityPool.pop();
if (!entity) {
entity = new Entity();
// @ts-ignore
entity.eid = eid++; // 实体id也是有限的资源
}
eid2Entity.set(entity.eid, entity);
return entity as E;
}
/**
* 创建组件对象
* @param ctor
*/
function createComp<T extends IComp>(ctor: CompCtor<T>): T {
if (!compCtors[ctor.tid]) {
throw Error(`没有找到该组件的构造函数,检查${ctor.compName}是否为不可构造的组件`);
}
let component = compPools.get(ctor.tid)!.pop() || new (compCtors[ctor.tid] as CompCtor<T>);
return component as T;
}
/**
* 指定一个组件创建实体,返回组件对象。
* @param ctor
*/
export function createEntityWithComp<T extends IComp>(obj: T): Entity;
export function createEntityWithComp(ctor: number): Entity;
export function createEntityWithComp<T extends IComp>(ctor: CompType<T>): T;
// export function createEntityWithComp<T extends IComp>(ctor: CompCtor<T>): T;
// export function createEntityWithComp<T extends IComp>(ctor: CompType<T> | T): T | Entity;
export function createEntityWithComp<T extends IComp>(ctor: CompType<T>): T | Entity {
let entity = createEntity();
return entity.add(ctor);
}
/**
* 指定多个组件创建实体,返回实体对象。
* @param ctors
*/
export function createEntityWithComps<E extends Entity = Entity>(...ctors: CompType<IComp>[]): E {
let entity = createEntity();
entity.addComponents(...ctors);
return entity as E;
}
/**
* 销毁实体。
*
* 缓存销毁的实体,下次新建实体时会优先从缓存中拿。
* @param entity
*/
function destroyEntity(entity: Entity) {
if (eid2Entity.has(entity.eid)) {
entityPool.push(entity);
eid2Entity.delete(entity.eid);
}
else {
console.warn('试图销毁不存在的实体!');
}
}
/**
* 创建group,每个group只关心对应组件的添加和删除
* @param matcher 实体筛选器
*/
export function createGroup<E extends Entity = Entity>(matcher: IMatcher): Group<E> {
let group = groups.get(matcher.mid);
if (!group) {
group = new Group(matcher);
groups.set(matcher.mid, group);
let careComponentTypeIds = matcher.indices;
for (let i = 0; i < careComponentTypeIds.length; i++) {
compAddOrRemove.get(careComponentTypeIds[i])!.push(group.onComponentAddOrRemove.bind(group));
}
}
return group as unknown as Group<E>;
}
/**
* 动态查询实体
* @param matcher
* @returns
*/
export function query<E extends Entity = Entity>(matcher: IMatcher): E[] {
let group = groups.get(matcher.mid);
if (!group) {
group = createGroup(matcher);
eid2Entity.forEach(group.onComponentAddOrRemove, group);
}
return group.matchEntities as E[];
}
/**
* 清理所有的实体
*/
export function clear() {
eid2Entity.forEach((entity) => {
entity.destroy();
});
groups.forEach((group) => {
group.clear();
});
compAddOrRemove.forEach(callbackLst => {
callbackLst.length = 0;
});
eid2Entity.clear();
groups.clear();
}
/**
* 实体身上组件有增删操作,广播通知对应的观察者。
* @param entity 实体对象
* @param componentTypeId 组件类型id
*/
function broadcastCompAddOrRemove(entity: Entity, componentTypeId: number) {
let events = compAddOrRemove.get(componentTypeId);
for (let i = events!.length - 1; i >= 0; i--) {
events![i](entity);
}
// 判断是不是删了单例组件
if (tid2comp.has(componentTypeId)) {
tid2comp.delete(componentTypeId);
}
}
/**
* 根据实体id获得实体对象
* @param eid
*/
export function getEntityByEid<E extends Entity = Entity>(eid: number): E {
return eid2Entity.get(eid) as E;
}
/**
* 当前活动中的实体数量
*/
export function activeEntityCount() {
return eid2Entity.size;
}
//#endregion
/**
* 表示只关心这些组件的添加和删除动作。虽然实体可能有这些组件之外的组件,但是它们的添加和删除没有被关注,所以不会存在对关注之外的组件
* 进行添加操作引发Group重复添加实体。
* @param args
*/
export function allOf(...args: CompType<IComp>[]) {
return new Matcher().allOf(...args);
}
/**
* 组件间是或的关系,表示关注拥有任意一个这些组件的实体。
* @param args 组件索引
*/
export function anyOf(...args: CompType<IComp>[]) {
return new Matcher().anyOf(...args);
}
/**
* 表示关注只拥有这些组件的实体
*
* 注意:
* 不是特殊情况不建议使用onlyOf。因为onlyOf会监听所有组件的添加和删除事件。
* @param args 组件索引
*/
export function onlyOf(...args: CompType<IComp>[]) {
return new Matcher().onlyOf(...args);
}
/**
* 不包含指定的任意一个组件
*
* eg.
* ecs.excludeOf(A, B);表示不包含组件A或者组件B
* @param args
*/
export function excludeOf(...args: CompType<IComp>[]) {
return new Matcher().excludeOf(...args);
}
//#region 单例组件
let tid2comp: Map<number, IComp> = new Map();
/**
* 获取单例组件
* @param ctor 组件类
*/
export function getSingleton<T extends IComp>(ctor: CompCtor<T>) {
if (!tid2comp.has(ctor.tid)) {
let comp = createEntityWithComp(ctor) as T;
tid2comp.set(ctor.tid, comp);
}
return tid2comp.get(ctor.tid) as T;
}
/**
* 注册单例。主要用于那些不能手动创建对象的组件
* @param obj
*/
export function addSingleton(obj: IComp) {
let tid = (obj.constructor as CompCtor<IComp>).tid;
if (!tid2comp.has(tid)) {
tid2comp.set(tid, obj);
}
}
//#endregion
class Mask {
private mask: Uint32Array;
private size: number = 0;
constructor() {
let length = Math.ceil(compTid / 31);
this.mask = new Uint32Array(length);
this.size = length;
}
set(num: number) {
// https://stackoverflow.com/questions/34896909/is-it-correct-to-set-bit-31-in-javascript
// this.mask[((num / 32) >>> 0)] |= ((1 << (num % 32)) >>> 0);
this.mask[((num / 31) >>> 0)] |= (1 << (num % 31));
}
delete(num: number) {
this.mask[((num / 31) >>> 0)] &= ~(1 << (num % 31));
}
has(num: number) {
return !!(this.mask[((num / 31) >>> 0)] & (1 << (num % 31)));
}
or(other: Mask) {
for (let i = 0; i < this.size; i++) {
// &操作符最大也只能对2^30进行操作,如果对2^31&2^31会得到负数。当然可以(2^31&2^31) >>> 0,这样多了一步右移操作。
if (this.mask[i] & other.mask[i]) {
return true;
}
}
return false;
}
and(other: Mask) {
for (let i = 0; i < this.size; i++) {
if ((this.mask[i] & other.mask[i]) != this.mask[i]) {
return false;
}
}
return true;
}
clear() {
for (let i = 0; i < this.size; i++) {
this.mask[i] = 0;
}
}
}
export class Entity {
/**
* 实体唯一标识,不要手动修改。
*/
public eid: number = -1;
private mask = new Mask();
/**
* 当前实体身上附加的组件构造函数
*/
private compTid2Ctor: Map<number, CompType<IComp>> = new Map();
private compTid2Obj: Map<number, IComp> = new Map();
constructor() { }
/**
* 根据组件id动态创建组件,并通知关心的系统。
*
* 如果实体存在了这个组件,那么会先删除之前的组件然后添加新的。
*
* 注意:不要直接new Component,new来的Component不会从Component的缓存池拿缓存的数据。
* @param componentTypeId 组件id
* @param isReAdd true-表示用户指定这个实体可能已经存在了该组件,那么再次add组件的时候会先移除该组件然后再添加一遍。false-表示不重复添加组件。
*/
add<T extends IComp>(obj: T): Entity;
add(ctor: number, isReAdd?: boolean): Entity;
// add<T extends IComp>(ctor: CompCtor<T>, isReAdd?: boolean): T;
add<T extends IComp>(ctor: CompType<T>, isReAdd?: boolean): T;
add<T extends IComp>(ctor: CompType<T> | T, isReAdd: boolean = false): T | Entity {
// console.log('typeof: ', typeof ctor);
if (typeof ctor === 'function') {
let compTid = ctor.tid;
if (ctor.tid === -1) {
throw Error('组件未注册!');
}
if (this.compTid2Ctor.has(compTid)) {// 判断是否有该组件,如果有则先移除
if (isReAdd) {
this.remove(ctor);
}
else {
console.log(`已经存在组件:${ctor.compName}`);
// @ts-ignore
return this[ctor.compName] as T;
}
}
this.mask.set(compTid);
let comp: T;
if (this.compTid2Obj.has(compTid)) {
comp = this.compTid2Obj.get(compTid) as T;
this.compTid2Obj.delete(compTid);
}
else {
// 创建组件对象
comp = createComp(ctor) as T;
}
// 将组件对象直接附加到实体对象身上,方便直接获取。
// @ts-ignore
this[ctor.compName] = comp;
this.compTid2Ctor.set(compTid, ctor);
comp.ent = this;
// 广播实体添加组件的消息
broadcastCompAddOrRemove(this, compTid);
return comp;
}
else if (typeof ctor === 'number') {
if (tags.has(ctor)) {
this.mask.set(ctor);
this.compTid2Ctor.set(ctor, ctor);
let tagName = tags.get(ctor)!;
// @ts-ignore
this[tagName] = ctor;
broadcastCompAddOrRemove(this, ctor);
}
else {
throw Error('不存在的tag!');
}
return this;
}
else {
let tmpCtor = (ctor.constructor as CompCtor<T>);
let compTid = tmpCtor.tid;
// console.assert(compTid !== -1 || !compTid, '组件未注册!');
// console.assert(this.compTid2Ctor.has(compTid), '已存在该组件!');
if (compTid === -1 || compTid == null) {
throw Error('组件未注册!');
}
if (this.compTid2Ctor.has(compTid)) {
throw Error('已经存在该组件!');
}
this.mask.set(compTid);
this[tmpCtor.compName] = ctor;
this.compTid2Ctor.set(compTid, tmpCtor);
ctor.ent = this;
ctor.canRecycle = false;
broadcastCompAddOrRemove(this, compTid);
return this;
}
}
addComponents<T extends IComp>(...ctors: CompType<T>[]) {
for (let ctor of ctors) {
this.add(ctor);
}
return this;
}
get(ctor: number): number;
get<T extends IComp>(ctor: CompCtor<T>): T;
get<T extends IComp>(ctor: CompCtor<T> | number): T {
let compName: string;
if (typeof (ctor) === 'number') {
compName = tags.get(ctor)!;
}
else {
compName = ctor.compName;
}
// @ts-ignore
return this[compName];
}
has(ctor: CompType<IComp>): boolean {
if (typeof ctor == "number") {
return this.mask.has(ctor);
}
else {
return this.compTid2Ctor.has(ctor.tid);
}
}
/**
*
* @param ctor 组件构造函数或者组件Tag
* @param isRecycle 是否回收该组件对象。对于有些组件上有大量数据,当要描述移除组件但是不想清除组件上的数据是可以
* 设置该参数为false,这样该组件对象会缓存在实体身上,下次重新添加组件时会将该组件对象添加回来,不会重新从组件缓存
* 池中拿一个组件来用。
*/
remove(ctor: CompType<IComp>, isRecycle: boolean = true) {
let componentTypeId = -1;
let compName = '';
let hasComp = false;
if (typeof ctor === "number") {
componentTypeId = ctor;
if (this.mask.has(ctor)) {
hasComp = true;
compName = tags.get(ctor)!;
}
}
else {
componentTypeId = ctor.tid;
compName = ctor.compName;
if (this.mask.has(componentTypeId)) {
hasComp = true;
let comp = this[ctor.compName] as IComp;
comp.ent = null;
if (isRecycle) {
comp.reset();
if (comp.canRecycle) {
compPools.get(componentTypeId).push(comp);
}
}
else {
this.compTid2Obj.set(componentTypeId, comp);
}
}
}
if (hasComp) {
this[compName] = null;
this.mask.delete(componentTypeId);
this.compTid2Ctor.delete(componentTypeId);
broadcastCompAddOrRemove(this, componentTypeId);
}
}
private _remove(comp: CompType<IComp>) {
this.remove(comp, false);
}
/**
* 销毁实体,实体会被回收到实体缓存池中。
*/
destroy() {
this.compTid2Ctor.forEach(this._remove, this);
destroyEntity(this);
this.compTid2Obj.clear();
}
}
export class Group<E extends Entity = Entity> {
/**
* 实体筛选规则
*/
private matcher: IMatcher;
private _matchEntities: Map<number, E> = new Map();
private _entitiesCache: E[] | null = null;
/**
* 符合规则的实体
*/
public get matchEntities() {
if (this._entitiesCache === null) {
this._entitiesCache = Array.from(this._matchEntities.values());
}
return this._entitiesCache;
}
/**
* 当前group中实体的数量。
*
* 不要手动修改这个属性值。
*/
public count = 0; // 其实可以通过this._matchEntities.size获得实体数量,但是需要封装get方法。为了减少一次方法的调用所以才直接创建一个count属性
/**
* 获取matchEntities中第一个实体
*/
get entity(): E {
return this.matchEntities[0];
}
private _enteredEntities: Map<number, E> | null = null;
private _removedEntities: Map<number, E> | null = null;
constructor(matcher: IMatcher) {
this.matcher = matcher;
}
public onComponentAddOrRemove(entity: E) {
if (this.matcher.isMatch(entity)) { // Group只关心指定组件在实体身上的添加和删除动作。
this._matchEntities.set(entity.eid, entity);
this._entitiesCache = null;
this.count++;
if (this._enteredEntities) {
this._enteredEntities.set(entity.eid, entity);
this._removedEntities!.delete(entity.eid);
}
}
else if (this._matchEntities.has(entity.eid)) { // 如果Group中有这个实体,但是这个实体已经不满足匹配规则,则从Group中移除该实体
this._matchEntities.delete(entity.eid);
this._entitiesCache = null;
this.count--;
if (this._enteredEntities) {
this._enteredEntities.delete(entity.eid);
this._removedEntities!.set(entity.eid, entity);
}
}
}
public watchEntityEnterAndRemove(enteredEntities: Map<number, E>, removedEntities: Map<number, E>) {
this._enteredEntities = enteredEntities;
this._removedEntities = removedEntities;
}
clear() {
this._matchEntities.clear();
this._entitiesCache = null;
this.count = 0;
this._enteredEntities?.clear();
this._removedEntities?.clear();
}
}
abstract class BaseOf {
protected mask = new Mask();
public indices: number[] = [];
constructor(...args: CompType<IComp>[]) {
let componentTypeId = -1;
let len = args.length;
for (let i = 0; i < len; i++) {
if (typeof (args[i]) === "number") {
componentTypeId = args[i] as number;
}
else {
componentTypeId = (args[i] as CompCtor<IComp>).tid;
}
if (componentTypeId == -1) {
throw Error('存在没有注册的组件!');
}
this.mask.set(componentTypeId);
if (this.indices.indexOf(componentTypeId) < 0) { // 去重
this.indices.push(componentTypeId);
}
}
if (len > 1) {
this.indices.sort((a, b) => { return a - b; }); // 对组件类型id进行排序,这样关注相同组件的系统就能共用同一个group
}
}
public toString(): string {
return this.indices.join('-'); // 生成group的key
}
public abstract getKey(): string;
public abstract isMatch(entity: Entity): boolean;
}
/**
* 用于描述包含任意一个这些组件的实体
*/
class AnyOf extends BaseOf {
public isMatch(entity: Entity): boolean {
// @ts-ignore
return this.mask.or(entity.mask);
}
getKey(): string {
return 'anyOf:' + this.toString();
}
}
/**
* 用于描述包含了“这些”组件的实体,这个实体除了包含这些组件还可以包含其他组件
*/
class AllOf extends BaseOf {
public isMatch(entity: Entity): boolean {
// @ts-ignore
return this.mask.and(entity.mask);
}
getKey(): string {
return 'allOf:' + this.toString();
}
}
/**
* 不包含指定的任意一个组件
*/
class ExcludeOf extends BaseOf {
public getKey(): string {
return 'excludeOf:' + this.toString();
}
public isMatch(entity: Entity): boolean {
// @ts-ignore
return !this.mask.or(entity.mask);
}
}
export interface IMatcher {
mid: number;
indices: number[];
key: string;
isMatch(entity: Entity): boolean;
}
let macherId: number = 1;
/**
* 筛选规则间是“与”的关系
* 比如:ecs.Macher.allOf(...).excludeOf(...)表达的是allOf && excludeOf,即实体有“这些组件” 并且 “没有这些组件”
*/
class Matcher implements IMatcher {
protected rules: BaseOf[] = [];
protected _indices: number[] | null = null;
public isMatch!: (entity: Entity) => boolean;
public mid: number = -1;
private _key: string | null = null;
public get key(): string {
if (!this._key) {
let s = '';
for (let i = 0; i < this.rules.length; i++) {
s += this.rules[i].getKey()
if (i < this.rules.length - 1) {
s += ' && '
}
}
this._key = s;
}
return this._key;
}
constructor() {
this.mid = macherId++;
}
/**
* 匹配器关注的组件索引。在创建Group时,Context根据组件id去给Group关联组件的添加和移除事件。
*/
public get indices() {
if (this._indices === null) {
this._indices = [];
this.rules.forEach((rule) => {
Array.prototype.push.apply(this._indices, rule.indices);
});
}
return this._indices;
}
/**
* 组件间是或的关系,表示关注拥有任意一个这些组件的实体。
* @param args 组件索引
*/
public anyOf(...args: CompType<IComp>[]): Matcher {
this.rules.push(new AnyOf(...args));
this.bindMatchMethod();
return this;
}
/**
* 组件间是与的关系,表示关注拥有所有这些组件的实体。
* @param args 组件索引
*/
public allOf(...args: CompType<IComp>[]): Matcher {
this.rules.push(new AllOf(...args));
this.bindMatchMethod();
return this;
}
/**
* 表示关注只拥有这些组件的实体
*
* 注意:
* 不是特殊情况不建议使用onlyOf。因为onlyOf会监听所有组件的添加和删除事件。
* @param args 组件索引
*/
public onlyOf(...args: CompType<IComp>[]): Matcher {
this.rules.push(new AllOf(...args));
let otherTids: CompType<IComp>[] = [];
for (let ctor of compCtors) {
if (args.indexOf(ctor) < 0) {
otherTids.push(ctor);
}
}
this.rules.push(new ExcludeOf(...otherTids));
this.bindMatchMethod();
return this;
}
/**
* 不包含指定的任意一个组件
* @param args
*/
public excludeOf(...args: CompType<IComp>[]) {
this.rules.push(new ExcludeOf(...args));
this.bindMatchMethod();
return this;
}
private bindMatchMethod() {
if (this.rules.length === 1) {
this.isMatch = this.isMatch1;
}
else if (this.rules.length === 2) {
this.isMatch = this.isMatch2;
}
else {
this.isMatch = this.isMatchMore;
}
}
private isMatch1(entity: Entity): boolean {
return this.rules[0].isMatch(entity);
}
private isMatch2(entity: Entity): boolean {
return this.rules[0].isMatch(entity) && this.rules[1].isMatch(entity);
}
private isMatchMore(entity: Entity): boolean {
for (let rule of this.rules) {
if (!rule.isMatch(entity)) {
return false;
}
}
return true;
}
public clone(): Matcher {
let newMatcher = new Matcher();
newMatcher.mid = macherId++;
this.rules.forEach(rule => newMatcher.rules.push(rule));
return newMatcher;
}
}
//#region System
/**
* 如果需要监听实体首次进入System的情况,实现这个接口。
*
* entityEnter会在update方法之前执行,实体进入后,不会再次进入entityEnter方法中。
* 当实体从当前System移除,下次再次符合条件进入System也会执行上述流程。
*/
export interface IEntityEnterSystem<E extends Entity = Entity> {
entityEnter(entities: E[]): void;
}
/**
* 如果需要监听实体从当前System移除,需要实现这个接口。
*/
export interface IEntityRemoveSystem<E extends Entity = Entity> {
entityRemove(entities: E[]): void;
}
/**
* 第一次执行update
*/
export interface ISystemFirstUpdate<E extends Entity = Entity> {
firstUpdate(entities: E[]): void;
}
export abstract class ComblockSystem<E extends Entity = Entity> {
protected group: Group<E>;
protected dt: number = 0;
private enteredEntities: Map<number, E> | null = null;
private removedEntities: Map<number, E> | null = null;
private hasEntityEnter: boolean = false;
private hasEntityRemove: boolean = false;
private tmpExecute: ((dt: number) => void) | null = null;
private execute!: (dt: number) => void;
constructor() {
let hasOwnProperty = Object.hasOwnProperty;
let prototype = Object.getPrototypeOf(this);
let hasEntityEnter = hasOwnProperty.call(prototype, 'entityEnter');
let hasEntityRemove = hasOwnProperty.call(prototype, 'entityRemove');
let hasFirstUpdate = hasOwnProperty.call(prototype, 'firstUpdate');
this.hasEntityEnter = hasEntityEnter;
this.hasEntityRemove = hasEntityRemove;
if (hasEntityEnter || hasEntityRemove) {
this.enteredEntities = new Map<number, E>();
this.removedEntities = new Map<number, E>();
this.execute = this.execute1;
this.group = createGroup(this.filter());
this.group.watchEntityEnterAndRemove(this.enteredEntities, this.removedEntities);
}
else {
this.execute = this.execute0;
this.group = createGroup(this.filter());
}
if (hasFirstUpdate) {
this.tmpExecute = this.execute;
this.execute = this.updateOnce;
}
}
init(): void {
}
onDestroy(): void {
}
hasEntity(): boolean {
return this.group.count > 0;
}
private updateOnce(dt: number) {
if (this.group.count === 0) {
return;
}
this.dt = dt;
// 处理刚进来的实体
if (this.enteredEntities!.size > 0) {
(this as unknown as IEntityEnterSystem).entityEnter(Array.from(this.enteredEntities!.values()) as E[]);
this.enteredEntities!.clear();
}
(this as unknown as ISystemFirstUpdate).firstUpdate(this.group.matchEntities);
this.execute = this.tmpExecute!;
this.execute(dt);
this.tmpExecute = null;
}
/**
* 只执行update
* @param dt
* @returns
*/
private execute0(dt: number): void {