@dcl/ecs 7.0.6-3823801200.commit-32470bd → 7.0.6-3830539086.commit-6152fbd
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/entity.d.ts +35 -6
- package/dist/engine/entity.js +121 -32
- package/dist/engine/index.js +6 -9
- package/dist/engine/types.d.ts +8 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/runtime/types.d.ts +0 -1
- package/dist/serialization/crdt/deleteComponent.d.ts +11 -0
- package/dist/serialization/crdt/deleteComponent.js +45 -0
- package/dist/serialization/crdt/deleteEntity.d.ts +8 -0
- package/dist/serialization/crdt/deleteEntity.js +28 -0
- package/dist/serialization/crdt/index.d.ts +32 -0
- package/dist/serialization/crdt/index.js +70 -0
- package/dist/serialization/crdt/message.d.ts +3 -0
- package/dist/serialization/crdt/message.js +17 -0
- package/dist/serialization/crdt/putComponent.d.ts +13 -0
- package/dist/serialization/crdt/putComponent.js +44 -0
- package/dist/serialization/crdt/types.d.ts +53 -0
- package/dist/serialization/crdt/types.js +10 -0
- package/dist/systems/crdt/index.d.ts +5 -5
- package/dist/systems/crdt/index.js +189 -72
- package/dist/systems/crdt/types.d.ts +2 -8
- package/dist/systems/events.js +2 -1
- package/package.json +3 -3
- package/dist/serialization/crdt/componentOperation.d.ts +0 -31
- package/dist/serialization/crdt/componentOperation.js +0 -47
- package/dist/serialization/wireMessage.d.ts +0 -41
- package/dist/serialization/wireMessage.js +0 -56
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Entity } from '../../engine/entity';
|
|
2
|
+
export declare type Uint32 = number;
|
|
3
|
+
export declare enum CrdtMessageType {
|
|
4
|
+
RESERVED = 0,
|
|
5
|
+
PUT_COMPONENT = 1,
|
|
6
|
+
DELETE_COMPONENT = 2,
|
|
7
|
+
DELETE_ENTITY = 3,
|
|
8
|
+
MAX_MESSAGE_TYPE = 4
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Min length = 8 bytes
|
|
12
|
+
* All message length including
|
|
13
|
+
* @param length - Uint32 the length of all message (including the header)
|
|
14
|
+
* @param type - define the function which handles the data
|
|
15
|
+
*/
|
|
16
|
+
export declare type CrdtMessageHeader = {
|
|
17
|
+
length: Uint32;
|
|
18
|
+
type: Uint32;
|
|
19
|
+
};
|
|
20
|
+
export declare const CRDT_MESSAGE_HEADER_LENGTH = 8;
|
|
21
|
+
/**
|
|
22
|
+
* Min. length = header (8 bytes) + 20 bytes = 28 bytes
|
|
23
|
+
*
|
|
24
|
+
* @param entity - Uint32 number of the entity
|
|
25
|
+
* @param componentId - Uint32 number of id
|
|
26
|
+
* @param timestamp - Uint64 Lamport timestamp
|
|
27
|
+
* @param data - Uint8[] data of component => length(4 bytes) + block of bytes[0..length-1]
|
|
28
|
+
*/
|
|
29
|
+
export declare type PutComponentMessageBody = {
|
|
30
|
+
type: CrdtMessageType.PUT_COMPONENT;
|
|
31
|
+
entityId: Entity;
|
|
32
|
+
componentId: number;
|
|
33
|
+
timestamp: number;
|
|
34
|
+
data: Uint8Array;
|
|
35
|
+
};
|
|
36
|
+
export declare type DeleteComponentMessageBody = {
|
|
37
|
+
type: CrdtMessageType.DELETE_COMPONENT;
|
|
38
|
+
entityId: Entity;
|
|
39
|
+
componentId: number;
|
|
40
|
+
timestamp: number;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* @param entity - Uint32 number of the entity
|
|
44
|
+
*/
|
|
45
|
+
export declare type DeleteEntityMessageBody = {
|
|
46
|
+
type: CrdtMessageType.DELETE_ENTITY;
|
|
47
|
+
entityId: Entity;
|
|
48
|
+
};
|
|
49
|
+
export declare type PutComponentMessage = CrdtMessageHeader & PutComponentMessageBody;
|
|
50
|
+
export declare type DeleteComponentMessage = CrdtMessageHeader & DeleteComponentMessageBody;
|
|
51
|
+
export declare type DeleteEntityMessage = CrdtMessageHeader & DeleteEntityMessageBody;
|
|
52
|
+
export declare type CrdtMessage = PutComponentMessage | DeleteComponentMessage | DeleteEntityMessage;
|
|
53
|
+
export declare type CrdtMessageBody = PutComponentMessageBody | DeleteComponentMessageBody | DeleteEntityMessage;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export var CrdtMessageType;
|
|
2
|
+
(function (CrdtMessageType) {
|
|
3
|
+
CrdtMessageType[CrdtMessageType["RESERVED"] = 0] = "RESERVED";
|
|
4
|
+
// Component Operation
|
|
5
|
+
CrdtMessageType[CrdtMessageType["PUT_COMPONENT"] = 1] = "PUT_COMPONENT";
|
|
6
|
+
CrdtMessageType[CrdtMessageType["DELETE_COMPONENT"] = 2] = "DELETE_COMPONENT";
|
|
7
|
+
CrdtMessageType[CrdtMessageType["DELETE_ENTITY"] = 3] = "DELETE_ENTITY";
|
|
8
|
+
CrdtMessageType[CrdtMessageType["MAX_MESSAGE_TYPE"] = 4] = "MAX_MESSAGE_TYPE";
|
|
9
|
+
})(CrdtMessageType || (CrdtMessageType = {}));
|
|
10
|
+
export const CRDT_MESSAGE_HEADER_LENGTH = 8;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import type { ComponentDefinition, IEngine } from '../../engine';
|
|
2
1
|
import { Entity } from '../../engine/entity';
|
|
3
|
-
import
|
|
2
|
+
import type { ComponentDefinition, IEngine } from '../../engine';
|
|
3
|
+
import { CrdtMessageType } from '../../serialization/crdt/types';
|
|
4
4
|
import { Transport } from './types';
|
|
5
5
|
/**
|
|
6
6
|
* @public
|
|
7
7
|
*/
|
|
8
|
-
export declare type OnChangeFunction = (entity: Entity,
|
|
9
|
-
export declare function crdtSceneSystem(engine: Pick<IEngine, 'getComponentOrNull' | 'getComponent' | 'componentsIter'>, onProcessEntityComponentChange: OnChangeFunction | null): {
|
|
8
|
+
export declare type OnChangeFunction = (entity: Entity, operation: CrdtMessageType, component?: ComponentDefinition<any>) => void;
|
|
9
|
+
export declare function crdtSceneSystem(engine: Pick<IEngine, 'getComponentOrNull' | 'getComponent' | 'entityContainer' | 'componentsIter'>, onProcessEntityComponentChange: OnChangeFunction | null): {
|
|
10
10
|
getCrdt: () => import("@dcl/crdt").State<Uint8Array>;
|
|
11
|
-
sendMessages: (dirtyEntities: Map<ComponentDefinition<unknown>, Array<Entity
|
|
11
|
+
sendMessages: (dirtyEntities: Map<ComponentDefinition<unknown>, Array<Entity>>, deletedEntities: Entity[]) => Promise<void>;
|
|
12
12
|
receiveMessages: () => Promise<void>;
|
|
13
13
|
addTransport: (transport: Transport) => void;
|
|
14
14
|
updateState: () => Map<ComponentDefinition<unknown>, Entity[]>;
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { crdtProtocol } from '@dcl/crdt';
|
|
2
|
+
import { CRDTMessageType, ProcessMessageResultType } from '@dcl/crdt/dist/types';
|
|
3
|
+
import { EntityState, EntityUtils } from '../../engine/entity';
|
|
2
4
|
import { createByteBuffer } from '../../serialization/ByteBuffer';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
+
import CrdtMessageProtocol from '../../serialization/crdt';
|
|
6
|
+
import { DeleteComponent } from '../../serialization/crdt/deleteComponent';
|
|
7
|
+
import { DeleteEntity } from '../../serialization/crdt/deleteEntity';
|
|
8
|
+
import { PutComponentOperation } from '../../serialization/crdt/putComponent';
|
|
9
|
+
import { CrdtMessageType } from '../../serialization/crdt/types';
|
|
5
10
|
export function crdtSceneSystem(engine, onProcessEntityComponentChange) {
|
|
6
11
|
const transports = [];
|
|
7
12
|
// CRDT Client
|
|
8
|
-
const crdtClient = crdtProtocol(
|
|
13
|
+
const crdtClient = crdtProtocol({
|
|
14
|
+
toEntityId: EntityUtils.toEntityId,
|
|
15
|
+
fromEntityId: EntityUtils.fromEntityId
|
|
16
|
+
});
|
|
9
17
|
// Messages that we received at transport.onMessage waiting to be processed
|
|
10
18
|
const receivedMessages = [];
|
|
11
19
|
// Messages already processed by the engine but that we need to broadcast to other transports.
|
|
@@ -27,21 +35,47 @@ export function crdtSceneSystem(engine, onProcessEntityComponentChange) {
|
|
|
27
35
|
const buffer = createByteBuffer({
|
|
28
36
|
reading: { buffer: chunkMessage, currentOffset: 0 }
|
|
29
37
|
});
|
|
30
|
-
|
|
38
|
+
let header;
|
|
39
|
+
while ((header = CrdtMessageProtocol.getHeader(buffer))) {
|
|
31
40
|
const offset = buffer.currentReadOffset();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
if (header.type === CrdtMessageType.DELETE_COMPONENT) {
|
|
42
|
+
const message = DeleteComponent.read(buffer);
|
|
43
|
+
receivedMessages.push({
|
|
44
|
+
...header,
|
|
45
|
+
...message,
|
|
46
|
+
transportId,
|
|
47
|
+
messageBuffer: buffer
|
|
48
|
+
.buffer()
|
|
49
|
+
.subarray(offset, buffer.currentReadOffset())
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
else if (header.type === CrdtMessageType.PUT_COMPONENT) {
|
|
53
|
+
const message = PutComponentOperation.read(buffer);
|
|
54
|
+
receivedMessages.push({
|
|
55
|
+
...header,
|
|
56
|
+
...message,
|
|
57
|
+
transportId,
|
|
58
|
+
messageBuffer: buffer
|
|
59
|
+
.buffer()
|
|
60
|
+
.subarray(offset, buffer.currentReadOffset())
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
else if (header.type === CrdtMessageType.DELETE_ENTITY) {
|
|
64
|
+
const message = DeleteEntity.read(buffer);
|
|
65
|
+
receivedMessages.push({
|
|
66
|
+
...header,
|
|
67
|
+
...message,
|
|
68
|
+
transportId,
|
|
69
|
+
messageBuffer: buffer
|
|
70
|
+
.buffer()
|
|
71
|
+
.subarray(offset, buffer.currentReadOffset())
|
|
72
|
+
});
|
|
73
|
+
// Unknown message, we skip it
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// consume the message
|
|
77
|
+
buffer.incrementReadOffset(header.length);
|
|
78
|
+
}
|
|
45
79
|
}
|
|
46
80
|
// TODO: do something if buffler.len>0
|
|
47
81
|
};
|
|
@@ -61,54 +95,109 @@ export function crdtSceneSystem(engine, onProcessEntityComponentChange) {
|
|
|
61
95
|
async function receiveMessages() {
|
|
62
96
|
const messagesToProcess = getMessages(receivedMessages);
|
|
63
97
|
const bufferForOutdated = createByteBuffer();
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
};
|
|
72
|
-
const component = engine.getComponentOrNull(componentId);
|
|
73
|
-
if (component?.isDirty(entity)) {
|
|
74
|
-
crdtClient.createEvent(entity, component._id, component.toBinaryOrNull(entity)?.toBinary() || null);
|
|
75
|
-
}
|
|
76
|
-
const current = crdtClient.processMessage(crdtMessage);
|
|
77
|
-
if (!component) {
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
// CRDT outdated message. Resend this message to the transport
|
|
81
|
-
// To do this we add this message to a queue that will be processed at the end of the update tick
|
|
82
|
-
if (crdtMessage !== current) {
|
|
83
|
-
const offset = bufferForOutdated.currentWriteOffset();
|
|
84
|
-
const type = WireMessage.getType(component, entity);
|
|
85
|
-
const ts = current.timestamp;
|
|
86
|
-
Message.write(type, entity, ts, component, bufferForOutdated);
|
|
87
|
-
outdatedMessages.push({
|
|
88
|
-
...message,
|
|
89
|
-
timestamp: current.timestamp,
|
|
90
|
-
messageBuffer: bufferForOutdated
|
|
91
|
-
.buffer()
|
|
92
|
-
.subarray(offset, bufferForOutdated.currentWriteOffset())
|
|
98
|
+
const entitiesShouldBeCleaned = [];
|
|
99
|
+
for (const msg of messagesToProcess) {
|
|
100
|
+
// TODO: emit delete entity to el onCrdtMessage
|
|
101
|
+
if (msg.type === CrdtMessageType.DELETE_ENTITY) {
|
|
102
|
+
crdtClient.processMessage({
|
|
103
|
+
type: CRDTMessageType.CRDTMT_DeleteEntity,
|
|
104
|
+
entityId: msg.entityId
|
|
93
105
|
});
|
|
106
|
+
entitiesShouldBeCleaned.push(msg.entityId);
|
|
94
107
|
}
|
|
95
108
|
else {
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
109
|
+
// TODO: emit pu/delete component to el onCrdtMessage
|
|
110
|
+
const crdtMessage = {
|
|
111
|
+
type: CRDTMessageType.CRDTMT_PutComponentData,
|
|
112
|
+
entityId: msg.entityId,
|
|
113
|
+
componentId: msg.componentId,
|
|
114
|
+
data: msg.type === CrdtMessageType.PUT_COMPONENT ? msg.data : null,
|
|
115
|
+
timestamp: msg.timestamp
|
|
116
|
+
};
|
|
117
|
+
const entityState = engine.entityContainer.getEntityState(msg.entityId);
|
|
118
|
+
// Skip updates from removed entityes
|
|
119
|
+
if (entityState === EntityState.Removed)
|
|
120
|
+
continue;
|
|
121
|
+
// Entities with unknown entities should update its entity state
|
|
122
|
+
if (entityState === EntityState.Unknown) {
|
|
123
|
+
engine.entityContainer.updateUsedEntity(msg.entityId);
|
|
101
124
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
125
|
+
const component = engine.getComponentOrNull(msg.componentId);
|
|
126
|
+
// The state isn't updated because the dirty was set
|
|
127
|
+
// out of the block of systems update between `receiveMessage` and `updateState`
|
|
128
|
+
if (component?.isDirty(msg.entityId)) {
|
|
129
|
+
crdtClient.createComponentDataEvent(component._id, msg.entityId, component.toBinaryOrNull(msg.entityId)?.toBinary() || null);
|
|
130
|
+
}
|
|
131
|
+
const processResult = crdtClient.processMessage(crdtMessage);
|
|
132
|
+
if (!component) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
switch (processResult) {
|
|
136
|
+
case ProcessMessageResultType.StateUpdatedTimestamp:
|
|
137
|
+
case ProcessMessageResultType.StateUpdatedData:
|
|
138
|
+
// Add message to transport queue to be processed by others transports
|
|
139
|
+
broadcastMessages.push(msg);
|
|
140
|
+
// Process CRDT Message
|
|
141
|
+
if (msg.type === CrdtMessageType.DELETE_COMPONENT) {
|
|
142
|
+
component.deleteFrom(msg.entityId, false);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
const opts = {
|
|
146
|
+
reading: { buffer: msg.data, currentOffset: 0 }
|
|
147
|
+
};
|
|
148
|
+
const data = createByteBuffer(opts);
|
|
149
|
+
component.upsertFromBinary(msg.entityId, data, false);
|
|
150
|
+
}
|
|
151
|
+
onProcessEntityComponentChange &&
|
|
152
|
+
onProcessEntityComponentChange(msg.entityId, msg.type, component);
|
|
153
|
+
break;
|
|
154
|
+
// CRDT outdated message. Resend this message to the transport
|
|
155
|
+
// To do this we add this message to a queue that will be processed at the end of the update tick
|
|
156
|
+
case ProcessMessageResultType.StateOutdatedData:
|
|
157
|
+
case ProcessMessageResultType.StateOutdatedTimestamp:
|
|
158
|
+
const current = crdtClient
|
|
159
|
+
.getState()
|
|
160
|
+
.components.get(msg.componentId)
|
|
161
|
+
?.get(msg.entityId);
|
|
162
|
+
if (current) {
|
|
163
|
+
const offset = bufferForOutdated.currentWriteOffset();
|
|
164
|
+
const ts = current.timestamp;
|
|
165
|
+
if (component.has(msg.entityId)) {
|
|
166
|
+
PutComponentOperation.write(msg.entityId, ts, component, bufferForOutdated);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
DeleteComponent.write(msg.entityId, component._id, ts, bufferForOutdated);
|
|
170
|
+
}
|
|
171
|
+
outdatedMessages.push({
|
|
172
|
+
...msg,
|
|
173
|
+
messageBuffer: bufferForOutdated
|
|
174
|
+
.buffer()
|
|
175
|
+
.subarray(offset, bufferForOutdated.currentWriteOffset())
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
case ProcessMessageResultType.NoChanges:
|
|
180
|
+
case ProcessMessageResultType.EntityDeleted:
|
|
181
|
+
case ProcessMessageResultType.EntityWasDeleted:
|
|
182
|
+
default:
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// TODO: emit delete entity to el onCrdtMessage
|
|
188
|
+
for (const entity of entitiesShouldBeCleaned) {
|
|
189
|
+
// If we tried to resend outdated message and the entity was deleted before, we avoid sending them.
|
|
190
|
+
for (let i = outdatedMessages.length - 1; i >= 0; i--) {
|
|
191
|
+
if (outdatedMessages[i].entityId === entity) {
|
|
192
|
+
outdatedMessages.splice(i, 1);
|
|
108
193
|
}
|
|
109
|
-
onProcessEntityComponentChange &&
|
|
110
|
-
onProcessEntityComponentChange(entity, component, type);
|
|
111
194
|
}
|
|
195
|
+
for (const definition of engine.componentsIter()) {
|
|
196
|
+
definition.deleteFrom(entity, false);
|
|
197
|
+
}
|
|
198
|
+
engine.entityContainer.updateRemovedEntity(entity);
|
|
199
|
+
onProcessEntityComponentChange &&
|
|
200
|
+
onProcessEntityComponentChange(entity, CrdtMessageType.DELETE_ENTITY);
|
|
112
201
|
}
|
|
113
202
|
}
|
|
114
203
|
/**
|
|
@@ -126,15 +215,20 @@ export function crdtSceneSystem(engine, onProcessEntityComponentChange) {
|
|
|
126
215
|
entitySet = [];
|
|
127
216
|
dirtyMap.set(component, entitySet);
|
|
128
217
|
}
|
|
129
|
-
entitySet.push(entity);
|
|
130
218
|
// TODO: reuse shared writer to prevent extra allocations of toBinary
|
|
131
219
|
const componentValue = component.toBinaryOrNull(entity)?.toBinary() ?? null;
|
|
132
220
|
// TODO: do not emit event if componentValue equals the value didn't change
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
221
|
+
// if update goes bad, the entity doesn't accept put anymore (it's added to deleted entities set)
|
|
222
|
+
if (crdtClient.createComponentDataEvent(component._id, entity, componentValue) === null) {
|
|
223
|
+
component.deleteFrom(entity, false);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
entitySet.push(entity);
|
|
227
|
+
onProcessEntityComponentChange &&
|
|
228
|
+
onProcessEntityComponentChange(entity, componentValue === null
|
|
229
|
+
? CrdtMessageType.DELETE_COMPONENT
|
|
230
|
+
: CrdtMessageType.PUT_COMPONENT, component);
|
|
231
|
+
}
|
|
138
232
|
}
|
|
139
233
|
}
|
|
140
234
|
return dirtyMap;
|
|
@@ -142,7 +236,7 @@ export function crdtSceneSystem(engine, onProcessEntityComponentChange) {
|
|
|
142
236
|
/**
|
|
143
237
|
* Iterates the dirty map and generates crdt messages to be send
|
|
144
238
|
*/
|
|
145
|
-
async function sendMessages(dirtyEntities) {
|
|
239
|
+
async function sendMessages(dirtyEntities, deletedEntities) {
|
|
146
240
|
// CRDT Messages will be the merge between the recieved transport messages and the new crdt messages
|
|
147
241
|
const crdtMessages = getMessages(broadcastMessages);
|
|
148
242
|
const outdatedMessagesBkp = getMessages(outdatedMessages);
|
|
@@ -152,19 +246,26 @@ export function crdtSceneSystem(engine, onProcessEntityComponentChange) {
|
|
|
152
246
|
// Component will be always defined here since dirtyMap its an iterator of engine.componentsDefinition
|
|
153
247
|
const { timestamp } = crdtClient
|
|
154
248
|
.getState()
|
|
155
|
-
.get(
|
|
156
|
-
.get(
|
|
249
|
+
.components.get(component._id)
|
|
250
|
+
.get(entity);
|
|
157
251
|
const offset = buffer.currentWriteOffset();
|
|
158
|
-
const type =
|
|
252
|
+
const type = component.has(entity)
|
|
253
|
+
? CrdtMessageType.PUT_COMPONENT
|
|
254
|
+
: CrdtMessageType.DELETE_COMPONENT;
|
|
159
255
|
const transportMessage = {
|
|
160
256
|
type,
|
|
257
|
+
entityId: entity,
|
|
161
258
|
componentId: component._id,
|
|
162
|
-
entity,
|
|
163
259
|
timestamp
|
|
164
260
|
};
|
|
165
261
|
// Avoid creating messages if there is no transport that will handle it
|
|
166
262
|
if (transports.some((t) => t.filter(transportMessage))) {
|
|
167
|
-
|
|
263
|
+
if (transportMessage.type === CrdtMessageType.PUT_COMPONENT) {
|
|
264
|
+
PutComponentOperation.write(entity, timestamp, component, buffer);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
DeleteComponent.write(entity, component._id, timestamp, buffer);
|
|
268
|
+
}
|
|
168
269
|
crdtMessages.push({
|
|
169
270
|
...transportMessage,
|
|
170
271
|
messageBuffer: buffer
|
|
@@ -174,6 +275,19 @@ export function crdtSceneSystem(engine, onProcessEntityComponentChange) {
|
|
|
174
275
|
}
|
|
175
276
|
}
|
|
176
277
|
}
|
|
278
|
+
// After all updates, I execute the DeletedEntity messages
|
|
279
|
+
for (const entityId of deletedEntities) {
|
|
280
|
+
crdtClient.createDeleteEntityEvent(entityId);
|
|
281
|
+
const offset = buffer.currentWriteOffset();
|
|
282
|
+
DeleteEntity.write(entityId, buffer);
|
|
283
|
+
crdtMessages.push({
|
|
284
|
+
type: CrdtMessageType.DELETE_ENTITY,
|
|
285
|
+
entityId,
|
|
286
|
+
messageBuffer: buffer
|
|
287
|
+
.buffer()
|
|
288
|
+
.subarray(offset, buffer.currentWriteOffset())
|
|
289
|
+
});
|
|
290
|
+
}
|
|
177
291
|
// Send CRDT messages to transports
|
|
178
292
|
const transportBuffer = createByteBuffer();
|
|
179
293
|
for (const index in transports) {
|
|
@@ -184,8 +298,11 @@ export function crdtSceneSystem(engine, onProcessEntityComponentChange) {
|
|
|
184
298
|
// So we can fix their crdt state
|
|
185
299
|
for (const message of outdatedMessagesBkp) {
|
|
186
300
|
if (message.transportId === transportIndex &&
|
|
301
|
+
// TODO: This is an optimization, the state should converge anyway, whatever the message is sent.
|
|
187
302
|
// Avoid sending multiple messages for the same entity-componentId
|
|
188
|
-
!crdtMessages.find((m) => m.
|
|
303
|
+
!crdtMessages.find((m) => m.entityId === message.entityId &&
|
|
304
|
+
// TODO: as any, with multiple type of messages, it should have many checks before the check for similar messages
|
|
305
|
+
m.componentId &&
|
|
189
306
|
m.componentId === message.componentId)) {
|
|
190
307
|
transportBuffer.writeBuffer(message.messageBuffer, false);
|
|
191
308
|
}
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export declare type ReceiveMessage = {
|
|
4
|
-
type: WireMessage.Enum;
|
|
5
|
-
entity: Entity;
|
|
6
|
-
componentId: number;
|
|
7
|
-
timestamp: number;
|
|
1
|
+
import { CrdtMessageBody } from '../../serialization/crdt/types';
|
|
2
|
+
export declare type ReceiveMessage = CrdtMessageBody & {
|
|
8
3
|
transportId?: number;
|
|
9
|
-
data?: Uint8Array;
|
|
10
4
|
messageBuffer: Uint8Array;
|
|
11
5
|
};
|
|
12
6
|
export declare type TransportMessage = Omit<ReceiveMessage, 'data'>;
|
package/dist/systems/events.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as components from '../components';
|
|
2
|
+
import { EntityState } from '../engine/entity';
|
|
2
3
|
import { checkNotThenable } from '../runtime/invariant';
|
|
3
4
|
export function createPointerEventSystem(engine, inputSystem) {
|
|
4
5
|
const PointerEvents = components.PointerEvents(engine);
|
|
@@ -53,7 +54,7 @@ export function createPointerEventSystem(engine, inputSystem) {
|
|
|
53
54
|
// @internal
|
|
54
55
|
engine.addSystem(function EventSystem() {
|
|
55
56
|
for (const [entity, event] of eventsMap) {
|
|
56
|
-
if (
|
|
57
|
+
if (engine.getEntityState(entity) === EntityState.Removed) {
|
|
57
58
|
eventsMap.delete(entity);
|
|
58
59
|
continue;
|
|
59
60
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dcl/ecs",
|
|
3
|
-
"version": "7.0.6-
|
|
3
|
+
"version": "7.0.6-3830539086.commit-6152fbd",
|
|
4
4
|
"description": "Decentraland ECS",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"typings": "./dist/index.d.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"ts-proto": "^1.112.0"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@dcl/crdt": "7.0.6-
|
|
30
|
+
"@dcl/crdt": "7.0.6-3830539086.commit-6152fbd",
|
|
31
31
|
"@dcl/js-runtime": "file:../js-runtime",
|
|
32
32
|
"@dcl/protocol": "^1.0.0-3808528053.commit-d66d462"
|
|
33
33
|
},
|
|
@@ -41,5 +41,5 @@
|
|
|
41
41
|
"displayName": "ECS",
|
|
42
42
|
"tsconfig": "./tsconfig.json"
|
|
43
43
|
},
|
|
44
|
-
"commit": "
|
|
44
|
+
"commit": "6152fbdf7dfabf5e0d61ef713e75e670ed3d69b8"
|
|
45
45
|
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { ComponentDefinition } from '../../engine/component';
|
|
2
|
-
import { Entity } from '../../engine/entity';
|
|
3
|
-
import { ByteBuffer } from '../ByteBuffer';
|
|
4
|
-
import WireMessage from '../wireMessage';
|
|
5
|
-
export declare namespace ComponentOperation {
|
|
6
|
-
/**
|
|
7
|
-
* @param entity - Uint32 number of the entity
|
|
8
|
-
* @param componentId - Uint32 number of id
|
|
9
|
-
* @param timestamp - Uint64 Lamport timestamp
|
|
10
|
-
* @param data - Uint8[] data of component
|
|
11
|
-
*/
|
|
12
|
-
type IPutComponent = {
|
|
13
|
-
entity: Entity;
|
|
14
|
-
componentId: number;
|
|
15
|
-
timestamp: number;
|
|
16
|
-
data: Uint8Array;
|
|
17
|
-
};
|
|
18
|
-
type IDeleteComponent = {
|
|
19
|
-
entity: Entity;
|
|
20
|
-
componentId: number;
|
|
21
|
-
timestamp: number;
|
|
22
|
-
data?: undefined;
|
|
23
|
-
};
|
|
24
|
-
const MESSAGE_HEADER_LENGTH = 20;
|
|
25
|
-
/**
|
|
26
|
-
* Call this function for an optimal writing data passing the ByteBuffer
|
|
27
|
-
* already allocated
|
|
28
|
-
*/
|
|
29
|
-
function write(type: WireMessage.Enum, entity: Entity, timestamp: number, componentDefinition: ComponentDefinition<unknown>, buf: ByteBuffer): void;
|
|
30
|
-
function read(buf: ByteBuffer): (WireMessage.Header & (IPutComponent | IDeleteComponent)) | null;
|
|
31
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import WireMessage from '../wireMessage';
|
|
2
|
-
export var ComponentOperation;
|
|
3
|
-
(function (ComponentOperation) {
|
|
4
|
-
ComponentOperation.MESSAGE_HEADER_LENGTH = 20;
|
|
5
|
-
/**
|
|
6
|
-
* Call this function for an optimal writing data passing the ByteBuffer
|
|
7
|
-
* already allocated
|
|
8
|
-
*/
|
|
9
|
-
function write(type, entity, timestamp, componentDefinition, buf) {
|
|
10
|
-
// reserve the beginning
|
|
11
|
-
const startMessageOffset = buf.incrementWriteOffset(WireMessage.HEADER_LENGTH + ComponentOperation.MESSAGE_HEADER_LENGTH);
|
|
12
|
-
// write body
|
|
13
|
-
if (type === WireMessage.Enum.PUT_COMPONENT) {
|
|
14
|
-
componentDefinition.writeToByteBuffer(entity, buf);
|
|
15
|
-
}
|
|
16
|
-
const messageLength = buf.size() - startMessageOffset;
|
|
17
|
-
// Write WireMessage header
|
|
18
|
-
buf.setUint32(startMessageOffset, messageLength);
|
|
19
|
-
buf.setUint32(startMessageOffset + 4, type);
|
|
20
|
-
// Write ComponentOperation header
|
|
21
|
-
buf.setUint32(startMessageOffset + 8, entity);
|
|
22
|
-
buf.setUint32(startMessageOffset + 12, componentDefinition._id);
|
|
23
|
-
buf.setUint64(startMessageOffset + 16, BigInt(timestamp));
|
|
24
|
-
buf.setUint32(startMessageOffset + 24, messageLength - ComponentOperation.MESSAGE_HEADER_LENGTH - WireMessage.HEADER_LENGTH);
|
|
25
|
-
}
|
|
26
|
-
ComponentOperation.write = write;
|
|
27
|
-
function read(buf) {
|
|
28
|
-
const header = WireMessage.readHeader(buf);
|
|
29
|
-
if (!header) {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
const common = {
|
|
33
|
-
...header,
|
|
34
|
-
entity: buf.readUint32(),
|
|
35
|
-
componentId: buf.readInt32(),
|
|
36
|
-
timestamp: Number(buf.readUint64())
|
|
37
|
-
};
|
|
38
|
-
if (header.type === WireMessage.Enum.DELETE_COMPONENT) {
|
|
39
|
-
return common;
|
|
40
|
-
}
|
|
41
|
-
return {
|
|
42
|
-
...common,
|
|
43
|
-
data: buf.readBuffer()
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
ComponentOperation.read = read;
|
|
47
|
-
})(ComponentOperation || (ComponentOperation = {}));
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The wire message is the top-level message that can be packed
|
|
3
|
-
* inside it can contain a data with another structure or protocol
|
|
4
|
-
*
|
|
5
|
-
* Each wire message has three primitive property that it'll never change
|
|
6
|
-
* ---> length uint32 (message size up to 4,294,967,295)
|
|
7
|
-
* ---> version uint32 (for now just a number which is zero)
|
|
8
|
-
* ---> message type uint32
|
|
9
|
-
* The length indicates how many bytes are above self, the version in
|
|
10
|
-
* combination with message type defines the set of handlers that will be
|
|
11
|
-
* available to process the message
|
|
12
|
-
*
|
|
13
|
-
*/
|
|
14
|
-
import { ComponentDefinition, Entity } from '../engine';
|
|
15
|
-
import { ByteBuffer } from './ByteBuffer';
|
|
16
|
-
export declare namespace WireMessage {
|
|
17
|
-
type Uint32 = number;
|
|
18
|
-
enum Enum {
|
|
19
|
-
RESERVED = 0,
|
|
20
|
-
PUT_COMPONENT = 1,
|
|
21
|
-
DELETE_COMPONENT = 2,
|
|
22
|
-
MAX_MESSAGE_TYPE = 3
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* @param length - Uint32 the length of all message (including the header)
|
|
26
|
-
* @param type - define the function which handles the data
|
|
27
|
-
*/
|
|
28
|
-
type Header = {
|
|
29
|
-
length: Uint32;
|
|
30
|
-
type: Uint32;
|
|
31
|
-
};
|
|
32
|
-
const HEADER_LENGTH = 8;
|
|
33
|
-
/**
|
|
34
|
-
* Validate if the message incoming is completed
|
|
35
|
-
* @param buf - ByteBuffer
|
|
36
|
-
*/
|
|
37
|
-
function validate(buf: ByteBuffer): boolean;
|
|
38
|
-
function readHeader(buf: ByteBuffer): Header | null;
|
|
39
|
-
function getType(component: ComponentDefinition<unknown>, entity: Entity): Enum;
|
|
40
|
-
}
|
|
41
|
-
export default WireMessage;
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The wire message is the top-level message that can be packed
|
|
3
|
-
* inside it can contain a data with another structure or protocol
|
|
4
|
-
*
|
|
5
|
-
* Each wire message has three primitive property that it'll never change
|
|
6
|
-
* ---> length uint32 (message size up to 4,294,967,295)
|
|
7
|
-
* ---> version uint32 (for now just a number which is zero)
|
|
8
|
-
* ---> message type uint32
|
|
9
|
-
* The length indicates how many bytes are above self, the version in
|
|
10
|
-
* combination with message type defines the set of handlers that will be
|
|
11
|
-
* available to process the message
|
|
12
|
-
*
|
|
13
|
-
*/
|
|
14
|
-
export var WireMessage;
|
|
15
|
-
(function (WireMessage) {
|
|
16
|
-
let Enum;
|
|
17
|
-
(function (Enum) {
|
|
18
|
-
Enum[Enum["RESERVED"] = 0] = "RESERVED";
|
|
19
|
-
// Component Operation
|
|
20
|
-
Enum[Enum["PUT_COMPONENT"] = 1] = "PUT_COMPONENT";
|
|
21
|
-
Enum[Enum["DELETE_COMPONENT"] = 2] = "DELETE_COMPONENT";
|
|
22
|
-
Enum[Enum["MAX_MESSAGE_TYPE"] = 3] = "MAX_MESSAGE_TYPE";
|
|
23
|
-
})(Enum = WireMessage.Enum || (WireMessage.Enum = {}));
|
|
24
|
-
WireMessage.HEADER_LENGTH = 8;
|
|
25
|
-
/**
|
|
26
|
-
* Validate if the message incoming is completed
|
|
27
|
-
* @param buf - ByteBuffer
|
|
28
|
-
*/
|
|
29
|
-
function validate(buf) {
|
|
30
|
-
const rem = buf.remainingBytes();
|
|
31
|
-
if (rem < WireMessage.HEADER_LENGTH) {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
const messageLength = buf.getUint32(buf.currentReadOffset());
|
|
35
|
-
if (rem < messageLength) {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
WireMessage.validate = validate;
|
|
41
|
-
function readHeader(buf) {
|
|
42
|
-
if (!validate(buf)) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
return {
|
|
46
|
-
length: buf.readUint32(),
|
|
47
|
-
type: buf.readUint32()
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
WireMessage.readHeader = readHeader;
|
|
51
|
-
function getType(component, entity) {
|
|
52
|
-
return component.has(entity) ? Enum.PUT_COMPONENT : Enum.DELETE_COMPONENT;
|
|
53
|
-
}
|
|
54
|
-
WireMessage.getType = getType;
|
|
55
|
-
})(WireMessage || (WireMessage = {}));
|
|
56
|
-
export default WireMessage;
|