@dcl/ecs 7.28.0 → 7.28.1-34624845556.commit-7d0808b

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.
Files changed (51) hide show
  1. package/dist/components/index.d.ts +5 -0
  2. package/dist/components/index.js +5 -2
  3. package/dist/components/manual/CreatedBy.d.ts +9 -0
  4. package/dist/components/manual/CreatedBy.js +8 -0
  5. package/dist/components/manual/Transform.d.ts +9 -0
  6. package/dist/components/manual/Transform.js +3 -3
  7. package/dist/components/types.d.ts +1 -0
  8. package/dist/engine/component.d.ts +52 -1
  9. package/dist/engine/grow-only-value-set-component-definition.js +45 -2
  10. package/dist/engine/lww-element-set-component-definition.d.ts +3 -1
  11. package/dist/engine/lww-element-set-component-definition.js +68 -13
  12. package/dist/index.d.ts +2 -1
  13. package/dist/index.js +1 -0
  14. package/dist/serialization/crdt/authoritativePutComponent.d.ts +15 -0
  15. package/dist/serialization/crdt/authoritativePutComponent.js +47 -0
  16. package/dist/serialization/crdt/index.d.ts +1 -0
  17. package/dist/serialization/crdt/index.js +1 -0
  18. package/dist/serialization/crdt/types.d.ts +25 -3
  19. package/dist/serialization/crdt/types.js +3 -1
  20. package/dist/systems/crdt/index.d.ts +0 -1
  21. package/dist/systems/crdt/index.js +76 -143
  22. package/dist/systems/crdt/types.d.ts +10 -0
  23. package/dist/systems/events.js +47 -18
  24. package/dist-cjs/components/index.d.ts +5 -0
  25. package/dist-cjs/components/index.js +7 -3
  26. package/dist-cjs/components/manual/CreatedBy.d.ts +9 -0
  27. package/dist-cjs/components/manual/CreatedBy.js +10 -0
  28. package/dist-cjs/components/manual/Transform.d.ts +9 -0
  29. package/dist-cjs/components/manual/Transform.js +3 -3
  30. package/dist-cjs/components/types.d.ts +1 -0
  31. package/dist-cjs/engine/component.d.ts +52 -1
  32. package/dist-cjs/engine/grow-only-value-set-component-definition.js +44 -1
  33. package/dist-cjs/engine/lww-element-set-component-definition.d.ts +3 -1
  34. package/dist-cjs/engine/lww-element-set-component-definition.js +71 -14
  35. package/dist-cjs/index.d.ts +2 -1
  36. package/dist-cjs/index.js +2 -1
  37. package/dist-cjs/serialization/crdt/authoritativePutComponent.d.ts +15 -0
  38. package/dist-cjs/serialization/crdt/authoritativePutComponent.js +50 -0
  39. package/dist-cjs/serialization/crdt/index.d.ts +1 -0
  40. package/dist-cjs/serialization/crdt/index.js +1 -0
  41. package/dist-cjs/serialization/crdt/types.d.ts +25 -3
  42. package/dist-cjs/serialization/crdt/types.js +3 -1
  43. package/dist-cjs/systems/crdt/index.d.ts +0 -1
  44. package/dist-cjs/systems/crdt/index.js +76 -166
  45. package/dist-cjs/systems/crdt/types.d.ts +10 -0
  46. package/dist-cjs/systems/events.js +47 -18
  47. package/package.json +2 -2
  48. package/dist/serialization/crdt/network/utils.d.ts +0 -9
  49. package/dist/serialization/crdt/network/utils.js +0 -60
  50. package/dist-cjs/serialization/crdt/network/utils.d.ts +0 -9
  51. package/dist-cjs/serialization/crdt/network/utils.js +0 -67
@@ -1,50 +1,20 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
2
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.crdtSceneSystem = exports.LIVEKIT_MAX_SIZE = void 0;
3
+ exports.crdtSceneSystem = void 0;
27
4
  const entity_1 = require("../../engine/entity");
28
5
  const ByteBuffer_1 = require("../../serialization/ByteBuffer");
29
6
  const crdt_1 = require("../../serialization/crdt");
30
7
  const deleteComponent_1 = require("../../serialization/crdt/deleteComponent");
31
8
  const deleteEntity_1 = require("../../serialization/crdt/deleteEntity");
32
9
  const putComponent_1 = require("../../serialization/crdt/putComponent");
10
+ const authoritativePutComponent_1 = require("../../serialization/crdt/authoritativePutComponent");
33
11
  const types_1 = require("../../serialization/crdt/types");
34
- const putComponentNetwork_1 = require("../../serialization/crdt/network/putComponentNetwork");
35
- const components_1 = require("../../components");
36
- const networkUtils = __importStar(require("../../serialization/crdt/network/utils"));
37
- // NetworkMessages can only have a MAX_SIZE of 12kb. So we need to send it in chunks.
38
- exports.LIVEKIT_MAX_SIZE = 12;
39
12
  /**
40
13
  * @internal
41
14
  */
