@dcl/sdk 7.3.31-7169243942.commit-5f044c6 → 7.3.32-7170667436.commit-11a2181

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 (46) hide show
  1. package/internal/transports/logger.js +7 -4
  2. package/internal/transports/rendererTransport.js +3 -2
  3. package/message-bus.js +8 -6
  4. package/network/binary-message-bus.d.ts +18 -0
  5. package/network/binary-message-bus.js +68 -0
  6. package/network/entities.d.ts +11 -0
  7. package/network/entities.js +77 -0
  8. package/network/filter.d.ts +2 -0
  9. package/network/filter.js +37 -0
  10. package/network/index.d.ts +2 -0
  11. package/network/index.js +7 -0
  12. package/network/message-bus-sync.d.ts +17 -0
  13. package/network/message-bus-sync.js +52 -0
  14. package/network/state.js +39 -0
  15. package/network/utils.d.ts +18 -0
  16. package/network/utils.js +69 -0
  17. package/package.json +6 -6
  18. package/src/internal/transports/logger.ts +9 -4
  19. package/src/internal/transports/rendererTransport.ts +2 -1
  20. package/src/message-bus.ts +7 -9
  21. package/src/network/README.md +122 -0
  22. package/src/network/binary-message-bus.ts +72 -0
  23. package/src/network/entities.ts +132 -0
  24. package/src/network/filter.ts +64 -0
  25. package/src/network/index.ts +13 -0
  26. package/src/network/message-bus-sync.ts +97 -0
  27. package/src/network/state.ts +65 -0
  28. package/src/network/utils.ts +145 -0
  29. package/network-transport/client.d.ts +0 -5
  30. package/network-transport/client.js +0 -68
  31. package/network-transport/index.d.ts +0 -10
  32. package/network-transport/index.js +0 -29
  33. package/network-transport/server.d.ts +0 -2
  34. package/network-transport/server.js +0 -62
  35. package/network-transport/state.js +0 -11
  36. package/network-transport/types.d.ts +0 -36
  37. package/network-transport/types.js +0 -7
  38. package/network-transport/utils.d.ts +0 -8
  39. package/network-transport/utils.js +0 -46
  40. package/src/network-transport/client.ts +0 -77
  41. package/src/network-transport/index.ts +0 -45
  42. package/src/network-transport/server.ts +0 -66
  43. package/src/network-transport/state.ts +0 -13
  44. package/src/network-transport/types.ts +0 -41
  45. package/src/network-transport/utils.ts +0 -67
  46. /package/{network-transport → network}/state.d.ts +0 -0
