@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.
- package/internal/transports/logger.js +7 -4
- package/internal/transports/rendererTransport.js +3 -2
- package/message-bus.js +8 -6
- package/network/binary-message-bus.d.ts +18 -0
- package/network/binary-message-bus.js +68 -0
- package/network/entities.d.ts +11 -0
- package/network/entities.js +77 -0
- package/network/filter.d.ts +2 -0
- package/network/filter.js +37 -0
- package/network/index.d.ts +2 -0
- package/network/index.js +7 -0
- package/network/message-bus-sync.d.ts +17 -0
- package/network/message-bus-sync.js +52 -0
- package/network/state.js +39 -0
- package/network/utils.d.ts +18 -0
- package/network/utils.js +69 -0
- package/package.json +6 -6
- package/src/internal/transports/logger.ts +9 -4
- package/src/internal/transports/rendererTransport.ts +2 -1
- package/src/message-bus.ts +7 -9
- package/src/network/README.md +122 -0
- package/src/network/binary-message-bus.ts +72 -0
- package/src/network/entities.ts +132 -0
- package/src/network/filter.ts +64 -0
- package/src/network/index.ts +13 -0
- package/src/network/message-bus-sync.ts +97 -0
- package/src/network/state.ts +65 -0
- package/src/network/utils.ts +145 -0
- package/network-transport/client.d.ts +0 -5
- package/network-transport/client.js +0 -68
- package/network-transport/index.d.ts +0 -10
- package/network-transport/index.js +0 -29
- package/network-transport/server.d.ts +0 -2
- package/network-transport/server.js +0 -62
- package/network-transport/state.js +0 -11
- package/network-transport/types.d.ts +0 -36
- package/network-transport/types.js +0 -7
- package/network-transport/utils.d.ts +0 -8
- package/network-transport/utils.js +0 -46
- package/src/network-transport/client.ts +0 -77
- package/src/network-transport/index.ts +0 -45
- package/src/network-transport/server.ts +0 -66
- package/src/network-transport/state.ts +0 -13
- package/src/network-transport/types.ts +0 -41
- package/src/network-transport/utils.ts +0 -67
- /package/{network-transport → network}/state.d.ts +0 -0
package/network/utils.js
ADDED
@@ -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.
|
4
|
+
"version": "7.3.32-7170667436.commit-11a2181",
|
5
5
|
"author": "Decentraland",
|
6
6
|
"dependencies": {
|
7
|
-
"@dcl/ecs": "7.3.
|
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.
|
11
|
-
"@dcl/react-ecs": "7.3.
|
12
|
-
"@dcl/sdk-commands": "7.3.
|
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": "
|
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 =
|
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 (
|
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`
|
package/src/message-bus.ts
CHANGED
@@ -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
|
-
|
17
|
+
try {
|
18
|
+
const m = JSON.parse(e.message)
|
18
19
|
|
19
|
-
|
20
|
-
|
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
|
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 }
|