@dcl/ecs 7.0.5 → 7.0.6-3652441072.commit-39343cb
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.d.ts +1 -0
- package/dist/engine/component.js +25 -6
- package/dist/engine/index.js +38 -36
- package/dist/engine/types.d.ts +15 -9
- package/dist/serialization/wireMessage.d.ts +2 -0
- package/dist/serialization/wireMessage.js +4 -0
- package/dist/systems/crdt/index.d.ts +5 -3
- package/dist/systems/crdt/index.js +128 -80
- package/dist/systems/crdt/types.d.ts +2 -3
- package/package.json +3 -3
|
@@ -50,6 +50,7 @@ export declare type ComponentDefinition<T extends ISchema<ConstructorType>, Cons
|
|
|
50
50
|
*/
|
|
51
51
|
createOrReplace(entity: Entity, val?: ConstructorType): ComponentType<T>;
|
|
52
52
|
/**
|
|
53
|
+
* @public
|
|
53
54
|
* Delete the current component to an entity, return null if the entity doesn't have the current component.
|
|
54
55
|
* - Internal comment: This method adds the <entity,component> to the list to be reviewed next frame
|
|
55
56
|
* @param entity - Entity to delete the component from
|
package/dist/engine/component.js
CHANGED
|
@@ -36,10 +36,15 @@ export function defineComponent(componentId, spec, constructorDefault
|
|
|
36
36
|
has(entity) {
|
|
37
37
|
return data.has(entity);
|
|
38
38
|
},
|
|
39
|
-
deleteFrom(entity) {
|
|
39
|
+
deleteFrom(entity, markAsDirty = true) {
|
|
40
40
|
const component = data.get(entity);
|
|
41
41
|
data.delete(entity);
|
|
42
|
-
|
|
42
|
+
if (markAsDirty) {
|
|
43
|
+
dirtyIterator.add(entity);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
dirtyIterator.delete(entity);
|
|
47
|
+
}
|
|
43
48
|
return component || null;
|
|
44
49
|
},
|
|
45
50
|
getOrNull(entity) {
|
|
@@ -103,6 +108,15 @@ export function defineComponent(componentId, spec, constructorDefault
|
|
|
103
108
|
spec.serialize(component, writeBuffer);
|
|
104
109
|
return writeBuffer;
|
|
105
110
|
},
|
|
111
|
+
toBinaryOrNull(entity) {
|
|
112
|
+
const component = data.get(entity);
|
|
113
|
+
if (!component) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const writeBuffer = createByteBuffer();
|
|
117
|
+
spec.serialize(component, writeBuffer);
|
|
118
|
+
return writeBuffer;
|
|
119
|
+
},
|
|
106
120
|
writeToByteBuffer(entity, buffer) {
|
|
107
121
|
const component = data.get(entity);
|
|
108
122
|
if (!component) {
|
|
@@ -110,17 +124,22 @@ export function defineComponent(componentId, spec, constructorDefault
|
|
|
110
124
|
}
|
|
111
125
|
spec.serialize(component, buffer);
|
|
112
126
|
},
|
|
113
|
-
updateFromBinary(entity, buffer) {
|
|
127
|
+
updateFromBinary(entity, buffer, markAsDirty = true) {
|
|
114
128
|
const component = data.get(entity);
|
|
115
129
|
if (!component) {
|
|
116
130
|
throw new Error(`[updateFromBinary] Component ${componentId} for ${entity} not found`);
|
|
117
131
|
}
|
|
118
|
-
return this.upsertFromBinary(entity, buffer);
|
|
132
|
+
return this.upsertFromBinary(entity, buffer, markAsDirty);
|
|
119
133
|
},
|
|
120
|
-
upsertFromBinary(entity, buffer) {
|
|
134
|
+
upsertFromBinary(entity, buffer, markAsDirty = true) {
|
|
121
135
|
const newValue = spec.deserialize(buffer);
|
|
122
136
|
data.set(entity, newValue);
|
|
123
|
-
|
|
137
|
+
if (markAsDirty) {
|
|
138
|
+
dirtyIterator.add(entity);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
dirtyIterator.delete(entity);
|
|
142
|
+
}
|
|
124
143
|
return newValue;
|
|
125
144
|
},
|
|
126
145
|
clearDirty() {
|
package/dist/engine/index.js
CHANGED
|
@@ -33,6 +33,14 @@ function preEngine() {
|
|
|
33
33
|
}
|
|
34
34
|
return entityContainer.removeEntity(entity);
|
|
35
35
|
}
|
|
36
|
+
function registerCustomComponent(component, componentId) {
|
|
37
|
+
const prev = componentsDefinition.get(componentId);
|
|
38
|
+
if (prev) {
|
|
39
|
+
throw new Error(`Component number ${componentId} was already registered.`);
|
|
40
|
+
}
|
|
41
|
+
componentsDefinition.set(componentId, component);
|
|
42
|
+
return component;
|
|
43
|
+
}
|
|
36
44
|
function defineComponentFromSchema(spec, componentId, constructorDefault) {
|
|
37
45
|
const prev = componentsDefinition.get(componentId);
|
|
38
46
|
if (prev) {
|
|
@@ -84,6 +92,24 @@ function preEngine() {
|
|
|
84
92
|
function removeComponentDefinition(componentId) {
|
|
85
93
|
componentsDefinition.delete(componentId);
|
|
86
94
|
}
|
|
95
|
+
const Transform = components.Transform({ defineComponentFromSchema });
|
|
96
|
+
function* getTreeEntityArray(firstEntity, proccesedEntities) {
|
|
97
|
+
// This avoid infinite loop when there is a cyclic parenting
|
|
98
|
+
if (proccesedEntities.find((value) => firstEntity === value))
|
|
99
|
+
return;
|
|
100
|
+
proccesedEntities.push(firstEntity);
|
|
101
|
+
for (const [entity, value] of getEntitiesWith(Transform)) {
|
|
102
|
+
if (value.parent === firstEntity) {
|
|
103
|
+
yield* getTreeEntityArray(entity, proccesedEntities);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
yield firstEntity;
|
|
107
|
+
}
|
|
108
|
+
function removeEntityWithChildren(firstEntity) {
|
|
109
|
+
for (const entity of getTreeEntityArray(firstEntity, [])) {
|
|
110
|
+
removeEntity(entity);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
87
113
|
return {
|
|
88
114
|
entityExists,
|
|
89
115
|
componentsDefinition,
|
|
@@ -97,7 +123,9 @@ function preEngine() {
|
|
|
97
123
|
getEntitiesWith,
|
|
98
124
|
getComponent,
|
|
99
125
|
getComponentOrNull,
|
|
100
|
-
removeComponentDefinition
|
|
126
|
+
removeComponentDefinition,
|
|
127
|
+
removeEntityWithChildren,
|
|
128
|
+
registerCustomComponent
|
|
101
129
|
};
|
|
102
130
|
}
|
|
103
131
|
/**
|
|
@@ -106,55 +134,27 @@ function preEngine() {
|
|
|
106
134
|
export function Engine() {
|
|
107
135
|
const engine = preEngine();
|
|
108
136
|
const crdtSystem = crdtSceneSystem(engine);
|
|
109
|
-
function update(dt) {
|
|
110
|
-
crdtSystem.receiveMessages();
|
|
137
|
+
async function update(dt) {
|
|
138
|
+
await crdtSystem.receiveMessages();
|
|
111
139
|
for (const system of engine.getSystems()) {
|
|
112
140
|
const ret = system.fn(dt);
|
|
113
141
|
checkNotThenable(ret, `A system (${system.name || 'anonymous'}) returned a thenable. Systems cannot be async functions. Documentation: https://dcl.gg/sdk/sync-systems`);
|
|
114
142
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// to iterate all the component definitions to get the dirty ones ?
|
|
118
|
-
const dirtySet = new Map();
|
|
119
|
-
for (const [componentId, definition] of engine.componentsDefinition) {
|
|
120
|
-
for (const entity of definition.dirtyIterator()) {
|
|
121
|
-
if (!dirtySet.has(entity)) {
|
|
122
|
-
dirtySet.set(entity, new Set());
|
|
123
|
-
}
|
|
124
|
-
dirtySet.get(entity).add(componentId);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
crdtSystem.createMessages(dirtySet);
|
|
143
|
+
const dirtyEntities = crdtSystem.updateState();
|
|
144
|
+
await crdtSystem.sendMessages(dirtyEntities);
|
|
128
145
|
for (const [_componentId, definition] of engine.componentsDefinition) {
|
|
129
146
|
definition.clearDirty();
|
|
130
147
|
}
|
|
131
148
|
}
|
|
132
|
-
const Transform = components.Transform(engine);
|
|
133
|
-
function* getTreeEntityArray(firstEntity, proccesedEntities) {
|
|
134
|
-
// This avoid infinite loop when there is a cyclic parenting
|
|
135
|
-
if (proccesedEntities.find((value) => firstEntity === value))
|
|
136
|
-
return;
|
|
137
|
-
proccesedEntities.push(firstEntity);
|
|
138
|
-
for (const [entity, value] of engine.getEntitiesWith(Transform)) {
|
|
139
|
-
if (value.parent === firstEntity) {
|
|
140
|
-
yield* getTreeEntityArray(entity, proccesedEntities);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
yield firstEntity;
|
|
144
|
-
}
|
|
145
|
-
function removeEntityWithChildren(firstEntity) {
|
|
146
|
-
for (const entity of getTreeEntityArray(firstEntity, [])) {
|
|
147
|
-
engine.removeEntity(entity);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
149
|
return {
|
|
151
150
|
addEntity: engine.addEntity,
|
|
152
151
|
removeEntity: engine.removeEntity,
|
|
153
|
-
removeEntityWithChildren,
|
|
152
|
+
removeEntityWithChildren: engine.removeEntityWithChildren,
|
|
154
153
|
addSystem: engine.addSystem,
|
|
155
154
|
removeSystem: engine.removeSystem,
|
|
156
155
|
defineComponent: engine.defineComponent,
|
|
157
156
|
defineComponentFromSchema: engine.defineComponentFromSchema,
|
|
157
|
+
registerCustomComponent: engine.registerCustomComponent,
|
|
158
158
|
getEntitiesWith: engine.getEntitiesWith,
|
|
159
159
|
getComponent: engine.getComponent,
|
|
160
160
|
getComponentOrNull: engine.getComponentOrNull,
|
|
@@ -164,6 +164,8 @@ export function Engine() {
|
|
|
164
164
|
PlayerEntity: 1,
|
|
165
165
|
CameraEntity: 2,
|
|
166
166
|
entityExists: engine.entityExists,
|
|
167
|
-
addTransport: crdtSystem.addTransport
|
|
167
|
+
addTransport: crdtSystem.addTransport,
|
|
168
|
+
getCrdtState: crdtSystem.getCrdt,
|
|
169
|
+
componentsDefinition: engine.componentsDefinition
|
|
168
170
|
};
|
|
169
171
|
}
|
package/dist/engine/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ISchema } from '../schemas/ISchema';
|
|
2
2
|
import { Result, Spec } from '../schemas/Map';
|
|
3
3
|
import { Transport } from '../systems/crdt/types';
|
|
4
|
-
import { ComponentDefinition
|
|
4
|
+
import { ComponentDefinition } from './component';
|
|
5
5
|
import { Entity } from './entity';
|
|
6
6
|
import { SystemFn } from './systems';
|
|
7
7
|
import { ReadonlyComponentSchema } from './readonly';
|
|
@@ -13,8 +13,8 @@ export declare type Unpacked<T> = T extends (infer U)[] ? U : T;
|
|
|
13
13
|
/**
|
|
14
14
|
* @public
|
|
15
15
|
*/
|
|
16
|
-
export declare type ComponentSchema<T extends [
|
|
17
|
-
[K in keyof T]: T[K] extends
|
|
16
|
+
export declare type ComponentSchema<T extends [ComponentDefinition<any>, ...ComponentDefinition<any>[]]> = {
|
|
17
|
+
[K in keyof T]: T[K] extends ComponentDefinition<any> ? ReturnType<T[K]['getMutable']> : never;
|
|
18
18
|
};
|
|
19
19
|
/**
|
|
20
20
|
* @public
|
|
@@ -66,6 +66,12 @@ export declare type IEngine = {
|
|
|
66
66
|
* @returns if it was found and removed
|
|
67
67
|
*/
|
|
68
68
|
removeSystem(selector: string | SystemFn): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Registers a custom component definition.
|
|
71
|
+
* @param component - The component definition
|
|
72
|
+
* @param componentId - unique id to identify the component, if the component id already exist, it will fail.
|
|
73
|
+
*/
|
|
74
|
+
registerCustomComponent<T extends ISchema, V>(component: ComponentDefinition<T, V>, componentId: number): ComponentDefinition<T, V>;
|
|
69
75
|
/**
|
|
70
76
|
* Define a component and add it to the engine.
|
|
71
77
|
* @param spec - An object with schema fields
|
|
@@ -82,7 +88,7 @@ export declare type IEngine = {
|
|
|
82
88
|
*
|
|
83
89
|
* ```
|
|
84
90
|
*/
|
|
85
|
-
defineComponent<T extends Spec, ConstructorType = Partial<Result<T>>>(spec: T, componentId: number, constructorDefault?: ConstructorType):
|
|
91
|
+
defineComponent<T extends Spec, ConstructorType = Partial<Result<T>>>(spec: T, componentId: number, constructorDefault?: ConstructorType): ComponentDefinition<ISchema<Result<T>>, Partial<Result<T>>>;
|
|
86
92
|
/**
|
|
87
93
|
* Define a component and add it to the engine.
|
|
88
94
|
* @param spec - An object with schema fields
|
|
@@ -94,7 +100,7 @@ export declare type IEngine = {
|
|
|
94
100
|
* const StateComponent = engine.defineComponent(Schemas.Bool, VisibleComponentId)
|
|
95
101
|
* ```
|
|
96
102
|
*/
|
|
97
|
-
defineComponentFromSchema<T extends ISchema<ConstructorType>, ConstructorType>(spec: T, componentId: number, constructorDefault?: ConstructorType):
|
|
103
|
+
defineComponentFromSchema<T extends ISchema<ConstructorType>, ConstructorType>(spec: T, componentId: number, constructorDefault?: ConstructorType): ComponentDefinition<T, ConstructorType>;
|
|
98
104
|
/**
|
|
99
105
|
* Get the component definition from the component id.
|
|
100
106
|
* @param componentId - component number used to identify the component descriptor
|
|
@@ -104,7 +110,7 @@ export declare type IEngine = {
|
|
|
104
110
|
* const StateComponent = engine.getComponent(StateComponentId)
|
|
105
111
|
* ```
|
|
106
112
|
*/
|
|
107
|
-
getComponent<T extends ISchema>(componentId: number):
|
|
113
|
+
getComponent<T extends ISchema>(componentId: number): ComponentDefinition<T>;
|
|
108
114
|
/**
|
|
109
115
|
* Get the component definition from the component id.
|
|
110
116
|
* @param componentId - component number used to identify the component descriptor
|
|
@@ -114,7 +120,7 @@ export declare type IEngine = {
|
|
|
114
120
|
* const StateComponent = engine.getComponent(StateComponentId)
|
|
115
121
|
* ```
|
|
116
122
|
*/
|
|
117
|
-
getComponentOrNull<T extends ISchema>(componentId: number):
|
|
123
|
+
getComponentOrNull<T extends ISchema>(componentId: number): ComponentDefinition<T> | null;
|
|
118
124
|
/**
|
|
119
125
|
* Get a iterator of entities that has all the component requested.
|
|
120
126
|
* @param components - a list of component definitions
|
|
@@ -127,11 +133,11 @@ export declare type IEngine = {
|
|
|
127
133
|
* }
|
|
128
134
|
* ```
|
|
129
135
|
*/
|
|
130
|
-
getEntitiesWith<T extends [
|
|
136
|
+
getEntitiesWith<T extends [ComponentDefinition<any>, ...ComponentDefinition<any>[]]>(...components: T): Iterable<[Entity, ...ReadonlyComponentSchema<T>]>;
|
|
131
137
|
/**
|
|
132
138
|
* @param deltaTime - deltaTime in seconds
|
|
133
139
|
*/
|
|
134
|
-
update(deltaTime: number): void
|
|
140
|
+
update(deltaTime: number): Promise<void>;
|
|
135
141
|
/**
|
|
136
142
|
* @public
|
|
137
143
|
* Refer to the root of the scene, all Transforms without a parent are parenting with RootEntity.
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* available to process the message
|
|
12
12
|
*
|
|
13
13
|
*/
|
|
14
|
+
import { ComponentDefinition, Entity, ISchema } from '../engine';
|
|
14
15
|
import { ByteBuffer } from './ByteBuffer';
|
|
15
16
|
export declare namespace WireMessage {
|
|
16
17
|
type Uint32 = number;
|
|
@@ -35,5 +36,6 @@ export declare namespace WireMessage {
|
|
|
35
36
|
*/
|
|
36
37
|
function validate(buf: ByteBuffer): boolean;
|
|
37
38
|
function readHeader(buf: ByteBuffer): Header | null;
|
|
39
|
+
function getType(component: ComponentDefinition<ISchema<unknown>, unknown>, entity: Entity): Enum;
|
|
38
40
|
}
|
|
39
41
|
export default WireMessage;
|
|
@@ -48,5 +48,9 @@ export var WireMessage;
|
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
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;
|
|
51
55
|
})(WireMessage || (WireMessage = {}));
|
|
52
56
|
export default WireMessage;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { IEngine } from '../../engine';
|
|
2
2
|
import { Entity } from '../../engine/entity';
|
|
3
3
|
import { Transport } from './types';
|
|
4
|
-
export declare function crdtSceneSystem(engine: Pick<IEngine, 'getComponentOrNull'>): {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
export declare function crdtSceneSystem(engine: Pick<IEngine, 'getComponentOrNull' | 'getComponent' | 'componentsDefinition'>): {
|
|
5
|
+
getCrdt: () => import("@dcl/crdt").State<Uint8Array>;
|
|
6
|
+
sendMessages: (dirtyEntities: Map<Entity, Set<number>>) => Promise<void>;
|
|
7
|
+
receiveMessages: () => Promise<void>;
|
|
7
8
|
addTransport: (transport: Transport) => void;
|
|
9
|
+
updateState: () => Map<unknown, Set<number>>;
|
|
8
10
|
};
|
|
@@ -9,14 +9,15 @@ export function crdtSceneSystem(engine) {
|
|
|
9
9
|
// Messages that we received at transport.onMessage waiting to be processed
|
|
10
10
|
const receivedMessages = [];
|
|
11
11
|
// Messages already processed by the engine but that we need to broadcast to other transports.
|
|
12
|
-
const
|
|
13
|
-
//
|
|
12
|
+
const broadcastMessages = [];
|
|
13
|
+
// Messages receieved by a transport that were outdated. We need to correct them
|
|
14
|
+
const outdatedMessages = [];
|
|
14
15
|
/**
|
|
15
16
|
*
|
|
16
|
-
* @param
|
|
17
|
+
* @param transportId tranport id to identiy messages
|
|
17
18
|
* @returns a function to process received messages
|
|
18
19
|
*/
|
|
19
|
-
function parseChunkMessage(
|
|
20
|
+
function parseChunkMessage(transportId) {
|
|
20
21
|
/**
|
|
21
22
|
* Receives a chunk of binary messages and stores all the valid
|
|
22
23
|
* Component Operation Messages at messages queue
|
|
@@ -36,7 +37,7 @@ export function crdtSceneSystem(engine) {
|
|
|
36
37
|
componentId,
|
|
37
38
|
data,
|
|
38
39
|
timestamp,
|
|
39
|
-
|
|
40
|
+
transportId,
|
|
40
41
|
messageBuffer: buffer
|
|
41
42
|
.buffer()
|
|
42
43
|
.subarray(offset, buffer.currentReadOffset())
|
|
@@ -50,93 +51,117 @@ export function crdtSceneSystem(engine) {
|
|
|
50
51
|
* @returns messages recieved by the transport to process on the next tick
|
|
51
52
|
*/
|
|
52
53
|
function getMessages(value) {
|
|
53
|
-
const messagesToProcess =
|
|
54
|
-
value.length = 0;
|
|
54
|
+
const messagesToProcess = value.splice(0, value.length);
|
|
55
55
|
return messagesToProcess;
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
58
|
* This fn will be called on every tick.
|
|
59
59
|
* Process all the messages queue received by the transport
|
|
60
60
|
*/
|
|
61
|
-
function receiveMessages() {
|
|
61
|
+
async function receiveMessages() {
|
|
62
62
|
const messagesToProcess = getMessages(receivedMessages);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
63
|
+
const bufferForOutdated = createByteBuffer();
|
|
64
|
+
for (const message of messagesToProcess) {
|
|
65
|
+
const { data, timestamp, componentId, entity, type } = message;
|
|
66
|
+
const crdtMessage = {
|
|
67
|
+
key1: entity,
|
|
68
|
+
key2: componentId,
|
|
69
|
+
data: data || null,
|
|
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())
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// Add message to transport queue to be processed by others transports
|
|
97
|
+
broadcastMessages.push(message);
|
|
98
|
+
// Process CRDT Message
|
|
99
|
+
if (type === WireMessage.Enum.DELETE_COMPONENT) {
|
|
100
|
+
component.deleteFrom(entity, false);
|
|
85
101
|
}
|
|
86
102
|
else {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const opts = {
|
|
93
|
-
reading: { buffer: message.data, currentOffset: 0 }
|
|
94
|
-
};
|
|
95
|
-
const bb = createByteBuffer(opts);
|
|
96
|
-
// Update engine component
|
|
97
|
-
component.upsertFromBinary(message.entity, bb);
|
|
98
|
-
component.clearDirty();
|
|
99
|
-
}
|
|
100
|
-
// Add message to transport queue to be processed by others transports
|
|
101
|
-
transportMessages.push(message);
|
|
103
|
+
const opts = {
|
|
104
|
+
reading: { buffer: message.data, currentOffset: 0 }
|
|
105
|
+
};
|
|
106
|
+
const data = createByteBuffer(opts);
|
|
107
|
+
component.upsertFromBinary(message.entity, data, false);
|
|
102
108
|
}
|
|
103
109
|
}
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function getDirtyMap() {
|
|
113
|
+
const dirtySet = new Map();
|
|
114
|
+
for (const [componentId, definition] of engine.componentsDefinition) {
|
|
115
|
+
for (const entity of definition.dirtyIterator()) {
|
|
116
|
+
if (!dirtySet.has(entity)) {
|
|
117
|
+
dirtySet.set(entity, new Set());
|
|
118
|
+
}
|
|
119
|
+
dirtySet.get(entity).add(componentId);
|
|
106
120
|
}
|
|
107
121
|
}
|
|
122
|
+
return dirtySet;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Updates CRDT state of the current engine dirty components
|
|
126
|
+
*/
|
|
127
|
+
function updateState() {
|
|
128
|
+
const dirtyEntities = getDirtyMap();
|
|
129
|
+
for (const [entity, componentsId] of getDirtyMap()) {
|
|
130
|
+
for (const componentId of componentsId) {
|
|
131
|
+
const component = engine.getComponent(componentId);
|
|
132
|
+
const componentValue = component.toBinaryOrNull(entity)?.toBinary() ?? null;
|
|
133
|
+
crdtClient.createEvent(entity, componentId, componentValue);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return dirtyEntities;
|
|
108
137
|
}
|
|
109
138
|
/**
|
|
110
139
|
* Iterates the dirty map and generates crdt messages to be send
|
|
111
|
-
* @param dirtyMap a map of { entities: [componentId] }
|
|
112
140
|
*/
|
|
113
|
-
function
|
|
141
|
+
async function sendMessages(dirtyEntities) {
|
|
114
142
|
// CRDT Messages will be the merge between the recieved transport messages and the new crdt messages
|
|
115
|
-
const crdtMessages = getMessages(
|
|
143
|
+
const crdtMessages = getMessages(broadcastMessages);
|
|
144
|
+
const outdatedMessagesBkp = getMessages(outdatedMessages);
|
|
116
145
|
const buffer = createByteBuffer();
|
|
117
|
-
for (const [entity, componentsId] of
|
|
146
|
+
for (const [entity, componentsId] of dirtyEntities) {
|
|
118
147
|
for (const componentId of componentsId) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
? component.toBinary(entity).toBinary()
|
|
126
|
-
: null;
|
|
127
|
-
const event = crdtClient.createEvent(entity, componentId, entityComponent);
|
|
148
|
+
// Component will be always defined here since dirtyMap its an iterator of engine.componentsDefinition
|
|
149
|
+
const component = engine.getComponent(componentId);
|
|
150
|
+
const { timestamp } = crdtClient
|
|
151
|
+
.getState()
|
|
152
|
+
.get(entity)
|
|
153
|
+
.get(componentId);
|
|
128
154
|
const offset = buffer.currentWriteOffset();
|
|
129
|
-
const type =
|
|
130
|
-
? WireMessage.Enum.PUT_COMPONENT
|
|
131
|
-
: WireMessage.Enum.DELETE_COMPONENT;
|
|
155
|
+
const type = WireMessage.getType(component, entity);
|
|
132
156
|
const transportMessage = {
|
|
133
157
|
type,
|
|
134
158
|
componentId,
|
|
135
159
|
entity,
|
|
136
|
-
timestamp
|
|
160
|
+
timestamp
|
|
137
161
|
};
|
|
162
|
+
// Avoid creating messages if there is no transport that will handle it
|
|
138
163
|
if (transports.some((t) => t.filter(transportMessage))) {
|
|
139
|
-
Message.write(type, entity,
|
|
164
|
+
Message.write(type, entity, timestamp, component, buffer);
|
|
140
165
|
crdtMessages.push({
|
|
141
166
|
...transportMessage,
|
|
142
167
|
messageBuffer: buffer
|
|
@@ -146,32 +171,55 @@ export function crdtSceneSystem(engine) {
|
|
|
146
171
|
}
|
|
147
172
|
}
|
|
148
173
|
}
|
|
149
|
-
// Send messages to transports
|
|
174
|
+
// Send CRDT messages to transports
|
|
150
175
|
const transportBuffer = createByteBuffer();
|
|
151
|
-
for (const
|
|
176
|
+
for (const index in transports) {
|
|
177
|
+
const transportIndex = Number(index);
|
|
178
|
+
const transport = transports[transportIndex];
|
|
152
179
|
transportBuffer.resetBuffer();
|
|
153
|
-
|
|
154
|
-
|
|
180
|
+
// First we need to send all the messages that were outdated from a transport
|
|
181
|
+
// So we can fix their crdt state
|
|
182
|
+
for (const message of outdatedMessagesBkp) {
|
|
183
|
+
if (message.transportId === transportIndex &&
|
|
184
|
+
// Avoid sending multiple messages for the same entity-componentId
|
|
185
|
+
!crdtMessages.find((m) => m.entity === message.entity &&
|
|
186
|
+
m.componentId === message.componentId)) {
|
|
155
187
|
transportBuffer.writeBuffer(message.messageBuffer, false);
|
|
156
188
|
}
|
|
157
189
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
190
|
+
// Then we send all the new crdtMessages that the transport needs to process
|
|
191
|
+
for (const message of crdtMessages) {
|
|
192
|
+
if (message.transportId !== transportIndex &&
|
|
193
|
+
transport.filter(message)) {
|
|
194
|
+
transportBuffer.writeBuffer(message.messageBuffer, false);
|
|
195
|
+
}
|
|
163
196
|
}
|
|
197
|
+
const message = transportBuffer.size()
|
|
198
|
+
? transportBuffer.toBinary()
|
|
199
|
+
: new Uint8Array([]);
|
|
200
|
+
await transport.send(message);
|
|
164
201
|
}
|
|
165
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* @public
|
|
205
|
+
* Add a transport to the crdt system
|
|
206
|
+
*/
|
|
166
207
|
function addTransport(transport) {
|
|
167
|
-
transports.push(transport);
|
|
168
|
-
transport.onmessage = parseChunkMessage(
|
|
169
|
-
|
|
170
|
-
|
|
208
|
+
const id = transports.push(transport) - 1;
|
|
209
|
+
transport.onmessage = parseChunkMessage(id);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* @public
|
|
213
|
+
* @returns returns the crdt state
|
|
214
|
+
*/
|
|
215
|
+
function getCrdt() {
|
|
216
|
+
return crdtClient.getState();
|
|
171
217
|
}
|
|
172
218
|
return {
|
|
173
|
-
|
|
219
|
+
getCrdt,
|
|
220
|
+
sendMessages,
|
|
174
221
|
receiveMessages,
|
|
175
|
-
addTransport
|
|
222
|
+
addTransport,
|
|
223
|
+
updateState
|
|
176
224
|
};
|
|
177
225
|
}
|
|
@@ -5,14 +5,13 @@ export declare type ReceiveMessage = {
|
|
|
5
5
|
entity: Entity;
|
|
6
6
|
componentId: number;
|
|
7
7
|
timestamp: number;
|
|
8
|
-
|
|
8
|
+
transportId?: number;
|
|
9
9
|
data?: Uint8Array;
|
|
10
10
|
messageBuffer: Uint8Array;
|
|
11
11
|
};
|
|
12
12
|
export declare type TransportMessage = Omit<ReceiveMessage, 'data'>;
|
|
13
13
|
export declare type Transport = {
|
|
14
|
-
|
|
15
|
-
send(message: Uint8Array): void;
|
|
14
|
+
send(message: Uint8Array): Promise<void>;
|
|
16
15
|
onmessage?(message: Uint8Array): void;
|
|
17
16
|
filter(message: Omit<TransportMessage, 'messageBuffer'>): boolean;
|
|
18
17
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dcl/ecs",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.6-3652441072.commit-39343cb",
|
|
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.
|
|
30
|
+
"@dcl/crdt": "7.0.6-3652441072.commit-39343cb",
|
|
31
31
|
"@dcl/js-runtime": "file:../js-runtime",
|
|
32
32
|
"@dcl/protocol": "^1.0.0-3603890942.commit-44633cf"
|
|
33
33
|
},
|
|
@@ -35,5 +35,5 @@
|
|
|
35
35
|
"dist",
|
|
36
36
|
"etc"
|
|
37
37
|
],
|
|
38
|
-
"commit": "
|
|
38
|
+
"commit": "39343cb1de3fa2beb4f7cd3955f044216a2be085"
|
|
39
39
|
}
|