@@ -0,0 +1,69 @@
1
+ import { EngineInfo as _EngineInfo, Schemas } from '@dcl/ecs';
2
+ import { componentNumberFromName } from '@dcl/ecs/dist/components/component-number';
3
+ export const definePlayersInScene = (engine) => engine.defineComponent('players-scene', {
4
+ timestamp: Schemas.Number,
5
+ userId: Schemas.String
6
+ });
7
+ export let stateInitialized = false;
8
+ export let playerSceneEntity;
9
+ export function setInitialized() {
10
+ stateInitialized = true;
11
+ }
12
+ export let INITIAL_CRDT_RENDERER_MESSAGES_SENT = false;
13
+ export function fetchProfile(myProfile, getUserData) {
14
+ void getUserData({}).then(({ data }) => {
15
+ if (data?.userId) {
16
+ const userId = data.userId;
17
+ const networkId = componentNumberFromName(data.userId);
18
+ myProfile.networkId = networkId;
19
+ myProfile.userId = userId;
20
+ }
21
+ else {
22
+ throw new Error(`Couldn't fetch profile data`);
23
+ }
24
+ });
25
+ }
26
+ export function createPlayerTimestampData(engine, profile, syncEntity) {
27
+ if (!profile?.userId)
28
+ return undefined;
29
+ const PlayersInScene = definePlayersInScene(engine);
30
+ const entity = engine.addEntity();
31
+ PlayersInScene.create(entity, { timestamp: Date.now(), userId: profile.userId });
32
+ syncEntity(entity, [PlayersInScene.componentId]);
33
+ playerSceneEntity = entity;
34
+ return playerSceneEntity;
35
+ }
36
+ export function oldestUser(engine, profile, syncEntity) {
37
+ const PlayersInScene = definePlayersInScene(engine);
38
+ if (!PlayersInScene.has(playerSceneEntity)) {
39
+ createPlayerTimestampData(engine, profile, syncEntity);
40
+ return oldestUser(engine, profile, syncEntity);
41
+ }
42
+ const { timestamp } = PlayersInScene.get(playerSceneEntity);
43
+ for (const [_, player] of engine.getEntitiesWith(PlayersInScene)) {
44
+ if (player.timestamp < timestamp)
45
+ return false;
46
+ }
47
+ return true;
48
+ }
49
+ export function syncTransportIsReady(engine) {
50
+ const EngineInfo = engine.getComponent(_EngineInfo.componentId);
51
+ if (!INITIAL_CRDT_RENDERER_MESSAGES_SENT) {
52
+ const engineInfo = EngineInfo.getOrNull(engine.RootEntity);
53
+ if (engineInfo && engineInfo.tickNumber > 2) {
54
+ INITIAL_CRDT_RENDERER_MESSAGES_SENT = true;
55
+ }
56
+ }
57
+ return INITIAL_CRDT_RENDERER_MESSAGES_SENT;
58
+ }
59
+ export function stateInitializedChecker(engine, _profile, _syncEntity) {
60
+ const EngineInfo = engine.getComponent(_EngineInfo.componentId);
61
+ async function enterScene() {
62
+ if ((EngineInfo.getOrNull(engine.RootEntity)?.tickNumber ?? 0) > 100) {
63
+ setInitialized();
64
+ return;
65
+ }
66
+ }
67
+ void enterScene();
68
+ }
69
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvbmV0d29yay91dGlscy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsVUFBVSxJQUFJLFdBQVcsRUFJekIsT0FBTyxFQUdSLE1BQU0sVUFBVSxDQUFBO0FBQ2pCLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxNQUFNLDJDQUEyQyxDQUFBO0FBUW5GLE1BQU0sQ0FBQyxNQUFNLG9CQUFvQixHQUFHLENBQUMsTUFBZSxFQUFFLEVBQUUsQ0FDdEQsTUFBTSxDQUFDLGVBQWUsQ0FBQyxlQUFlLEVBQUU7SUFDdEMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxNQUFNO0lBQ3pCLE1BQU0sRUFBRSxPQUFPLENBQUMsTUFBTTtDQUN2QixDQUFDLENBQUE7QUFHSixNQUFNLENBQUMsSUFBSSxnQkFBZ0IsR0FBRyxLQUFLLENBQUE7QUFHbkMsTUFBTSxDQUFDLElBQUksaUJBQXlCLENBQUE7QUFFcEMsTUFBTSxVQUFVLGNBQWM7SUFDNUIsZ0JBQWdCLEdBQUcsSUFBSSxDQUFBO0FBQ3pCLENBQUM7QUFJRCxNQUFNLENBQUMsSUFBSSxtQ0FBbUMsR0FBRyxLQUFLLENBQUE7QUFHdEQsTUFBTSxVQUFVLFlBQVksQ0FDMUIsU0FBbUIsRUFDbkIsV0FBd0U7SUFFeEUsS0FBSyxXQUFXLENBQUMsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsRUFBRSxJQUFJLEVBQUUsRUFBRSxFQUFFO1FBQ3JDLElBQUksSUFBSSxFQUFFLE1BQU0sRUFBRTtZQUNoQixNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFBO1lBQzFCLE1BQU0sU0FBUyxHQUFHLHVCQUF1QixDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQTtZQUN0RCxTQUFTLENBQUMsU0FBUyxHQUFHLFNBQVMsQ0FBQTtZQUMvQixTQUFTLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQTtTQUMxQjthQUFNO1lBQ0wsTUFBTSxJQUFJLEtBQUssQ0FBQyw2QkFBNkIsQ0FBQyxDQUFBO1NBQy9DO0lBQ0gsQ0FBQyxDQUFDLENBQUE7QUFDSixDQUFDO0FBTUQsTUFBTSxVQUFVLHlCQUF5QixDQUFDLE1BQWUsRUFBRSxPQUFpQixFQUFFLFVBQXNCO0lBQ2xHLElBQUksQ0FBQyxPQUFPLEVBQUUsTUFBTTtRQUFFLE9BQU8sU0FBUyxDQUFBO0lBQ3RDLE1BQU0sY0FBYyxHQUFHLG9CQUFvQixDQUFDLE1BQU0sQ0FBQyxDQUFBO0lBQ25ELE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxTQUFTLEVBQUUsQ0FBQTtJQUNqQyxjQUFjLENBQUMsTUFBTSxDQUFDLE1BQU0sRUFBRSxFQUFFLFNBQVMsRUFBRSxJQUFJLENBQUMsR0FBRyxFQUFFLEVBQUUsTUFBTSxFQUFFLE9BQU8sQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFBO0lBQ2hGLFVBQVUsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxjQUFjLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQTtJQUNoRCxpQkFBaUIsR0FBRyxNQUFNLENBQUE7SUFDMUIsT0FBTyxpQkFBaUIsQ0FBQTtBQUMxQixDQUFDO0FBS0QsTUFBTSxVQUFVLFVBQVUsQ0FBQyxNQUFlLEVBQUUsT0FBaUIsRUFBRSxVQUFzQjtJQUNuRixNQUFNLGNBQWMsR0FBRyxvQkFBb0IsQ0FBQyxNQUFNLENBQUMsQ0FBQTtJQUVuRCxJQUFJLENBQUMsY0FBYyxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsQ0FBQyxFQUFFO1FBQzFDLHlCQUF5QixDQUFDLE1BQU0sRUFBRSxPQUFPLEVBQUUsVUFBVSxDQUFDLENBQUE7UUFDdEQsT0FBTyxVQUFVLENBQUMsTUFBTSxFQUFFLE9BQU8sRUFBRSxVQUFVLENBQUMsQ0FBQTtLQUMvQztJQUNELE1BQU0sRUFBRSxTQUFTLEVBQUUsR0FBRyxjQUFjLENBQUMsR0FBRyxDQUFDLGlCQUFpQixDQUFDLENBQUE7SUFDM0QsS0FBSyxNQUFNLENBQUMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxJQUFJLE1BQU0sQ0FBQyxlQUFlLENBQUMsY0FBYyxDQUFDLEVBQUU7UUFDaEUsSUFBSSxNQUFNLENBQUMsU0FBUyxHQUFHLFNBQVM7WUFBRSxPQUFPLEtBQUssQ0FBQTtLQUMvQztJQUNELE9BQU8sSUFBSSxDQUFBO0FBQ2IsQ0FBQztBQUtELE1BQU0sVUFBVSxvQkFBb0IsQ0FBQyxNQUFlO0lBQ2xELE1BQU0sVUFBVSxHQUFHLE1BQU0sQ0FBQyxZQUFZLENBQ3BDLFdBQVcsQ0FBQyxXQUFXLENBQ21DLENBQUE7SUFDNUQsSUFBSSxDQUFDLG1DQUFtQyxFQUFFO1FBQ3hDLE1BQU0sVUFBVSxHQUFHLFVBQVUsQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLFVBQVUsQ0FBQyxDQUFBO1FBQzFELElBQUksVUFBVSxJQUFJLFVBQVUsQ0FBQyxVQUFVLEdBQUcsQ0FBQyxFQUFFO1lBQzNDLG1DQUFtQyxHQUFHLElBQUksQ0FBQTtTQUMzQztLQUNGO0lBQ0QsT0FBTyxtQ0FBbUMsQ0FBQTtBQUM1QyxDQUFDO0FBU0QsTUFBTSxVQUFVLHVCQUF1QixDQUFDLE1BQWUsRUFBRSxRQUFrQixFQUFFLFdBQXVCO0lBRWxHLE1BQU0sVUFBVSxHQUFHLE1BQU0sQ0FBQyxZQUFZLENBQUMsV0FBVyxDQUFDLFdBQVcsQ0FBdUIsQ0FBQTtJQUVyRixLQUFLLFVBQVUsVUFBVTtRQWdCdkIsSUFBSSxDQUFDLFVBQVUsQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLFVBQVUsQ0FBQyxFQUFFLFVBQVUsSUFBSSxDQUFDLENBQUMsR0FBRyxHQUFHLEVBQUU7WUFDcEUsY0FBYyxFQUFFLENBQUE7WUFDaEIsT0FBTTtTQUNQO0lBV0gsQ0FBQztJQUNELEtBQUssVUFBVSxFQUFFLENBQUE7QUFDbkIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIEVuZ2luZUluZm8gYXMgX0VuZ2luZUluZm8sXG4gIEVudGl0eSxcbiAgSUVuZ2luZSxcbiAgTmV0d29ya0VudGl0eSBhcyBfTmV0d29ya0VudGl0eSxcbiAgU2NoZW1hcyxcbiAgTGFzdFdyaXRlV2luRWxlbWVudFNldENvbXBvbmVudERlZmluaXRpb24sXG4gIFBCRW5naW5lSW5mb1xufSBmcm9tICdAZGNsL2VjcydcbmltcG9ydCB7IGNvbXBvbmVudE51bWJlckZyb21OYW1lIH0gZnJvbSAnQGRjbC9lY3MvZGlzdC9jb21wb25lbnRzL2NvbXBvbmVudC1udW1iZXInXG5cbmltcG9ydCB0eXBlIHsgR2V0VXNlckRhdGFSZXF1ZXN0LCBHZXRVc2VyRGF0YVJlc3BvbnNlIH0gZnJvbSAnfnN5c3RlbS9Vc2VySWRlbnRpdHknXG5pbXBvcnQgeyBTeW5jRW50aXR5IH0gZnJvbSAnLi9lbnRpdGllcydcbmltcG9ydCB7IElQcm9maWxlIH0gZnJvbSAnLi9tZXNzYWdlLWJ1cy1zeW5jJ1xuXG4vLyBDb21wb25lbnQgdG8gdHJhY2sgYWxsIHRoZSBwbGF5ZXJzIGFuZCB3aGVuIHRoZXkgZW50ZXIgdG8gdGhlIHNjZW5lLlxuLy8gS25vdyB3aG8gaXMgaW4gY2hhcmdlIG9mIHNlbmRpbmcgdGhlIGluaXRpYWwgc3RhdGUgKG9sZGVzdCBvbmUpXG5leHBvcnQgY29uc3QgZGVmaW5lUGxheWVyc0luU2NlbmUgPSAoZW5naW5lOiBJRW5naW5lKSA9PlxuICBlbmdpbmUuZGVmaW5lQ29tcG9uZW50KCdwbGF5ZXJzLXNjZW5lJywge1xuICAgIHRpbWVzdGFtcDogU2NoZW1hcy5OdW1iZXIsXG4gICAgdXNlcklkOiBTY2hlbWFzLlN0cmluZ1xuICB9KVxuXG4vLyBBbHJlYWR5IGluaXRpYWxpemVkIG15IHN0YXRlLiBJZ25vcmUgbmV3IHN0YXRlcyBtZXNzYWdlcy5cbmV4cG9ydCBsZXQgc3RhdGVJbml0aWFsaXplZCA9IGZhbHNlXG5cbi8vIE15IHBsYXllciBlbnRpdHkgdG8gY2hlY2sgaWYgSSdtIHRoZSBvbGRlc3QgcGxheWVyIGluIHRoZSBzY2VuZFxuZXhwb3J0IGxldCBwbGF5ZXJTY2VuZUVudGl0eTogRW50aXR5XG5cbmV4cG9ydCBmdW5jdGlvbiBzZXRJbml0aWFsaXplZCgpIHtcbiAgc3RhdGVJbml0aWFsaXplZCA9IHRydWVcbn1cblxuLy8gRmxhZyB0byBhdm9pZCBzZW5kaW5nIG92ZXIgdGhlIHdpcmUgYWxsIHRoZSBpbml0aWFsIG1lc3NhZ2VzIHRoYXQgdGhlIGVuZ2luZSBhZGQncyB0byB0aGUgcmVuZGVyZXJUcmFuc3BvcnRcbi8vIElOSVRJQUxfQ1JEVF9NRVNTQUdFUyB0aGF0IGFyZSBiZWluZyBwcm9jZXNzZWQgb24gdGhlIG9uU3RhcnQgbG9vcCwgYmVmb3JlIHRoZSBvblVwZGF0ZS5cbmV4cG9ydCBsZXQgSU5JVElBTF9DUkRUX1JFTkRFUkVSX01FU1NBR0VTX1NFTlQgPSBmYWxzZVxuXG4vLyBSZXRyaWV2ZSB1c2VySWQgdG8gc3RhcnQgc2VuZGluZyB0aGlzIGluZm8gYXMgdGhlIG5ldHdvcmtJZFxuZXhwb3J0IGZ1bmN0aW9uIGZldGNoUHJvZmlsZShcbiAgbXlQcm9maWxlOiBJUHJvZmlsZSxcbiAgZ2V0VXNlckRhdGE6ICh2YWx1ZTogR2V0VXNlckRhdGFSZXF1ZXN0KSA9PiBQcm9taXNlPEdldFVzZXJEYXRhUmVzcG9uc2U+XG4pIHtcbiAgdm9pZCBnZXRVc2VyRGF0YSh7fSkudGhlbigoeyBkYXRhIH0pID0+IHtcbiAgICBpZiAoZGF0YT8udXNlcklkKSB7XG4gICAgICBjb25zdCB1c2VySWQgPSBkYXRhLnVzZXJJZFxuICAgICAgY29uc3QgbmV0d29ya0lkID0gY29tcG9uZW50TnVtYmVyRnJvbU5hbWUoZGF0YS51c2VySWQpXG4gICAgICBteVByb2ZpbGUubmV0d29ya0lkID0gbmV0d29ya0lkXG4gICAgICBteVByb2ZpbGUudXNlcklkID0gdXNlcklkXG4gICAgfSBlbHNlIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgQ291bGRuJ3QgZmV0Y2ggcHJvZmlsZSBkYXRhYClcbiAgICB9XG4gIH0pXG59XG5cbi8qKlxuICogQWRkJ3MgdGhlIHVzZXIgaW5mb3JtYXRpb24gYWJvdXQgd2hlbiBoZSBqb2luZWQgdGhlIHNjZW5lLlxuICogSXQncyB1c2VkIHRvIGNoZWNrIHdobyBpcyB0aGUgb2xkZXN0IG9uZSwgdG8gc3luYyB0aGUgc3RhdGVcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZVBsYXllclRpbWVzdGFtcERhdGEoZW5naW5lOiBJRW5naW5lLCBwcm9maWxlOiBJUHJvZmlsZSwgc3luY0VudGl0eTogU3luY0VudGl0eSkge1xuICBpZiAoIXByb2ZpbGU/LnVzZXJJZCkgcmV0dXJuIHVuZGVmaW5lZFxuICBjb25zdCBQbGF5ZXJzSW5TY2VuZSA9IGRlZmluZVBsYXllcnNJblNjZW5lKGVuZ2luZSlcbiAgY29uc3QgZW50aXR5ID0gZW5naW5lLmFkZEVudGl0eSgpXG4gIFBsYXllcnNJblNjZW5lLmNyZWF0ZShlbnRpdHksIHsgdGltZXN0YW1wOiBEYXRlLm5vdygpLCB1c2VySWQ6IHByb2ZpbGUudXNlcklkIH0pXG4gIHN5bmNFbnRpdHkoZW50aXR5LCBbUGxheWVyc0luU2NlbmUuY29tcG9uZW50SWRdKVxuICBwbGF5ZXJTY2VuZUVudGl0eSA9IGVudGl0eVxuICByZXR1cm4gcGxheWVyU2NlbmVFbnRpdHlcbn1cblxuLyoqXG4gKiBDaGVjayBpZiBJJ20gdGhlIG9sZGVyIHVzZXIgdG8gc2VuZCB0aGUgaW5pdGlhbCBzdGF0ZVxuICovXG5leHBvcnQgZnVuY3Rpb24gb2xkZXN0VXNlcihlbmdpbmU6IElFbmdpbmUsIHByb2ZpbGU6IElQcm9maWxlLCBzeW5jRW50aXR5OiBTeW5jRW50aXR5KTogYm9vbGVhbiB7XG4gIGNvbnN0IFBsYXllcnNJblNjZW5lID0gZGVmaW5lUGxheWVyc0luU2NlbmUoZW5naW5lKVxuICAvLyBXaGVuIHRoZSB1c2VyIGxlYXZlcyB0aGUgc2NlbmUgYnV0IGl0J3Mgc3RpbGwgY29ubmVjdGVkLlxuICBpZiAoIVBsYXllcnNJblNjZW5lLmhhcyhwbGF5ZXJTY2VuZUVudGl0eSkpIHtcbiAgICBjcmVhdGVQbGF5ZXJUaW1lc3RhbXBEYXRhKGVuZ2luZSwgcHJvZmlsZSwgc3luY0VudGl0eSlcbiAgICByZXR1cm4gb2xkZXN0VXNlcihlbmdpbmUsIHByb2ZpbGUsIHN5bmNFbnRpdHkpXG4gIH1cbiAgY29uc3QgeyB0aW1lc3RhbXAgfSA9IFBsYXllcnNJblNjZW5lLmdldChwbGF5ZXJTY2VuZUVudGl0eSlcbiAgZm9yIChjb25zdCBbXywgcGxheWVyXSBvZiBlbmdpbmUuZ2V0RW50aXRpZXNXaXRoKFBsYXllcnNJblNjZW5lKSkge1xuICAgIGlmIChwbGF5ZXIudGltZXN0YW1wIDwgdGltZXN0YW1wKSByZXR1cm4gZmFsc2VcbiAgfVxuICByZXR1cm4gdHJ1ZVxufVxuXG4vKipcbiAqIElnbm9yZSBDUkRUJ3MgaW5pdGlhbCBtZXNzYWdlcyBmcm9tIHRoZSByZW5kZXJlci5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHN5bmNUcmFuc3BvcnRJc1JlYWR5KGVuZ2luZTogSUVuZ2luZSkge1xuICBjb25zdCBFbmdpbmVJbmZvID0gZW5naW5lLmdldENvbXBvbmVudChcbiAgICBfRW5naW5lSW5mby5jb21wb25lbnRJZFxuICApIGFzIExhc3RXcml0ZVdpbkVsZW1lbnRTZXRDb21wb25lbnREZWZpbml0aW9uPFBCRW5naW5lSW5mbz5cbiAgaWYgKCFJTklUSUFMX0NSRFRfUkVOREVSRVJfTUVTU0FHRVNfU0VOVCkge1xuICAgIGNvbnN0IGVuZ2luZUluZm8gPSBFbmdpbmVJbmZvLmdldE9yTnVsbChlbmdpbmUuUm9vdEVudGl0eSlcbiAgICBpZiAoZW5naW5lSW5mbyAmJiBlbmdpbmVJbmZvLnRpY2tOdW1iZXIgPiAyKSB7XG4gICAgICBJTklUSUFMX0NSRFRfUkVOREVSRVJfTUVTU0FHRVNfU0VOVCA9IHRydWVcbiAgICB9XG4gIH1cbiAgcmV0dXJuIElOSVRJQUxfQ1JEVF9SRU5ERVJFUl9NRVNTQUdFU19TRU5UXG59XG5cbi8qKlxuICogQ2hlY2sgaWYgd2UgYXJlIGFscmVhZHkgaW5pdGlhbGl6ZWRcbiAqIEFkZCB0aGUgcGxheWVyU2NlbmVEYXRhIGNvbXBvbmVudCBhbmQgc3luY3Jvbml6ZSBpdCB0aWxsIHdlIHJlY2VpdmUgdGhlIHN0YXRlLlxuICogVGhpcyBmbiBzaG91bGQgYmUgYWRkZWQgYXMgYSBzeXN0ZW0gc28gaXQgcnVucyBvbiBldmVyeSB0aWNrXG4gKi9cbi8vIFRPRE86IEhhZCB0byBjb21tZW50IGFsbCB0aGUgbG9naWMgYmVjYXVzZSBnZXRDb25uZWN0ZWRQbGF5ZXJzIHdhcyBub3Qgd29ya2luZyBhcyBleHBlY3RlZFxuLy8gQSBsb3Qgb2YgcmFpc2UgY29uZGl0aW9ucy4gRm9yIG5vdyB3ZSB3aWxsIGdvIHdpdGggdGhlIGFwcHJvYWNoIHRoYXQgZXZlcnkgY2xpZW50IHRoYXQgaXQncyBpbml0aWFsaXplZCB3aWxsIHNlbmQgaGlzIGNyZHQgc3RhdGUuXG5leHBvcnQgZnVuY3Rpb24gc3RhdGVJbml0aWFsaXplZENoZWNrZXIoZW5naW5lOiBJRW5naW5lLCBfcHJvZmlsZTogSVByb2ZpbGUsIF9zeW5jRW50aXR5OiBTeW5jRW50aXR5KSB7XG4gIC8vIGNvbnN0IFBsYXllcnNJblNjZW5lID0gZGVmaW5lUGxheWVyc0luU2NlbmUoZW5naW5lKVxuICBjb25zdCBFbmdpbmVJbmZvID0gZW5naW5lLmdldENvbXBvbmVudChfRW5naW5lSW5mby5jb21wb25lbnRJZCkgYXMgdHlwZW9mIF9FbmdpbmVJbmZvXG4gIC8vIGNvbnN0IE5ldHdvcmtFbnRpdHkgPSBlbmdpbmUuZ2V0Q29tcG9uZW50KF9OZXR3b3JrRW50aXR5LmNvbXBvbmVudElkKSBhcyBJTmV0b3dya0VudGl0eVxuICBhc3luYyBmdW5jdGlvbiBlbnRlclNjZW5lKCkge1xuICAgIC8vIGlmICghcGxheWVyU2NlbmVFbnRpdHkpIHtcbiAgICAvLyAgIGNyZWF0ZVBsYXllclRpbWVzdGFtcERhdGEoZW5naW5lLCBwcm9maWxlLCBzeW5jRW50aXR5KVxuICAgIC8vIH1cblxuICAgIC8qKlxuICAgICAqIEtlZXBzIFBsYXllcnNJblNjZW5lIHVwLXRvLWRhdGUgd2l0aCB0aGUgY3VycmVudCBwbGF5ZXJzLlxuICAgICAqL1xuICAgIC8vIGNvbnN0IGNvbm5lY3RlZFBsYXllcnMgPSBhd2FpdCBnZXRDb25uZWN0ZWRQbGF5ZXJzKHt9KVxuICAgIC8vIGZvciAoY29uc3QgW2VudGl0eSwgcGxheWVyXSBvZiBlbmdpbmUuZ2V0RW50aXRpZXNXaXRoKFBsYXllcnNJblNjZW5lKSkge1xuICAgIC8vICAgaWYgKCFjb25uZWN0ZWRQbGF5ZXJzLnBsYXllcnMuZmluZCgoJCkgPT4gJC51c2VySWQgPT09IHBsYXllci51c2VySWQpKSB7XG4gICAgLy8gICAgIFBsYXllcnNJblNjZW5lLmRlbGV0ZUZyb20oZW50aXR5KVxuICAgIC8vICAgfVxuICAgIC8vIH1cblxuICAgIC8vIFdhaXQgZm9yIGNvbW1zIHRvIGJlIHJlYWR5ID8/IH4zMDAwbXNcbiAgICBpZiAoKEVuZ2luZUluZm8uZ2V0T3JOdWxsKGVuZ2luZS5Sb290RW50aXR5KT8udGlja051bWJlciA/PyAwKSA+IDEwMCkge1xuICAgICAgc2V0SW5pdGlhbGl6ZWQoKVxuICAgICAgcmV0dXJuXG4gICAgfVxuXG4gICAgLy8gSWYgd2UgYWxyZWFkeSBoYXZlIGRhdGEgZnJvbSBwbGF5ZXJzLCBkb250IHNlbmQgdGhlIGhlYXJ0YmVhdCBtZXNzYWdlc1xuICAgIC8vIGlmIChjb25uZWN0ZWRQbGF5ZXJzLnBsYXllcnMubGVuZ3RoKSByZXR1cm5cblxuICAgIC8vIGlmICghc3RhdGVJbml0aWFsaXplZCAmJiBwbGF5ZXJTY2VuZUVudGl0eSkge1xuICAgIC8vICAgLy8gU2VuZCB0aGlzIGRhdGEgdG8gYWxsIHRoZSBwbGF5ZXJzIGNvbm5lY3RlZCAobmV3IGFuZCBvbGQpXG4gICAgLy8gICAvLyBTbyBldmVyeW9uZSBjYW4gZGVjaWRlIGlmIGl0J3MgdGhlIG9sZGVzdCBvbmUgb3Igbm8uXG4gICAgLy8gICAvLyBJdCdzIGZvciB0aGUgY2FzZSB0aGF0IG11bHRpcGxlIHVzZXJzIGVudGVycyB+IGF0IHRoZSBzYW1lIHRpbWUuXG4gICAgLy8gICBQbGF5ZXJzSW5TY2VuZS5nZXRNdXRhYmxlKHBsYXllclNjZW5lRW50aXR5KVxuICAgIC8vIH1cbiAgfVxuICB2b2lkIGVudGVyU2NlbmUoKVxufVxuIl19
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@dcl/sdk",
3
3
  "description": "",
