@dcl/ecs 7.0.6-3824930139.commit-9bd6c05 → 7.0.6-3832097301.commit-4e46b20
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/component.js +3 -3
- 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/ByteBuffer/index.d.ts +67 -31
- package/dist/serialization/ByteBuffer/index.js +217 -231
- 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 -80
- 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,13 @@
|
|
|
1
|
+
import { ComponentDefinition } from '../../engine/component';
|
|
2
|
+
import { Entity } from '../../engine/entity';
|
|
3
|
+
import { ByteBuffer } from '../ByteBuffer';
|
|
4
|
+
import { PutComponentMessage } from './types';
|
|
5
|
+
export declare namespace PutComponentOperation {
|
|
6
|
+
const MESSAGE_HEADER_LENGTH = 20;
|
|
7
|
+
/**
|
|
8
|
+
* Call this function for an optimal writing data passing the ByteBuffer
|
|
9
|
+
* already allocated
|
|
10
|
+
*/
|
|
11
|
+
function write(entity: Entity, timestamp: number, componentDefinition: ComponentDefinition<unknown>, buf: ByteBuffer): void;
|
|
12
|
+
function read(buf: ByteBuffer): PutComponentMessage | null;
|
|
13
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import CrdtMessageProtocol from '.';
|
|
2
|
+
import { CrdtMessageType, CRDT_MESSAGE_HEADER_LENGTH } from './types';
|
|
3
|
+
export var PutComponentOperation;
|
|
4
|
+
(function (PutComponentOperation) {
|
|
5
|
+
PutComponentOperation.MESSAGE_HEADER_LENGTH = 20;
|
|
6
|
+
/**
|
|
7
|
+
* Call this function for an optimal writing data passing the ByteBuffer
|
|
8
|
+
* already allocated
|
|
9
|
+
*/
|
|
10
|
+
function write(entity, timestamp, componentDefinition, buf) {
|
|
11
|
+
// reserve the beginning
|
|
12
|
+
const startMessageOffset = buf.incrementWriteOffset(CRDT_MESSAGE_HEADER_LENGTH + PutComponentOperation.MESSAGE_HEADER_LENGTH);
|
|
13
|
+
// write body
|
|
14
|
+
componentDefinition.writeToByteBuffer(entity, buf);
|
|
15
|
+
const messageLength = buf.currentWriteOffset() - startMessageOffset;
|
|
16
|
+
// Write CrdtMessage header
|
|
17
|
+
buf.setUint32(startMessageOffset, messageLength);
|
|
18
|
+
buf.setUint32(startMessageOffset + 4, CrdtMessageType.PUT_COMPONENT);
|
|
19
|
+
// Write ComponentOperation header
|
|
20
|
+
buf.setUint32(startMessageOffset + 8, entity);
|
|
21
|
+
buf.setUint32(startMessageOffset + 12, componentDefinition._id);
|
|
22
|
+
buf.setUint64(startMessageOffset + 16, BigInt(timestamp));
|
|
23
|
+
const newLocal = messageLength - PutComponentOperation.MESSAGE_HEADER_LENGTH - CRDT_MESSAGE_HEADER_LENGTH;
|
|
24
|
+
buf.setUint32(startMessageOffset + 24, newLocal);
|
|
25
|
+
}
|
|
26
|
+
PutComponentOperation.write = write;
|
|
27
|
+
function read(buf) {
|
|
28
|
+
const header = CrdtMessageProtocol.readHeader(buf);
|
|
29
|
+
if (!header) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (header.type !== CrdtMessageType.PUT_COMPONENT) {
|
|
33
|
+
throw new Error('PutComponentOperation tried to read another message type.');
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
...header,
|
|
37
|
+
entityId: buf.readUint32(),
|
|
38
|
+
componentId: buf.readInt32(),
|
|
39
|
+
timestamp: Number(buf.readUint64()),
|
|
40
|
+
data: buf.readBuffer()
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
PutComponentOperation.read = read;
|
|
44
|
+
})(PutComponentOperation || (PutComponentOperation = {}));
|
|
@@ -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 {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
2
|
+
import { CRDTMessageType, ProcessMessageResultType } from '@dcl/crdt/dist/types';
|
|
3
|
+
import { EntityState, EntityUtils } from '../../engine/entity';
|
|
4
|
+
import { ReadWriteByteBuffer } from '../../serialization/ByteBuffer';
|
|
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.
|
|
@@ -24,24 +32,48 @@ export function crdtSceneSystem(engine, onProcessEntityComponentChange) {
|
|
|
24
32
|
* @param chunkMessage A chunk of binary messages
|
|
25
33
|
*/
|
|
26
34
|
return function parseChunkMessage(chunkMessage) {
|
|
27
|
-
const buffer =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
while (WireMessage.validate(buffer)) {
|
|
35
|
+
const buffer = new ReadWriteByteBuffer(chunkMessage);
|
|
36
|
+
let header;
|
|
37
|
+
while ((header = CrdtMessageProtocol.getHeader(buffer))) {
|
|
31
38
|
const offset = buffer.currentReadOffset();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
if (header.type === CrdtMessageType.DELETE_COMPONENT) {
|
|
40
|
+
const message = DeleteComponent.read(buffer);
|
|
41
|
+
receivedMessages.push({
|
|
42
|
+
...header,
|
|
43
|
+
...message,
|
|
44
|
+
transportId,
|
|
45
|
+
messageBuffer: buffer
|
|
46
|
+
.buffer()
|
|
47
|
+
.subarray(offset, buffer.currentReadOffset())
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
else if (header.type === CrdtMessageType.PUT_COMPONENT) {
|
|
51
|
+
const message = PutComponentOperation.read(buffer);
|
|
52
|
+
receivedMessages.push({
|
|
53
|
+
...header,
|
|
54
|
+
...message,
|
|
55
|
+
transportId,
|
|
56
|
+
messageBuffer: buffer
|
|
57
|
+
.buffer()
|
|
58
|
+
.subarray(offset, buffer.currentReadOffset())
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else if (header.type === CrdtMessageType.DELETE_ENTITY) {
|
|
62
|
+
const message = DeleteEntity.read(buffer);
|
|
63
|
+
receivedMessages.push({
|
|
64
|
+
...header,
|
|
65
|
+
...message,
|
|
66
|
+
transportId,
|
|
67
|
+
messageBuffer: buffer
|
|
68
|
+
.buffer()
|
|
69
|
+
.subarray(offset, buffer.currentReadOffset())
|
|
70
|
+
});
|
|
71
|
+
// Unknown message, we skip it
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// consume the message
|
|
75
|
+
buffer.incrementReadOffset(header.length);
|
|
76
|
+
}
|
|
45
77
|
}
|
|
46
78
|
// TODO: do something if buffler.len>0
|
|
47
79
|
};
|
|
@@ -60,56 +92,105 @@ export function crdtSceneSystem(engine, onProcessEntityComponentChange) {
|
|
|
60
92
|
*/
|
|
61
93
|
async function receiveMessages() {
|
|
62
94
|
const messagesToProcess = getMessages(receivedMessages);
|
|
63
|
-
const bufferForOutdated =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
timestamp: timestamp
|
|
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())
|
|
95
|
+
const bufferForOutdated = new ReadWriteByteBuffer();
|
|
96
|
+
const entitiesShouldBeCleaned = [];
|
|
97
|
+
for (const msg of messagesToProcess) {
|
|
98
|
+
if (msg.type === CrdtMessageType.DELETE_ENTITY) {
|
|
99
|
+
crdtClient.processMessage({
|
|
100
|
+
type: CRDTMessageType.CRDTMT_DeleteEntity,
|
|
101
|
+
entityId: msg.entityId
|
|
93
102
|
});
|
|
103
|
+
entitiesShouldBeCleaned.push(msg.entityId);
|
|
94
104
|
}
|
|
95
105
|
else {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
const crdtMessage = {
|
|
107
|
+
type: CRDTMessageType.CRDTMT_PutComponentData,
|
|
108
|
+
entityId: msg.entityId,
|
|
109
|
+
componentId: msg.componentId,
|
|
110
|
+
data: msg.type === CrdtMessageType.PUT_COMPONENT ? msg.data : null,
|
|
111
|
+
timestamp: msg.timestamp
|
|
112
|
+
};
|
|
113
|
+
const entityState = engine.entityContainer.getEntityState(msg.entityId);
|
|
114
|
+
// Skip updates from removed entityes
|
|
115
|
+
if (entityState === EntityState.Removed)
|
|
116
|
+
continue;
|
|
117
|
+
// Entities with unknown entities should update its entity state
|
|
118
|
+
if (entityState === EntityState.Unknown) {
|
|
119
|
+
engine.entityContainer.updateUsedEntity(msg.entityId);
|
|
101
120
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
121
|
+
const component = engine.getComponentOrNull(msg.componentId);
|
|
122
|
+
// The state isn't updated because the dirty was set
|
|
123
|
+
// out of the block of systems update between `receiveMessage` and `updateState`
|
|
124
|
+
if (component?.isDirty(msg.entityId)) {
|
|
125
|
+
crdtClient.createComponentDataEvent(component._id, msg.entityId, component.toBinaryOrNull(msg.entityId)?.toBinary() || null);
|
|
126
|
+
}
|
|
127
|
+
const processResult = crdtClient.processMessage(crdtMessage);
|
|
128
|
+
if (!component) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
switch (processResult) {
|
|
132
|
+
case ProcessMessageResultType.StateUpdatedTimestamp:
|
|
133
|
+
case ProcessMessageResultType.StateUpdatedData:
|
|
134
|
+
// Add message to transport queue to be processed by others transports
|
|
135
|
+
broadcastMessages.push(msg);
|
|
136
|
+
// Process CRDT Message
|
|
137
|
+
if (msg.type === CrdtMessageType.DELETE_COMPONENT) {
|
|
138
|
+
component.deleteFrom(msg.entityId, false);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
const data = new ReadWriteByteBuffer(msg.data);
|
|
142
|
+
component.upsertFromBinary(msg.entityId, data, false);
|
|
143
|
+
}
|
|
144
|
+
onProcessEntityComponentChange &&
|
|
145
|
+
onProcessEntityComponentChange(msg.entityId, msg.type, component);
|
|
146
|
+
break;
|
|
147
|
+
// CRDT outdated message. Resend this message to the transport
|
|
148
|
+
// To do this we add this message to a queue that will be processed at the end of the update tick
|
|
149
|
+
case ProcessMessageResultType.StateOutdatedData:
|
|
150
|
+
case ProcessMessageResultType.StateOutdatedTimestamp:
|
|
151
|
+
const current = crdtClient
|
|
152
|
+
.getState()
|
|
153
|
+
.components.get(msg.componentId)
|
|
154
|
+
?.get(msg.entityId);
|
|
155
|
+
if (current) {
|
|
156
|
+
const offset = bufferForOutdated.currentWriteOffset();
|
|
157
|
+
const ts = current.timestamp;
|
|
158
|
+
if (component.has(msg.entityId)) {
|
|
159
|
+
PutComponentOperation.write(msg.entityId, ts, component, bufferForOutdated);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
DeleteComponent.write(msg.entityId, component._id, ts, bufferForOutdated);
|
|
163
|
+
}
|
|
164
|
+
outdatedMessages.push({
|
|
165
|
+
...msg,
|
|
166
|
+
messageBuffer: bufferForOutdated
|
|
167
|
+
.buffer()
|
|
168
|
+
.subarray(offset, bufferForOutdated.currentWriteOffset())
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
case ProcessMessageResultType.NoChanges:
|
|
173
|
+
case ProcessMessageResultType.EntityDeleted:
|
|
174
|
+
case ProcessMessageResultType.EntityWasDeleted:
|
|
175
|
+
default:
|
|
176
|
+
break;
|
|
108
177
|
}
|
|
109
|
-
onProcessEntityComponentChange &&
|
|
110
|
-
onProcessEntityComponentChange(entity, component, type);
|
|
111
178
|
}
|
|
112
179
|
}
|
|
180
|
+
for (const entity of entitiesShouldBeCleaned) {
|
|
181
|
+
// If we tried to resend outdated message and the entity was deleted before, we avoid sending them.
|
|
182
|
+
for (let i = outdatedMessages.length - 1; i >= 0; i--) {
|
|
183
|
+
if (outdatedMessages[i].entityId === entity) {
|
|
184
|
+
outdatedMessages.splice(i, 1);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
for (const definition of engine.componentsIter()) {
|
|
188
|
+
definition.deleteFrom(entity, false);
|
|
189
|
+
}
|
|
190
|
+
engine.entityContainer.updateRemovedEntity(entity);
|
|
191
|
+
onProcessEntityComponentChange &&
|
|
192
|
+
onProcessEntityComponentChange(entity, CrdtMessageType.DELETE_ENTITY);
|
|
193
|
+
}
|
|
113
194
|
}
|
|
114
195
|
/**
|
|
115
196
|
* Updates CRDT state of the current engine dirty components
|
|
@@ -126,15 +207,20 @@ export function crdtSceneSystem(engine, onProcessEntityComponentChange) {
|
|
|
126
207
|
entitySet = [];
|
|
127
208
|
dirtyMap.set(component, entitySet);
|
|
128
209
|
}
|
|
129
|
-
entitySet.push(entity);
|
|
130
210
|
// TODO: reuse shared writer to prevent extra allocations of toBinary
|
|
131
211
|
const componentValue = component.toBinaryOrNull(entity)?.toBinary() ?? null;
|
|
132
212
|
// TODO: do not emit event if componentValue equals the value didn't change
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
213
|
+
// if update goes bad, the entity doesn't accept put anymore (it's added to deleted entities set)
|
|
214
|
+
if (crdtClient.createComponentDataEvent(component._id, entity, componentValue) === null) {
|
|
215
|
+
component.deleteFrom(entity, false);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
entitySet.push(entity);
|
|
219
|
+
onProcessEntityComponentChange &&
|
|
220
|
+
onProcessEntityComponentChange(entity, componentValue === null
|
|
221
|
+
? CrdtMessageType.DELETE_COMPONENT
|
|
222
|
+
: CrdtMessageType.PUT_COMPONENT, component);
|
|
223
|
+
}
|
|
138
224
|
}
|
|
139
225
|
}
|
|
140
226
|
return dirtyMap;
|
|
@@ -142,29 +228,36 @@ export function crdtSceneSystem(engine, onProcessEntityComponentChange) {
|
|
|
142
228
|
/**
|
|
143
229
|
* Iterates the dirty map and generates crdt messages to be send
|
|
144
230
|
*/
|
|
145
|
-
async function sendMessages(dirtyEntities) {
|
|
231
|
+
async function sendMessages(dirtyEntities, deletedEntities) {
|
|
146
232
|
// CRDT Messages will be the merge between the recieved transport messages and the new crdt messages
|
|
147
233
|
const crdtMessages = getMessages(broadcastMessages);
|
|
148
234
|
const outdatedMessagesBkp = getMessages(outdatedMessages);
|
|
149
|
-
const buffer =
|
|
235
|
+
const buffer = new ReadWriteByteBuffer();
|
|
150
236
|
for (const [component, entities] of dirtyEntities) {
|
|
151
237
|
for (const entity of entities) {
|
|
152
238
|
// Component will be always defined here since dirtyMap its an iterator of engine.componentsDefinition
|
|
153
239
|
const { timestamp } = crdtClient
|
|
154
240
|
.getState()
|
|
155
|
-
.get(
|
|
156
|
-
.get(
|
|
241
|
+
.components.get(component._id)
|
|
242
|
+
.get(entity);
|
|
157
243
|
const offset = buffer.currentWriteOffset();
|
|
158
|
-
const type =
|
|
244
|
+
const type = component.has(entity)
|
|
245
|
+
? CrdtMessageType.PUT_COMPONENT
|
|
246
|
+
: CrdtMessageType.DELETE_COMPONENT;
|
|
159
247
|
const transportMessage = {
|
|
160
248
|
type,
|
|
249
|
+
entityId: entity,
|
|
161
250
|
componentId: component._id,
|
|
162
|
-
entity,
|
|
163
251
|
timestamp
|
|
164
252
|
};
|
|
165
253
|
// Avoid creating messages if there is no transport that will handle it
|
|
166
254
|
if (transports.some((t) => t.filter(transportMessage))) {
|
|
167
|
-
|
|
255
|
+
if (transportMessage.type === CrdtMessageType.PUT_COMPONENT) {
|
|
256
|
+
PutComponentOperation.write(entity, timestamp, component, buffer);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
DeleteComponent.write(entity, component._id, timestamp, buffer);
|
|
260
|
+
}
|
|
168
261
|
crdtMessages.push({
|
|
169
262
|
...transportMessage,
|
|
170
263
|
messageBuffer: buffer
|
|
@@ -174,8 +267,21 @@ export function crdtSceneSystem(engine, onProcessEntityComponentChange) {
|
|
|
174
267
|
}
|
|
175
268
|
}
|
|
176
269
|
}
|
|
270
|
+
// After all updates, I execute the DeletedEntity messages
|
|
271
|
+
for (const entityId of deletedEntities) {
|
|
272
|
+
crdtClient.createDeleteEntityEvent(entityId);
|
|
273
|
+
const offset = buffer.currentWriteOffset();
|
|
274
|
+
DeleteEntity.write(entityId, buffer);
|
|
275
|
+
crdtMessages.push({
|
|
276
|
+
type: CrdtMessageType.DELETE_ENTITY,
|
|
277
|
+
entityId,
|
|
278
|
+
messageBuffer: buffer
|
|
279
|
+
.buffer()
|
|
280
|
+
.subarray(offset, buffer.currentWriteOffset())
|
|
281
|
+
});
|
|
282
|
+
}
|
|
177
283
|
// Send CRDT messages to transports
|
|
178
|
-
const transportBuffer =
|
|
284
|
+
const transportBuffer = new ReadWriteByteBuffer();
|
|
179
285
|
for (const index in transports) {
|
|
180
286
|
const transportIndex = Number(index);
|
|
181
287
|
const transport = transports[transportIndex];
|
|
@@ -184,8 +290,11 @@ export function crdtSceneSystem(engine, onProcessEntityComponentChange) {
|
|
|
184
290
|
// So we can fix their crdt state
|
|
185
291
|
for (const message of outdatedMessagesBkp) {
|
|
186
292
|
if (message.transportId === transportIndex &&
|
|
293
|
+
// TODO: This is an optimization, the state should converge anyway, whatever the message is sent.
|
|
187
294
|
// Avoid sending multiple messages for the same entity-componentId
|
|
188
|
-
!crdtMessages.find((m) => m.
|
|
295
|
+
!crdtMessages.find((m) => m.entityId === message.entityId &&
|
|
296
|
+
// TODO: as any, with multiple type of messages, it should have many checks before the check for similar messages
|
|
297
|
+
m.componentId &&
|
|
189
298
|
m.componentId === message.componentId)) {
|
|
190
299
|
transportBuffer.writeBuffer(message.messageBuffer, false);
|
|
191
300
|
}
|
|
@@ -197,7 +306,7 @@ export function crdtSceneSystem(engine, onProcessEntityComponentChange) {
|
|
|
197
306
|
transportBuffer.writeBuffer(message.messageBuffer, false);
|
|
198
307
|
}
|
|
199
308
|
}
|
|
200
|
-
const message = transportBuffer.
|
|
309
|
+
const message = transportBuffer.currentWriteOffset()
|
|
201
310
|
? transportBuffer.toBinary()
|
|
202
311
|
: new Uint8Array([]);
|
|
203
312
|
await transport.send(message);
|
|
@@ -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-3832097301.commit-4e46b20",
|
|
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-3832097301.commit-4e46b20",
|
|
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": "4e46b208355a0a0d340a899c44f98bebb94d0c8b"
|
|
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 = {}));
|