@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.
@@ -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 &lt;entity,component&gt; to the list to be reviewed next frame
55
56
  * @param entity - Entity to delete the component from
@@ -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
- dirtyIterator.add(entity);
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
- dirtyIterator.add(entity);
137
+ if (markAsDirty) {
138
+ dirtyIterator.add(entity);
139
+ }
140
+ else {
141
+ dirtyIterator.delete(entity);
142
+ }
124
143
  return newValue;
125
144
  },
126
145
  clearDirty() {
@@ -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
- // TODO: Perf tip
116
- // Should we add some dirtyIteratorSet at engine level so we dont have
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
  }
@@ -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 as CompDef } from './component';
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 [CompDef<any>, ...CompDef<any>[]]> = {
17
- [K in keyof T]: T[K] extends CompDef<any> ? ReturnType<T[K]['getMutable']> : never;
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): CompDef<ISchema<Result<T>>, Partial<Result<T>>>;
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): CompDef<T, 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): CompDef<T>;
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): CompDef<T> | null;
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 [CompDef<any>, ...CompDef<any>[]]>(...components: T): Iterable<[Entity, ...ReadonlyComponentSchema<T>]>;
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
- createMessages: (dirtyMap: Map<Entity, Set<number>>) => void;
6
- receiveMessages: () => void;
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 transportMessages = [];
13
- // Map of entities already processed at least once
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 transportType tranport id to identiy messages
17
+ * @param transportId tranport id to identiy messages
17
18
  * @returns a function to process received messages
18
19
  */
19
- function parseChunkMessage(transportType) {
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
- transportType,
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 = Array.from(value);
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
- for (const transport of transports) {
64
- const buffer = createByteBuffer();
65
- for (const message of messagesToProcess) {
66
- const { data, timestamp, componentId, entity, type } = message;
67
- const crdtMessage = {
68
- key1: entity,
69
- key2: componentId,
70
- data: data || null,
71
- timestamp: timestamp
72
- };
73
- const component = engine.getComponentOrNull(componentId);
74
- const current = crdtClient.processMessage(crdtMessage);
75
- /* istanbul ignore next */
76
- if (!component)
77
- // TODO: TEST
78
- continue;
79
- // CRDT outdated message. Resend this message through the wire
80
- if (crdtMessage !== current) {
81
- const type = component.has(entity)
82
- ? WireMessage.Enum.PUT_COMPONENT
83
- : WireMessage.Enum.DELETE_COMPONENT;
84
- Message.write(type, entity, current.timestamp, component, buffer);
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
- // Process CRDT Message
88
- if (type === WireMessage.Enum.DELETE_COMPONENT) {
89
- component.deleteFrom(entity);
90
- }
91
- else {
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
- if (buffer.size()) {
105
- transport.send(buffer.toBinary());
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 createMessages(dirtyMap) {
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(transportMessages);
143
+ const crdtMessages = getMessages(broadcastMessages);
144
+ const outdatedMessagesBkp = getMessages(outdatedMessages);
116
145
  const buffer = createByteBuffer();
117
- for (const [entity, componentsId] of dirtyMap) {
146
+ for (const [entity, componentsId] of dirtyEntities) {
118
147
  for (const componentId of componentsId) {
119
- const component = engine.getComponentOrNull(componentId);
120
- /* istanbul ignore next */
121
- if (!component)
122
- // TODO: test coverage
123
- continue;
124
- const entityComponent = component.has(entity)
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 = component.has(entity)
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: event.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, event.timestamp, component, buffer);
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 transport of transports) {
176
+ for (const index in transports) {
177
+ const transportIndex = Number(index);
178
+ const transport = transports[transportIndex];
152
179
  transportBuffer.resetBuffer();
153
- for (const message of crdtMessages) {
154
- if (transport.filter(message)) {
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
- if (transportBuffer.size()) {
159
- transport.send(transportBuffer.toBinary());
160
- }
161
- else {
162
- transport.send(new Uint8Array([]));
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(transport.type);
169
- // TODO: pull messages from transport
170
- // TODO: send entities to transport
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
- createMessages,
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
- transportType?: string;
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
- type: string;
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.5",
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.5",
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": "ad4efbcb7b87a23d25c26ca96f80d3ff795fcd9d"
38
+ "commit": "39343cb1de3fa2beb4f7cd3955f044216a2be085"
39
39
  }