4
- "version": "7.3.31-7169243942.commit-5f044c6",
4
+ "version": "7.3.32-7170667436.commit-11a2181",
5
5
  "author": "Decentraland",
6
6
  "dependencies": {
7
- "@dcl/ecs": "7.3.31-7169243942.commit-5f044c6",
7
+ "@dcl/ecs": "7.3.32-7170667436.commit-11a2181",
8
8
  "@dcl/ecs-math": "2.0.2",
9
9
  "@dcl/explorer": "1.0.157791-20231211140501.commit-65632e4",
10
- "@dcl/js-runtime": "7.3.31-7169243942.commit-5f044c6",
11
- "@dcl/react-ecs": "7.3.31-7169243942.commit-5f044c6",
12
- "@dcl/sdk-commands": "7.3.31-7169243942.commit-5f044c6",
10
+ "@dcl/js-runtime": "7.3.32-7170667436.commit-11a2181",
11
+ "@dcl/react-ecs": "7.3.32-7170667436.commit-11a2181",
12
+ "@dcl/sdk-commands": "7.3.32-7170667436.commit-11a2181",
13
13
  "text-encoding": "0.7.0"
14
14
  },
15
15
  "keywords": [],
@@ -35,5 +35,5 @@
35
35
  },
36
36
  "types": "./index.d.ts",