42
15
  function crdtSceneSystem(engine, onProcessEntityComponentChange) {
43
16
  const transports = [];
44
- // Components that we used on this system
45
- const NetworkEntity = (0, components_1.NetworkEntity)(engine);
46
- const NetworkParent = (0, components_1.NetworkParent)(engine);
47
- const Transform = (0, components_1.Transform)(engine);
17
+ // No network components needed - pure CRDT processing only
48
18
  // Messages that we received at transport.onMessage waiting to be processed
49
19
  const receivedMessages = [];
50
20
  // Messages already processed by the engine but that we need to broadcast to other transports.
@@ -69,27 +39,20 @@ function crdtSceneSystem(engine, onProcessEntityComponentChange) {
69
39
  if (header.type === types_1.CrdtMessageType.DELETE_COMPONENT) {
70
40
  message = deleteComponent_1.DeleteComponent.read(buffer);
71
41
  }
72
- else if (header.type === types_1.CrdtMessageType.DELETE_COMPONENT_NETWORK) {
73
- message = crdt_1.DeleteComponentNetwork.read(buffer);
74
- }
75
42
  else if (header.type === types_1.CrdtMessageType.PUT_COMPONENT) {
76
43
  message = putComponent_1.PutComponentOperation.read(buffer);
77
44
  }
78
- else if (header.type === types_1.CrdtMessageType.PUT_COMPONENT_NETWORK) {
79
- message = putComponentNetwork_1.PutNetworkComponentOperation.read(buffer);
45
+ else if (header.type === types_1.CrdtMessageType.AUTHORITATIVE_PUT_COMPONENT) {
46
+ message = authoritativePutComponent_1.AuthoritativePutComponentOperation.read(buffer);
80
47
  }
81
48
  else if (header.type === types_1.CrdtMessageType.DELETE_ENTITY) {
82
49
  message = deleteEntity_1.DeleteEntity.read(buffer);
83
50
  }
84
- else if (header.type === types_1.CrdtMessageType.DELETE_ENTITY_NETWORK) {
85
- message = crdt_1.DeleteEntityNetwork.read(buffer);
86
- }
87
51
  else if (header.type === types_1.CrdtMessageType.APPEND_VALUE) {
88
52
  message = crdt_1.AppendValueOperation.read(buffer);
89
- // Unknown message, we skip it
90
53
  }
91
54
  else {
92
- // consume the message
55
+ // Unknown message, we skip it (including NETWORK messages)
93
56
  buffer.incrementReadOffset(header.length);
94
57
  }
95
58
  if (message) {
@@ -111,20 +74,12 @@ function crdtSceneSystem(engine, onProcessEntityComponentChange) {
111
74
  return messagesToProcess;
112
75
  }
113
76
  /**
114
- * Find the local entityId associated to the network component message.
115
- * It's a mapping Network -> to Local
116
- * If it's not a network message, return the entityId received by the message
77
+ * Reserved-range entities (root/player/camera + avatars, entity number <
78
+ * RESERVED_STATIC_ENTITIES) are host-owned: only a transport flagged with
79
+ * `allowReservedEntities` (the host/renderer) may mutate or delete them.
117
80
  */
118
- function findNetworkId(msg) {
119
- const hasNetworkId = 'networkId' in msg;
120
- if (hasNetworkId) {
121
- for (const [entityId, network] of engine.getEntitiesWith(NetworkEntity)) {
122
- if (network.networkId === msg.networkId && network.entityId === msg.entityId) {
123
- return { entityId, network };
124
- }
125
- }
126
- }
127
- return { entityId: msg.entityId };
81
+ function isTrustedForReservedEntities(transportId) {
82
+ return transportId !== undefined && !!transports[transportId]?.allowReservedEntities;
128
83
  }
129
84
  /**
130
85
  * This fn will be called on every tick.
@@ -134,46 +89,68 @@ function crdtSceneSystem(engine, onProcessEntityComponentChange) {
134
89
  const messagesToProcess = getMessages(receivedMessages);
135
90
  const entitiesShouldBeCleaned = [];
136
91
  for (const msg of messagesToProcess) {
137
- let { entityId, network } = findNetworkId(msg);
138
- // We receive a new Entity. Create the localEntity and map it to the NetworkEntity component
139
- if (networkUtils.isNetworkMessage(msg) && !network) {
140
- entityId = engine.addEntity();
141
- network = { entityId: msg.entityId, networkId: msg.networkId };
142
- NetworkEntity.createOrReplace(entityId, network);
143
- }
144
- if (msg.type === types_1.CrdtMessageType.DELETE_ENTITY || msg.type === types_1.CrdtMessageType.DELETE_ENTITY_NETWORK) {
145
- entitiesShouldBeCleaned.push(entityId);
92
+ // Simple CRDT processing - no network logic
93
+ if (msg.type === types_1.CrdtMessageType.DELETE_ENTITY) {
94
+ // Reject an inbound DELETE_ENTITY on a reserved entity from an untrusted
95
+ // transport (a comms peer, or a scene-added transport). The allocator half
96
+ // is already guarded (engine.removeEntity is a no-op on this range); this
97
+ // closes the receive-side half.
98
+ const isReserved = engine.entityContainer.getEntityState(msg.entityId) === entity_1.EntityState.Reserved;
99
+ if (isReserved && !isTrustedForReservedEntities(msg.transportId)) {
100
+ continue;
101
+ }
102
+ entitiesShouldBeCleaned.push(msg.entityId);
146
103
  broadcastMessages.push(msg);
147
104
  }
148
105
  else {
149
- const entityState = engine.entityContainer.getEntityState(entityId);
150
- // Skip updates from removed entityes
106
+ const entityState = engine.entityContainer.getEntityState(msg.entityId);
107
+ // Skip updates from removed entities
151
108
  if (entityState === entity_1.EntityState.Removed)
152
109
  continue;
153
- // Entities with unknown entities should update its entity state
110
+ // Reject component operations (PUT/DELETE/APPEND, including AUTHORITATIVE_PUT
111
+ // which bypasses timestamps entirely) on reserved entities from untrusted
112
+ // transports. Dropping before updateFromCrdt keeps local LWW timestamps
113
+ // unpoisoned by forged high timestamps, and also prevents the
114
+ // unknown-component rebroadcast below from relaying forged reserved-entity
115
+ // data to other transports (including the renderer).
116
+ if (entityState === entity_1.EntityState.Reserved && !isTrustedForReservedEntities(msg.transportId))
117
+ continue;
118
+ // Entities with unknown state should update its entity state
154
119
  if (entityState === entity_1.EntityState.Unknown) {
155
- engine.entityContainer.updateUsedEntity(entityId);
156
- }
157
- const component = engine.getComponentOrNull(msg.componentId);
158
- /* istanbul ignore else */
159
- if (component) {
160
- if (msg.type === types_1.CrdtMessageType.PUT_COMPONENT &&
161
- component.componentId === Transform.componentId &&
162
- NetworkEntity.has(entityId) &&
163
- NetworkParent.has(entityId)) {
164
- msg.data = networkUtils.fixTransformParent(msg);
120
+ engine.entityContainer.updateUsedEntity(msg.entityId);
121
+ }
122
+ // Only process component-related messages (not DELETE_ENTITY)
123
+ if ('componentId' in msg) {
124
+ const component = engine.getComponentOrNull(msg.componentId);
125
+ if (component) {
126
+ // Handle authoritative messages differently - they force the state regardless of timestamp
127
+ const tryUpdate = () => {
128
+ try {
129
+ return msg.type === types_1.CrdtMessageType.AUTHORITATIVE_PUT_COMPONENT
130
+ ? component.__forceUpdateFromCrdt(msg)
131
+ : component.updateFromCrdt(msg);
132
+ }
133
+ catch (e) {
134
+ console.error('[receiveMessages] ERROR processing message', msg, e);
135
+ return null;
136
+ }
137
+ };
138
+ const result = tryUpdate();
139
+ if (!result)
140
+ continue;
141
+ const [conflictMessage, value] = result;
142
+ if (!conflictMessage) {
143
+ // Add message to broadcast queue when no conflict
144
+ broadcastMessages.push(msg);
145
+ onProcessEntityComponentChange && onProcessEntityComponentChange(msg.entityId, msg.type, component, value);
146
+ }
165
147
  }
166
- const [conflictMessage, value] = component.updateFromCrdt({ ...msg, entityId });
167
- if (!conflictMessage) {
168
- // Add message to transport queue to be processed by others transports
148
+ else {
149
+ // Component not found - still broadcast for editor compatibility
150
+ /* istanbul ignore next */
169
151
  broadcastMessages.push(msg);
170
- onProcessEntityComponentChange && onProcessEntityComponentChange(entityId, msg.type, component, value);
171
152
  }
172
153
  }
173
- else {
174
- // TODO: test this line, it is fundammental to make the editor work
175
- broadcastMessages.push(msg);
176
- }
177
154
  }
178
155
  }
179
156
  // the last stage of the syncrhonization is to delete the entities
@@ -187,16 +164,17 @@ function crdtSceneSystem(engine, onProcessEntityComponentChange) {
187
164
  }
188
165
  }
189
166
  /**
190
- * Iterates the dirty map and generates crdt messages to be send
167
+ * Simple CRDT message broadcasting - no network-specific logic
191
168
  */
192
169
  async function sendMessages(entitiesDeletedThisTick) {
193
- // CRDT Messages will be the merge between the recieved transport messages and the new crdt messages
170
+ // Get messages from broadcast queue and component updates
194
171
  const crdtMessages = getMessages(broadcastMessages);
195
172
  const buffer = new ByteBuffer_1.ReadWriteByteBuffer();
173
+ // Generate CRDT messages from component updates
196
174
  for (const component of engine.componentsIter()) {
197
175
  for (const message of component.getCrdtUpdates()) {
198
176
  const offset = buffer.currentWriteOffset();
199
- // Avoid creating messages if there is no transport that will handle it
177
+ // Only create messages if there's a transport that will handle it
200
178
  if (transports.some((t) => t.filter(message))) {
201
179
  if (message.type === types_1.CrdtMessageType.PUT_COMPONENT) {
202
180
  putComponent_1.PutComponentOperation.write(message.entityId, message.timestamp, message.componentId, message.data, buffer);
@@ -220,7 +198,7 @@ function crdtSceneSystem(engine, onProcessEntityComponentChange) {
220
198
  }
221
199
  }
222
200
  }
223
- // After all updates, I execute the DeletedEntity messages
201
+ // Handle deleted entities
224
202
  for (const entityId of entitiesDeletedThisTick) {
225
203
  const offset = buffer.currentWriteOffset();
226
204
  deleteEntity_1.DeleteEntity.write(entityId, buffer);
@@ -231,87 +209,19 @@ function crdtSceneSystem(engine, onProcessEntityComponentChange) {
231
209
  });
232
210
  onProcessEntityComponentChange && onProcessEntityComponentChange(entityId, types_1.CrdtMessageType.DELETE_ENTITY);
233
211
  }
234
- // Send CRDT messages to transports
235
- const transportBuffer = new ByteBuffer_1.ReadWriteByteBuffer();
236
- for (const index in transports) {
237
- const __NetworkMessagesBuffer = [];
238
- const transportIndex = Number(index);
239
- const transport = transports[transportIndex];
240
- const isRendererTransport = transport.type === 'renderer';
241
- const isNetworkTransport = transport.type === 'network';
242
- // Reset Buffer for each Transport
243
- transportBuffer.resetBuffer();
244
- const buffer = new ByteBuffer_1.ReadWriteByteBuffer();
245
- // Then we send all the new crdtMessages that the transport needs to process
212
+ // Simple transport broadcasting - no network-specific transforms
213
+ for (const transport of transports) {
214
+ const transportBuffer = new ByteBuffer_1.ReadWriteByteBuffer();
246
215
  for (const message of crdtMessages) {
247
216
  // Avoid echo messages
248
- if (message.transportId === transportIndex)
249
- continue;
250
- // Redundant message for the transport
251
- if (!transport.filter(message))
252
- continue;
253
- // Check if adding this message would exceed the size limit
254
- const currentBufferSize = transportBuffer.toBinary().byteLength;
255
- const messageSize = message.messageBuffer.byteLength;
256
- if (isNetworkTransport && (currentBufferSize + messageSize) / 1024 > exports.LIVEKIT_MAX_SIZE) {
257
- // If the current buffer has content, save it as a chunk
258
- if (currentBufferSize > 0) {
259
- __NetworkMessagesBuffer.push(transportBuffer.toCopiedBinary());
260
- transportBuffer.resetBuffer();
261
- }
262
- // If the message itself is larger than the limit, we need to handle it specially
263
- // For now, we'll skip it to prevent infinite loops
264
- if (messageSize / 1024 > exports.LIVEKIT_MAX_SIZE) {
265
- console.error(`Message too large (${messageSize} bytes), skipping message for entity ${message.entityId}`);
266
- continue;
267
- }
268
- }
269
- const { entityId } = findNetworkId(message);
270
- const transformNeedsFix = 'componentId' in message &&
271
- message.componentId === Transform.componentId &&
272
- Transform.has(entityId) &&
273
- NetworkParent.has(entityId) &&
274
- NetworkEntity.has(entityId);
275
- // If there was a LOCAL change in the transform. Add the parent to that transform
276
- if (isRendererTransport && message.type === types_1.CrdtMessageType.PUT_COMPONENT && transformNeedsFix) {
277
- const parent = findNetworkId(NetworkParent.get(entityId));
278
- const transformData = networkUtils.fixTransformParent(message, Transform.get(entityId), parent.entityId);
279
- const offset = buffer.currentWriteOffset();
280
- putComponent_1.PutComponentOperation.write(entityId, message.timestamp, message.componentId, transformData, buffer);
281
- transportBuffer.writeBuffer(buffer.buffer().subarray(offset, buffer.currentWriteOffset()), false);
217
+ if (message.transportId === transports.indexOf(transport))
282
218
  continue;
219
+ // Check if transport wants this message
220
+ if (transport.filter(message)) {
221
+ transportBuffer.writeBuffer(message.messageBuffer, false);
283
222
  }
284
- if (isRendererTransport && networkUtils.isNetworkMessage(message)) {
285
- // If it's the renderer transport and its a NetworkMessage, we need to fix the entityId field and convert it to a known Message.
286
- // PUT_NETWORK_COMPONENT -> PUT_COMPONENT
287
- let transformData = 'data' in message ? message.data : new Uint8Array();
288
- if (transformNeedsFix) {
289
- const parent = findNetworkId(NetworkParent.get(entityId));
290
- transformData = networkUtils.fixTransformParent(message, Transform.get(entityId), parent.entityId);
291
- }
292
- networkUtils.networkMessageToLocal({ ...message, data: transformData }, entityId, buffer, transportBuffer);
293
- // Iterate the next message
294
- continue;
295
- }
296
- // If its a network transport and its a PUT_COMPONENT that has a NetworkEntity component, we need to send this message
297
- // through comms with the EntityID and NetworkID from ther NetworkEntity so everyone can recieve this message and map to their custom entityID.
298
- if (isNetworkTransport && !networkUtils.isNetworkMessage(message)) {
299
- const networkData = NetworkEntity.getOrNull(message.entityId);
300
- // If it has networkData convert the message to PUT_NETWORK_COMPONENT.
301
- if (networkData) {
302
- networkUtils.localMessageToNetwork(message, networkData, buffer, transportBuffer);
303
- // Iterate the next message
304
- continue;
305
- }
306
- }
307
- // Common message
308
- transportBuffer.writeBuffer(message.messageBuffer, false);
309
- }
310
- if (isNetworkTransport && transportBuffer.currentWriteOffset()) {
311
- __NetworkMessagesBuffer.push(transportBuffer.toBinary());
312
223
  }
313
- const message = isNetworkTransport ? __NetworkMessagesBuffer : transportBuffer.toBinary();
314
- await transport.send(message);
224
+ await transport.send(transportBuffer.toBinary());
315
225
  }
316
226
  }
317
227
  /**
@@ -29,4 +29,14 @@ export type Transport = {
29
29
  onmessage?(message: Uint8Array): void;
30
30
  filter(message: Omit<TransportMessage, 'messageBuffer'>): boolean;
31
31
  type?: string;
32
+ /**
33
+ * Whether this transport is allowed to mutate reserved-range entities
34
+ * (root/player/camera + avatars, entity number below `RESERVED_STATIC_ENTITIES`).
35
+ * Only the trusted host/renderer transport sets this. An inbound DELETE_ENTITY
36
+ * or component operation (PUT_COMPONENT, DELETE_COMPONENT, APPEND_VALUE,
37
+ * AUTHORITATIVE_PUT_COMPONENT) on a reserved entity from a transport without
38
+ * this flag (a comms peer, or any scene-added transport) is rejected — see
39
+ * `receiveMessages`.
40
+ */
41
+ allowReservedEntities?: boolean;
32
42
  };
@@ -52,6 +52,16 @@ function createPointerEventsSystem(engine, inputSystem) {
52
52
  function getEvent(entity) {
53
53
  return eventsMap.get(entity) || eventsMap.set(entity, new Map()).get(entity);
54
54
  }
55
+ // A cursor handler and a proximity handler are separate registrations that
56
+ // produce separate PointerEvents entries, so they need separate slots here
57
+ // too. Keying on the event type alone made onProximityDown evict whatever
58
+ // onPointerDown had left.
59
+ function eventKey(type, interactionType) {
60
+ return `${type}:${interactionType}`;
61
+ }
62
+ function setEvent(entity, type, cb, opts, interactionType = 0 /* InteractionType.CURSOR */) {
63
+ getEvent(entity).set(eventKey(type, interactionType), { cb, opts, eventType: type });
64
+ }
55
65
  function setPointerEvent(entity, type, opts, interactionType = 0 /* InteractionType.CURSOR */) {
56
66
  const pointerEvent = PointerEvents.getMutableOrNull(entity) || PointerEvents.create(entity);
57
67
  pointerEvent.pointerEvents.push({
@@ -69,13 +79,27 @@ function createPointerEventsSystem(engine, inputSystem) {
69
79
  interactionType: interactionType ?? 0 /* InteractionType.CURSOR */
70
80
  });
71
81
  }
72
- function removePointerEvent(entity, type, button, interactionType = 0 /* InteractionType.CURSOR */) {
82
+ function removePointerEvent(entity, type, opts, interactionType = 0 /* InteractionType.CURSOR */) {
73
83
  const pointerEvent = PointerEvents.getMutableOrNull(entity);
74
84
  if (!pointerEvent)
75
85
  return;
76
- pointerEvent.pointerEvents = pointerEvent.pointerEvents.filter((pointer) => !(pointer.eventInfo?.button === button &&
77
- pointer.eventType === type &&
78
- pointer.interactionType === interactionType));
86
+ const sameSlot = (pointer) => pointer.eventType === type && pointer.interactionType === interactionType;
87
+ // onClick and onPointerDown both describe themselves as PET_DOWN, so the event
88
+ // type and button alone do not say which registration an entry belongs to. Match
89
+ // everything the entry was built from, or removing one handler can take the
90
+ // other's descriptor and leave the renderer advertising the wrong interaction.
91
+ const index = pointerEvent.pointerEvents.findIndex((pointer) => sameSlot(pointer) &&
92
+ pointer.eventInfo?.button === opts.button &&
93
+ pointer.eventInfo?.hoverText === opts.hoverText &&
94
+ pointer.eventInfo?.showFeedback === opts.showFeedback &&
95
+ pointer.eventInfo?.showHighlight === opts.showHighlight &&
96
+ pointer.eventInfo?.maxDistance === opts.maxDistance &&
97
+ pointer.eventInfo?.maxPlayerDistance === opts.maxPlayerDistance &&
98
+ pointer.eventInfo?.priority === opts.priority &&
99
+ pointer.eventInfo?.maxCameraDistance === opts.maxCameraDistance);
100
+ if (index === -1)
101
+ return;
102
+ pointerEvent.pointerEvents.splice(index, 1);
79
103
  }
80
104
  function getPointerEvent(eventType) {
81
105
  if (eventType === EventType.Up) {
@@ -97,11 +121,16 @@ function createPointerEventsSystem(engine, inputSystem) {
97
121
  }
98
122
  function removeEvent(entity, type, interactionType = 0 /* InteractionType.CURSOR */) {
99
123
  const event = getEvent(entity);
100
- const pointerEvent = event.get(type);
101
- if (pointerEvent?.opts.hoverText) {
102
- removePointerEvent(entity, getPointerEvent(type), pointerEvent.opts.button);
124
+ const key = eventKey(type, interactionType);
125
+ const pointerEvent = event.get(key);
126
+ // Every registration adds an entry, not only the ones carrying a hoverText,
127
+ // so every removal has to take one away. Gating on hoverText left the
128
+ // renderer showing an interaction whose callback was already gone, and made
129
+ // re-registering grow the component by one entry each time.
130
+ if (pointerEvent) {
131
+ removePointerEvent(entity, getPointerEvent(type), pointerEvent.opts, interactionType);
103
132
  }
104
- event.delete(type);
133
+ event.delete(key);
105
134
  }
106
135
  engine.addSystem(function PointerEventSystem() {
107
136
  for (const [entity, event] of eventsMap) {
@@ -109,7 +138,7 @@ function createPointerEventsSystem(engine, inputSystem) {
109
138
  eventsMap.delete(entity);
110
139
  continue;
111
140
  }
112
- for (const [eventType, { cb, opts }] of event) {
141
+ for (const [, { cb, opts, eventType }] of event) {
113
142
  if (eventType === EventType.Click) {
114
143
  const command = inputSystem.getClick(opts.button, entity);
115
144
  if (command)
@@ -137,7 +166,7 @@ function createPointerEventsSystem(engine, inputSystem) {
137
166
  const { entity, opts } = data;
138
167
  const options = (0, exports.getDefaultOpts)(opts);
139
168
  removeEvent(entity, EventType.Down);
140
- getEvent(entity).set(EventType.Down, { cb, opts: options });
169
+ setEvent(entity, EventType.Down, cb, options);
141
170
  setPointerEvent(entity, 1 /* PointerEventType.PET_DOWN */, options);
142
171
  };
143
172
  const onPointerUp = (...args) => {
@@ -148,7 +177,7 @@ function createPointerEventsSystem(engine, inputSystem) {
148
177
  const { entity, opts } = data;
149
178
  const options = (0, exports.getDefaultOpts)(opts);
150
179
  removeEvent(entity, EventType.Up);
151
- getEvent(entity).set(EventType.Up, { cb, opts: options });
180
+ setEvent(entity, EventType.Up, cb, options);
152
181
  setPointerEvent(entity, 0 /* PointerEventType.PET_UP */, options);
153
182
  };
154
183
  const onPointerHoverEnter = (...args) => {
@@ -156,7 +185,7 @@ function createPointerEventsSystem(engine, inputSystem) {
156
185
  const { entity, opts } = data;
157
186
  const options = (0, exports.getDefaultOpts)(opts);
158
187
  removeEvent(entity, EventType.HoverEnter);
159
- getEvent(entity).set(EventType.HoverEnter, { cb, opts: options });
188
+ setEvent(entity, EventType.HoverEnter, cb, options);
160
189
  setPointerEvent(entity, 2 /* PointerEventType.PET_HOVER_ENTER */, options);
161
190
  };
162
191
  const onPointerHoverLeave = (...args) => {
@@ -164,7 +193,7 @@ function createPointerEventsSystem(engine, inputSystem) {
164
193
  const { entity, opts } = data;
165
194
  const options = (0, exports.getDefaultOpts)(opts);
166
195
  removeEvent(entity, EventType.HoverLeave);
167
- getEvent(entity).set(EventType.HoverLeave, { cb, opts: options });
196
+ setEvent(entity, EventType.HoverLeave, cb, options);
168
197
  setPointerEvent(entity, 3 /* PointerEventType.PET_HOVER_LEAVE */, options);
169
198
  };
170
199
  const onProximityDown = (...args) => {
@@ -172,7 +201,7 @@ function createPointerEventsSystem(engine, inputSystem) {
172
201
  const { entity, opts } = data;
173
202
  const options = (0, exports.getDefaultOpts)(opts);
174
203
  removeEvent(entity, EventType.Down, 1 /* InteractionType.PROXIMITY */);
175
- getEvent(entity).set(EventType.Down, { cb, opts: options });
204
+ setEvent(entity, EventType.Down, cb, options, 1 /* InteractionType.PROXIMITY */);
176
205
  setPointerEvent(entity, 1 /* PointerEventType.PET_DOWN */, options, 1 /* InteractionType.PROXIMITY */);
177
206
  };
178
207
  const onProximityUp = (...args) => {
@@ -180,7 +209,7 @@ function createPointerEventsSystem(engine, inputSystem) {
180
209
  const { entity, opts } = data;
181
210
  const options = (0, exports.getDefaultOpts)(opts);
182
211
  removeEvent(entity, EventType.Up, 1 /* InteractionType.PROXIMITY */);
183
- getEvent(entity).set(EventType.Up, { cb, opts: options });
212
+ setEvent(entity, EventType.Up, cb, options, 1 /* InteractionType.PROXIMITY */);
184
213
  setPointerEvent(entity, 0 /* PointerEventType.PET_UP */, options, 1 /* InteractionType.PROXIMITY */);
185
214
  };
186
215
  const onProximityEnter = (...args) => {
@@ -188,7 +217,7 @@ function createPointerEventsSystem(engine, inputSystem) {
188
217
  const { entity, opts } = data;
189
218
  const options = (0, exports.getDefaultOpts)(opts);
190
219
  removeEvent(entity, EventType.ProximityEnter, 1 /* InteractionType.PROXIMITY */);
191
- getEvent(entity).set(EventType.ProximityEnter, { cb, opts: options });
220
+ setEvent(entity, EventType.ProximityEnter, cb, options, 1 /* InteractionType.PROXIMITY */);
192
221
  setPointerEvent(entity, 4 /* PointerEventType.PET_PROXIMITY_ENTER */, options, 1 /* InteractionType.PROXIMITY */);
193
222
  };
194
223
  const onProximityLeave = (...args) => {
@@ -196,7 +225,7 @@ function createPointerEventsSystem(engine, inputSystem) {
196
225
  const { entity, opts } = data;
197
226
  const options = (0, exports.getDefaultOpts)(opts);
198
227
  removeEvent(entity, EventType.ProximityLeave, 1 /* InteractionType.PROXIMITY */);
199
- getEvent(entity).set(EventType.ProximityLeave, { cb, opts: options });
228
+ setEvent(entity, EventType.ProximityLeave, cb, options, 1 /* InteractionType.PROXIMITY */);
200
229
  setPointerEvent(entity, 5 /* PointerEventType.PET_PROXIMITY_LEAVE */, options, 1 /* InteractionType.PROXIMITY */);
201
230
  };
202
231
  return {
@@ -233,7 +262,7 @@ function createPointerEventsSystem(engine, inputSystem) {
233
262
  // Clear previous event with over feedback included
234
263
  removeEvent(entity, EventType.Click);
235
264
  // Set new event
236
- getEvent(entity).set(EventType.Click, { cb, opts: options });
265
+ setEvent(entity, EventType.Click, cb, options);
237
266
  setPointerEvent(entity, 1 /* PointerEventType.PET_DOWN */, options);
238
267
  },
239
268
  onPointerDown,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dcl/ecs",
3
3
  "description": "Decentraland ECS",
4
- "version": "7.28.0",
4
+ "version": "7.28.1-34624845556.commit-7d0808b",
5
5
  "author": "DCL",
6
6
  "bugs": "https://github.com/decentraland/ecs/issues",
7
7
  "files": [
@@ -34,5 +34,5 @@
34
34
  "dependencies": {},
35
35
  "types": "./dist/index.d.ts",
36
36
  "typings": "./dist/index.d.ts",
37
- "commit": "2a59cb6c6005b4f6b5c2391d6138778e85e1ae65"
37
+ "commit": "7d0808ba51c39d887e075b28eb5fe3b756ec1afd"
38
38
  }
@@ -1,9 +0,0 @@
1
- import { Entity } from '../../../engine';
2
- import { ReceiveMessage, TransformType } from '../../../runtime/types';
3
- import { ReceiveNetworkMessage } from '../../../systems/crdt/types';
4
- import { ByteBuffer } from '../../ByteBuffer';
5
- import { INetowrkEntityType } from '../../../components/types';
6
- export declare function isNetworkMessage(message: ReceiveMessage): message is ReceiveNetworkMessage;
7
- export declare function networkMessageToLocal(message: ReceiveNetworkMessage, localEntityId: Entity, buffer: ByteBuffer, destinationBuffer: ByteBuffer): void;
8
- export declare function localMessageToNetwork(message: ReceiveMessage, network: INetowrkEntityType, buffer: ByteBuffer, destinationBuffer: ByteBuffer): void;
9
- export declare function fixTransformParent(message: ReceiveMessage, transformValue?: TransformType, parent?: Entity): Uint8Array;
@@ -1,60 +0,0 @@
1
- import { ReadWriteByteBuffer } from '../../ByteBuffer';
2
- import { PutComponentOperation } from '../putComponent';
3
- import { CrdtMessageType } from '../types';
4
- import { DeleteComponent } from '../deleteComponent';
5
- import { DeleteEntity } from '../deleteEntity';
6
- import { PutNetworkComponentOperation } from './putComponentNetwork';
7
- import { DeleteComponentNetwork } from './deleteComponentNetwork';
8
- import { DeleteEntityNetwork } from './deleteEntityNetwork';
9
- import { TransformSchema } from '../../../components/manual/Transform';
10
- /* istanbul ignore next */
11
- export function isNetworkMessage(message) {
12
- return [
13
- CrdtMessageType.DELETE_COMPONENT_NETWORK,
14
- CrdtMessageType.DELETE_ENTITY_NETWORK,
15
- CrdtMessageType.PUT_COMPONENT_NETWORK
16
- ].includes(message.type);
17
- }
18
- /* istanbul ignore next */
19
- export function networkMessageToLocal(message, localEntityId, buffer, destinationBuffer) {
20
- const offset = buffer.currentWriteOffset();
21
- if (message.type === CrdtMessageType.PUT_COMPONENT_NETWORK) {
22
- PutComponentOperation.write(localEntityId, message.timestamp, message.componentId, message.data, buffer);
23
- }
24
- else if (message.type === CrdtMessageType.DELETE_COMPONENT_NETWORK) {
25
- DeleteComponent.write(localEntityId, message.componentId, message.timestamp, buffer);
26
- }
27
- else if (message.type === CrdtMessageType.DELETE_ENTITY_NETWORK) {
28
- DeleteEntity.write(localEntityId, buffer);
29
- }
30
- destinationBuffer.writeBuffer(buffer.buffer().subarray(offset, buffer.currentWriteOffset()), false);
31
- }
32
- /* istanbul ignore next */
33
- export function localMessageToNetwork(message, network, buffer, destinationBuffer) {
34
- const offset = buffer.currentWriteOffset();
35
- if (message.type === CrdtMessageType.PUT_COMPONENT) {
36
- PutNetworkComponentOperation.write(network.entityId, message.timestamp, message.componentId, network.networkId, message.data, buffer);
37
- }
38
- else if (message.type === CrdtMessageType.DELETE_COMPONENT) {
39
- DeleteComponentNetwork.write(network.entityId, message.componentId, message.timestamp, network.networkId, buffer);
40
- }
41
- else if (message.type === CrdtMessageType.DELETE_ENTITY) {
42
- DeleteEntityNetwork.write(network.entityId, network.networkId, buffer);
43
- }
44
- destinationBuffer.writeBuffer(buffer.buffer().subarray(offset, buffer.currentWriteOffset()), false);
45
- }
46
- const buffer = new ReadWriteByteBuffer();
47
- /* istanbul ignore next */
48
- export function fixTransformParent(message, transformValue, parent) {
49
- buffer.resetBuffer();
50
- let transform = transformValue;
51
- if (!transform && 'data' in message) {
52
- transform = TransformSchema.deserialize(new ReadWriteByteBuffer(message.data));
53
- }
54
- if (!transform)
55
- throw new Error('Invalid parent transform');
56
- // Generate new transform raw data with the parent
57
- const newTransform = { ...transform, parent };
58
- TransformSchema.serialize(newTransform, buffer);
59
- return buffer.toBinary();
60
- }
@@ -1,9 +0,0 @@
1
- import { Entity } from '../../../engine';
2
- import { ReceiveMessage, TransformType } from '../../../runtime/types';
3
- import { ReceiveNetworkMessage } from '../../../systems/crdt/types';
4
- import { ByteBuffer } from '../../ByteBuffer';
5
- import { INetowrkEntityType } from '../../../components/types';
6
- export declare function isNetworkMessage(message: ReceiveMessage): message is ReceiveNetworkMessage;
7
- export declare function networkMessageToLocal(message: ReceiveNetworkMessage, localEntityId: Entity, buffer: ByteBuffer, destinationBuffer: ByteBuffer): void;
8
- export declare function localMessageToNetwork(message: ReceiveMessage, network: INetowrkEntityType, buffer: ByteBuffer, destinationBuffer: ByteBuffer): void;
9
- export declare function fixTransformParent(message: ReceiveMessage, transformValue?: TransformType, parent?: Entity): Uint8Array;