37
37
  "typings": "./index.d.ts",
38
- "commit": "5f044c65cc86498ef4a73f87e0082c070b9730f5"
38
+ "commit": "11a21816f9bf404c90ba02782ad7e359262a909d"
39
39
  }
@@ -1,4 +1,4 @@
1
- import { IEngine, CrdtMessage, CrdtMessageType } from '@dcl/ecs'
1
+ import { IEngine, CrdtMessage, CrdtMessageType, CRDT_MESSAGE_HEADER_LENGTH } from '@dcl/ecs'
2
2
  import { ReadWriteByteBuffer } from '@dcl/ecs/dist/serialization/ByteBuffer'
3
3
  import { readMessage } from '@dcl/ecs/dist/serialization/crdt/message'
4
4
 
@@ -8,14 +8,16 @@ export function* serializeCrdtMessages(prefix: string, data: Uint8Array, engine:
8
8
  let message: CrdtMessage | null
9
9
 
10
10
  while ((message = readMessage(buffer))) {
11
- const ent = `0x${message.entityId.toString(16)}`
11
+ const ent = message.entityId
12
12
  const preface = `${prefix}: ${CrdtMessageType[message.type]} e=${ent}`
13
- if (message.type === CrdtMessageType.DELETE_ENTITY) {
13
+ if (message.type === CrdtMessageType.DELETE_ENTITY || message.type === CrdtMessageType.DELETE_ENTITY_NETWORK) {
14
14
  yield `${preface}`
15
15
  }
16
16
 
17
17
  if (
18
18
  message.type === CrdtMessageType.PUT_COMPONENT ||
19
+ message.type === CrdtMessageType.PUT_COMPONENT_NETWORK ||
20
+ message.type === CrdtMessageType.DELETE_COMPONENT_NETWORK ||
19
21
  message.type === CrdtMessageType.DELETE_COMPONENT ||
20
22
  message.type === CrdtMessageType.APPEND_VALUE
21
23
  ) {
@@ -30,7 +32,10 @@ export function* serializeCrdtMessages(prefix: string, data: Uint8Array, engine:
30
32
  } catch {
31
33
  yield `${preface} c=${componentId} t=${timestamp} data=?`
32
34
  }
33
- } else if (message.type === CrdtMessageType.DELETE_ENTITY) {
35
+ } else if (
36
+ message.type === CrdtMessageType.DELETE_ENTITY ||
37
+ message.type === CrdtMessageType.DELETE_ENTITY_NETWORK
38
+ ) {
34
39
  yield preface
35
40
  } else {
36
41
  yield `${preface} Unknown CrdtMessageType`
@@ -42,7 +42,8 @@ export function createRendererTransport(engineApi: EngineApiForTransport): Trans
42
42
  }
43
43
 
44
44
  return !!message
45
- }
45
+ },
46
+ type: 'renderer'
46
47
  }
47
48
 
48
49
  return rendererTransport
@@ -14,11 +14,13 @@ export class MessageBus {
14
14
 
15
15
  on(message: string, callback: (value: any, sender: string) => void): Observer<IEvents['comms']> {
16
16
  return onCommsMessage.add((e) => {
17
- const m = JSON.parse(e.message)
17
+ try {
18
+ const m = JSON.parse(e.message)
18
19
 
19
- if (m.message === message) {
20
- callback(m.payload, e.sender)
21
- }
20
+ if (m.message === message) {
21
+ callback(m.payload, e.sender)
22
+ }
23
+ } catch (_) {}
22
24
  })!
23
25
  }
24
26
 
@@ -28,7 +30,6 @@ export class MessageBus {
28
30
 
29
31
  this.flush()
30
32
  }
31
-
32
33
  emit(message: string, payload: Record<any, any>) {
33
34
  const messageToSend = JSON.stringify({ message, payload })
34
35
  this.sendRaw(messageToSend)
@@ -36,13 +37,10 @@ export class MessageBus {
36
37
  }
37
38
 
38
39
  private flush() {
39
- if (this.messageQueue.length === 0) return
40
+ if (!this.messageQueue.length) return
40
41
  if (this.flushing) return
41
42
 
42
43
  const message = this.messageQueue.shift()!
43
-
44
- this.flushing = true
45
-
46
44
  communicationsController.send({ message }).then(
47
45
  (_) => {
48
46
  this.flushing = false
@@ -0,0 +1,122 @@
1
+ ## Mapping syncronized Entities
2
+ How this works ?
3
+ We create a new component created NetworkEntity.
4
+ This network component consists in the id of the entity that was created in that client, and the networkId is the user address converted to a number. So its kind of the unique user id in a int64 format.
5
+
6
+ type NetwokEntity = { entityId: Entity; networkId: number }
7
+
8
+ We also introduce a new kind of messages that are going to be sent through the wire, and that the SDK knows how to read.
9
+ PutNetworkComponent message.
10
+ This message is the same as the PUT_COMPONENT BUT! with the difference that we are sending always the NetworkEntity mapping.
11
+ So this message will not have the local entityId, it will always send the NetworkEntity component associated to the entity.
12
+ PUT_NETWORK_MESSAGE = { networkId: networkEntity.networkId, entityId: networkEntity.entityId, data: Uint8Array }
13
+ So this way, every client has the internal mapping.
14
+ Once you receive a network message, you iterate the NetworkEntity component to find the one that has the same entityId and networkId. The entity with that component is the entity that the network is talking about.
15
+ When you change a component of that entity, we do the same. We look for the networkEntity and create a PutNetwork message with the NetworkEntity values.
16
+
17
+ ## Syncronize Entities that are created in every client (Main Function)
18
+
19
+ enum Entities {
20
+ HOUSE = 1,
21
+ DOOR = 2
22
+ }
23
+ Same entity for all clients.
24
+
25
+ This is usefull when you have static entities that are created on every client, and you want to syncronize something about them.
26
+ For example a door of a house. You want every client to create the same door.
27
+ We use the Entities enum to tag the entity with an unique identifier, so every client knows which entity you are modifying, no matter the order they are created
28
+ This is used for code that is being executed on every client, and you want to sync some component about it. If you dont tag them, you can't be sure that both clients are talking about the same entity.
29
+ Maybe for client A the door is the entity 512, but for client B its 513, and you will have some missmatch there.
30
+ i.e.
31
+ const houseEntity = engine.addEntity()
32
+ syncEntity(houseEntity, [Transform.componentId], Entities.HOUSE)
33
+
34
+ If we dont use the SyncStaticEntities for static models like the house. Then each client will create a new House, and that House will be replicated on every client. So if you have 10 clients, you will have 10 houses being syncronized.
35
+ That's why we use the SyncStaticEntities identifier for things that you want to be created only once, and can syncronized if some component changed.
36
+
37
+
38
+ ## Syncronize RUNTIME/DYNAMIC entities
39
+ This are entities that are created on a client after some interaction, and you want to replicate them on all the other clients.
40
+ The client that runs this code will create an UNIQUE entity and will be sent to the others.
41
+
42
+ For example bullets of a gun. Every client will have their own bullets, and every time they shot the gun a new entity (bullet) will be created and replicated on every client.
43
+ Another example could be the spawn cubes. Every client that spawns a cube, will spawn an unique cube, and will be replicated on the others.
44
+ All this examples have the same pattern, code that is being executed in only one client, and need to be syncronize on the other ones.
45
+
46
+ function onShoot() {
47
+ const bullet = engine.addEntity()
48
+ Transform.create(bullet, {})
49
+ Material.create(bullet, {})
50
+ syncEntity(bullet, [Transform.componentId, Material.componentId])
51
+ }
52
+
53
+ ## Syncronizing Transform Issue
54
+ Let me tell you a sad story about syncronizing Transforms with different entities ids in each client
55
+ Scene wants to sync a door. (SyncEntities.Door & SyncEntities.ParentDoor).
56
+ We have clients A & B, and both creates the door in the main function.
57
+ Both clients knows that are the same door because we use the SyncEntities enum to map this entity.
58
+
59
+ A creates sync entity 512 (parent)
60
+ const parent = engine.addEntity() // 512
61
+ syncEntity(parent, [Transform.componentId], SyncEntities.ParentDoor)
62
+ A creates sync entity 513 (child)
63
+ const child = engine.addEntity() // 513
64
+ Transform.create({ parent, position: {} })
65
+ syncEntity(child, [Transform.componentId], SyncEntities.ChildDoor)
66
+
67
+
68
+ Client B does the same but the entities, we dont know why, result in diff order. (Maybe a race condition)
69
+ B creates sync entity 513 (parent)
70
+ const parent = engine.addEntity() // 513
71
+ syncEntity(parent, [Transform.componentId], SyncEntities.ParentDoor)
72
+ B creates sync entity 514 (child)
73
+ const child = engine.addEntity() // 514
74
+ Transform.create({ parent, position: {} })
75
+ syncEntity(child, [Transform.componentId], SyncEntities.ChildDoor)
76
+
77
+ So now client A & B had different raw data for the Transform component, because they have different parents.
78
+ Meaning that we have an inconsistent CRDT State between two clients.
79
+ So if there is a new message comming from client C we could have conflicts for client A but maybe not for client B.
80
+
81
+ Same problem would happen if a client after some interaction (i.e. a bullet) creates an entity with a parent. For the client A this could be { parent: 515, child: 516 } but for another user those entities are not going to be the same ones.
82
+
83
+ Solution 1:
84
+ What if we introduce a new ParentNetwork component.
85
+ This ParentNetwork will be in charge of syncronizing the parenting. If we have ParentNetwork then we should always ignore the Transform.parent property.
86
+ The ParentNetwork will have both entityId and networkId such as the PutNetworkMessage so we can map the entity in every client.
87
+
88
+ ParentNetwork.Schema = { entityId: Entity; networkId: number }.
89
+
90
+ Being the networkId the id of the user that owns that parent entity, and the entityId the parent entityId of the user that creates that entity.
91
+ So with this two values, we cant map the real parent entity id on every client.
92
+
93
+ ```ts
94
+ import { syncEntity, parentEntity } from '@dcl/sdk/network'
95
+ const parentEntity = engine.addEntity()
96
+ Transform.create(parentEntity, { position: somePosition })
97
+ syncEntity(parent, Transform.componentId)
98
+ const childEntity: Entity = engine.addEntity()
99
+ syncEntity(childEntity, Transform.componentId)
100
+
101
+ // create parentNetwork component. This maybe could be done in a system and use the original parent. TBD
102
+ parentEntity(childEntity, parentEntity)
103
+ ```
104
+ Every client will know how to map this entity because the ParentNetwork has the pointers to the parent entity. But we are still having an issue, the parent is not defined. We need to tell the renderer that the child entity has a parent property.
105
+
106
+ So every time we send a Transform component to the renderer, we should update the transform.parent property with the mapped Entity that we fetch from the ParentNetwork.
107
+ if (isTransform(message) && isRendererTransport && ParentNetwork.getOrNull(message.entityId)) {
108
+ // Generate a new transform raw data with the parent property included
109
+ }
110
+
111
+ And every time we recieve a message from the renderer, we should remove the parent property to keep consistency in all CRDT state clients.
112
+ if (isTransform(message) && message.type === CrdtMessageType.PUT_COMPONENT && ParentNetwork.has(message.entityId)) {
113
+ transform.parent = null
114
+ // Generate a new transform raw data without the parent property included
115
+ }
116
+
117
+ With this approach, all the clients will have the same Transform, so we avoid the inconsistency of crdt's state.
118
+ And when some user wants to update the transform, it has to modify the ParentNetwork and will update both values, the parent & the network.
119
+
120
+ I think this will work but there are some developer experience issues, like using the `parentEntity(child, parent)` function instead of the transform.parent.
121
+ This could end up with a lot of unexepcted issues/bugs. Maybe we can have a system that iterates over every syncronized entity and when the transform.parent changes, add the parentEntity function automatically.
122
+ First I wanna try to implement all of this and then came up with this approach to avoid inconsistencies
@@ -0,0 +1,72 @@
1
+ import { ReadWriteByteBuffer } from '@dcl/ecs/dist/serialization/ByteBuffer'
2
+
3
+ export enum CommsMessage {
4
+ CRDT = 1,
5
+ REQ_CRDT_STATE = 2,
6
+ RES_CRDT_STATE = 3
7
+ }
8
+
9
+ export function BinaryMessageBus<T extends CommsMessage>(send: (message: Uint8Array) => void) {
10
+ const mapping: Map<T, (value: Uint8Array, sender: string) => void> = new Map()
11
+ return {
12
+ on: <K extends T>(message: K, callback: (value: Uint8Array, sender: string) => void) => {
13
+ mapping.set(message, callback)
14
+ },
15
+ emit: <K extends T>(message: K, value: Uint8Array) => {
16
+ send(craftCommsMessage<T>(message, value))
17
+ },
18
+ __processMessages: (messages: Uint8Array[]) => {
19
+ for (const message of messages) {
20
+ const commsMsg = decodeCommsMessage<T>(message)
21
+ if (!commsMsg) continue
22
+ const { sender, messageType, data } = commsMsg
23
+ const fn = mapping.get(messageType)
24
+ if (fn) fn(data, sender)
25
+ }
26
+ }
27
+ }
28
+ }
29
+
30
+ export function craftCommsMessage<T extends CommsMessage>(messageType: T, payload: Uint8Array): Uint8Array {
31
+ const msg = new Uint8Array(payload.byteLength + 1)
32
+ msg.set([messageType])
33
+ msg.set(payload, 1)
34
+ return msg
35
+ }
36
+
37
+ export function decodeCommsMessage<T extends CommsMessage>(
38
+ data: Uint8Array
39
+ ): { sender: string; messageType: T; data: Uint8Array } | undefined {
40
+ try {
41
+ let offset = 0
42
+ const r = new Uint8Array(data)
43
+ const view = new DataView(r.buffer)
44
+ const senderLength = view.getUint8(offset)
45
+ offset += 1
46
+ const sender = decodeString(data.subarray(1, senderLength + 1))
47
+ offset += senderLength
48
+ const messageType = view.getUint8(offset) as T
49
+ offset += 1
50
+ const message = r.subarray(offset)
51
+
52
+ return {
53
+ sender,
54
+ messageType,
55
+ data: message
56
+ }
57
+ } catch (e) {
58
+ console.error('Invalid Comms message', e)
59
+ }
60
+ }
61
+
62
+ export function decodeString(data: Uint8Array): string {
63
+ const buffer = new ReadWriteByteBuffer()
64
+ buffer.writeBuffer(data, true)
65
+ return buffer.readUtf8String()
66
+ }
67
+
68
+ export function encodeString(s: string): Uint8Array {
69
+ const buffer = new ReadWriteByteBuffer()
70
+ buffer.writeUtf8String(s)
71
+ return buffer.readBuffer()
72
+ }
@@ -0,0 +1,132 @@
1
+ import {
2
+ Entity,
3
+ IEngine,
4
+ NetworkEntity as _NetworkEntity,
5
+ INetowrkEntity,
6
+ NetworkParent as _NetworkParent,
7
+ Transform as _Transform,
8
+ SyncComponents as _SyncComponents,
9
+ INetowrkParent,
10
+ TransformComponent,
11
+ ISyncComponents
12
+ } from '@dcl/ecs'
13
+ import { IProfile } from './message-bus-sync'
14
+
15
+ export type SyncEntity = (entityId: Entity, componentIds: number[], entityEnumId?: number) => void
16
+
17
+ export function entityUtils(engine: IEngine, profile: IProfile) {
18
+ const NetworkEntity = engine.getComponent(_NetworkEntity.componentId) as INetowrkEntity
19
+ const NetworkParent = engine.getComponent(_NetworkParent.componentId) as INetowrkParent
20
+ const Transform = engine.getComponent(_Transform.componentId) as TransformComponent
21
+ const SyncComponents = engine.getComponent(_SyncComponents.componentId) as ISyncComponents
22
+
23
+ /**
24
+ * Create a network entity (sync) through comms, and sync the received components
25
+ */
26
+ function syncEntity(entityId: Entity, componentIds: number[], entityEnumId?: number) {
27
+ // Profile not initialized
28
+ if (!profile?.networkId) {
29
+ throw new Error('Profile not initialized. Called syncEntity inside the main() function.')
30
+ }
31
+
32
+ // We use the networkId generated by the user address to identify this entity through the network
33
+ const networkValue = { entityId, networkId: profile.networkId }
34
+
35
+ // If there is an entityEnumId, it means is the same entity for all the clients created on the main funciton.
36
+ // So the networkId should be the same in all the clients to avoid re-creating this entity.
37
+ // For this case we use networkId = 0.
38
+ if (entityEnumId !== undefined) {
39
+ networkValue.networkId = 0
40
+ networkValue.entityId = entityEnumId as Entity
41
+
42
+ // Check if this enum is already used
43
+ for (const [_, network] of engine.getEntitiesWith(NetworkEntity)) {
44
+ if (network.networkId === networkValue.networkId && network.entityId === networkValue.entityId) {
45
+ throw new Error('syncEntity failed because the id provided is already in use')
46
+ }
47
+ }
48
+ }
49
+
50
+ // If is not defined, then is a entity created in runtime (what we called dynamic/runtime entities).
51
+ NetworkEntity.createOrReplace(entityId, networkValue)
52
+ SyncComponents.createOrReplace(entityId, { componentIds })
53
+ }
54
+
55
+ /**
56
+ * Returns an iterable of all the childrens of the given entity.
57
+ * for (const children of getChildren(parent)) { console.log(children) }
58
+ * or just => const childrens: Entity[] = Array.from(getChildren(parent))
59
+ */
60
+ function* getChildren(parent: Entity): Iterable<Entity> {
61
+ const network = NetworkEntity.getOrNull(parent)
62
+ if (network) {
63
+ for (const [entity, parent] of engine.getEntitiesWith(NetworkParent)) {
64
+ if (parent.entityId === network.entityId && parent.networkId === network.networkId) {
65
+ yield entity
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ function getFirstChild(entity: Entity) {
72
+ return Array.from(getChildren(entity))[0]
73
+ }
74
+
75
+ /**
76
+ * Returns the parent entity of the given entity.
77
+ */
78
+ function getParent(child: Entity): Entity | undefined {
79
+ const parent = NetworkParent.getOrNull(child)
80
+ if (!parent) return undefined
81
+ for (const [entity, network] of engine.getEntitiesWith(NetworkEntity)) {
82
+ if (parent.networkId === network.networkId && parent.entityId === network.entityId) {
83
+ return entity
84
+ }
85
+ }
86
+ return undefined
87
+ }
88
+
89
+ /**
90
+ * Adds the network parenting to sync entities.
91
+ * Equivalent to Transform.parent for local entities
92
+ */
93
+ function parentEntity(entity: Entity, parent: Entity) {
94
+ const network = NetworkEntity.getOrNull(parent)
95
+ if (!network) {
96
+ throw new Error('Entity is not sync. Call syncEntity on the parent.')
97
+ }
98
+
99
+ // Create network parent component
100
+ NetworkParent.createOrReplace(entity, network)
101
+
102
+ // If we dont have a transform for this entity, create an empty one to send it to the renderer
103
+ if (!Transform.getOrNull(entity)) {
104
+ Transform.create(entity)
105
+ } else {
106
+ // We force to send a tick update of the transform so we can send the NEW parent to the renderer
107
+ Transform.getMutable(entity)
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Removes the network parenting from an entity
113
+ */
114
+ function removeParent(entity: Entity) {
115
+ const network = NetworkEntity.getOrNull(entity)
116
+
117
+ if (!network) {
118
+ throw new Error('Entity is not sync')
119
+ }
120
+
121
+ NetworkParent.deleteFrom(entity)
122
+ }
123
+
124
+ return {
125
+ syncEntity,
126
+ getChildren,
127
+ getParent,
128
+ parentEntity,
129
+ removeParent,
130
+ getFirstChild
131
+ }
132
+ }
@@ -0,0 +1,64 @@
1
+ import {
2
+ TransportMessage,
3
+ PointerEventsResult,
4
+ GltfContainerLoadingState,
5
+ EntityUtils,
6
+ RESERVED_STATIC_ENTITIES,
7
+ CrdtMessageType,
8
+ SyncComponents as _SyncComponents,
9
+ NetworkEntity as _NetworkEntity,
10
+ NetworkParent as _NetworkParent,
11
+ IEngine
12
+ } from '@dcl/ecs'
13
+
14
+ export function syncFilter(engine: IEngine) {
15
+ const NetworkEntity = engine.getComponent(_NetworkEntity.componentId) as typeof _NetworkEntity
16
+ const SyncComponents = engine.getComponent(_SyncComponents.componentId) as typeof _SyncComponents
17
+
18
+ return function (message: Omit<TransportMessage, 'messageBuffer'>) {
19
+ const componentId = (message as any).componentId
20
+
21
+ if ([PointerEventsResult.componentId, GltfContainerLoadingState.componentId].includes(componentId)) {
22
+ return false
23
+ }
24
+
25
+ const [entityId] = EntityUtils.fromEntityId(message.entityId)
26
+
27
+ // filter messages from reserved entities.
28
+ if (entityId < RESERVED_STATIC_ENTITIES) {
29
+ return false
30
+ }
31
+
32
+ const network = NetworkEntity.getOrNull(message.entityId)
33
+ // Delete Network Entity Always
34
+ if (
35
+ message.type === CrdtMessageType.DELETE_ENTITY_NETWORK ||
36
+ (network && message.type === CrdtMessageType.DELETE_ENTITY)
37
+ ) {
38
+ return true
39
+ }
40
+
41
+ const sync = SyncComponents.getOrNull(message.entityId)
42
+ if (!sync) return false
43
+
44
+ // First component
45
+ if ((message as any).timestamp <= 1) {
46
+ return true
47
+ }
48
+
49
+ if (componentId === NetworkEntity.componentId) {
50
+ return false
51
+ }
52
+
53
+ // If there is a change in the network parent or syncComponents we should always sync
54
+ if (componentId === _NetworkParent.componentId || componentId === SyncComponents.componentId) {
55
+ return true
56
+ }
57
+
58
+ if (componentId && sync.componentIds.includes(componentId)) {
59
+ return true
60
+ }
61
+
62
+ return false
63
+ }
64
+ }
@@ -0,0 +1,13 @@
1
+ import { sendBinary } from '~system/CommunicationsController'
2
+ import { engine } from '@dcl/ecs'
3
+ import { addSyncTransport } from './message-bus-sync'
4
+ import { getUserData } from '~system/UserIdentity'
5
+
6
+ // initialize sync transport for sdk engine
7
+ const { getChildren, syncEntity, parentEntity, getParent, myProfile, removeParent, getFirstChild } = addSyncTransport(
8
+ engine,
9
+ sendBinary,
10
+ getUserData
11
+ )
12
+
13
+ export { getFirstChild, getChildren, syncEntity, parentEntity, getParent, myProfile, removeParent }