@dcl/sdk 7.22.6-25007982108.commit-83012ab → 7.22.6-25321038582.commit-63ddb3f

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 (78) hide show
  1. package/atom.d.ts +19 -0
  2. package/atom.js +83 -0
  3. package/future.d.ts +8 -0
  4. package/future.js +26 -0
  5. package/index.js +1 -5
  6. package/internal/transports/rendererTransport.d.ts +0 -4
  7. package/internal/transports/rendererTransport.js +1 -8
  8. package/network/binary-message-bus.d.ts +6 -3
  9. package/network/binary-message-bus.js +9 -5
  10. package/network/chunking.d.ts +5 -0
  11. package/network/chunking.js +38 -0
  12. package/network/entities.js +11 -3
  13. package/network/events/implementation.d.ts +93 -0
  14. package/network/events/implementation.js +221 -0
  15. package/network/events/index.d.ts +42 -0
  16. package/network/events/index.js +43 -0
  17. package/network/events/protocol.d.ts +27 -0
  18. package/network/events/protocol.js +66 -0
  19. package/network/events/registry.d.ts +8 -0
  20. package/network/events/registry.js +3 -0
  21. package/network/index.d.ts +8 -2
  22. package/network/index.js +16 -3
  23. package/network/message-bus-sync.d.ts +14 -1
  24. package/network/message-bus-sync.js +161 -103
  25. package/network/server/index.d.ts +14 -0
  26. package/network/server/index.js +219 -0
  27. package/network/server/utils.d.ts +18 -0
  28. package/network/server/utils.js +135 -0
  29. package/network/state.js +8 -7
  30. package/package.json +6 -6
  31. package/platform/index.d.ts +5 -0
  32. package/platform/index.js +29 -0
  33. package/players/index.d.ts +0 -1
  34. package/players/index.js +1 -2
  35. package/server/env-var.d.ts +15 -0
  36. package/server/env-var.js +31 -0
  37. package/server/index.d.ts +2 -0
  38. package/server/index.js +3 -0
  39. package/server/storage/constants.d.ts +23 -0
  40. package/server/storage/constants.js +2 -0
  41. package/server/storage/index.d.ts +22 -0
  42. package/server/storage/index.js +29 -0
  43. package/server/storage/player.d.ts +43 -0
  44. package/server/storage/player.js +92 -0
  45. package/server/storage/scene.d.ts +38 -0
  46. package/server/storage/scene.js +90 -0
  47. package/server/storage-url.d.ts +10 -0
  48. package/server/storage-url.js +29 -0
  49. package/server/utils.d.ts +35 -0
  50. package/server/utils.js +56 -0
  51. package/src/atom.ts +98 -0
  52. package/src/future.ts +38 -0
  53. package/src/index.ts +1 -5
  54. package/src/internal/transports/rendererTransport.ts +0 -13
  55. package/src/network/binary-message-bus.ts +9 -4
  56. package/src/network/chunking.ts +45 -0
  57. package/src/network/entities.ts +10 -2
  58. package/src/network/events/implementation.ts +271 -0
  59. package/src/network/events/index.ts +48 -0
  60. package/src/network/events/protocol.ts +94 -0
  61. package/src/network/events/registry.ts +18 -0
  62. package/src/network/index.ts +40 -3
  63. package/src/network/message-bus-sync.ts +174 -110
  64. package/src/network/server/index.ts +301 -0
  65. package/src/network/server/utils.ts +189 -0
  66. package/src/network/state.ts +9 -5
  67. package/src/platform/index.ts +35 -0
  68. package/src/players/index.ts +0 -2
  69. package/src/server/env-var.ts +36 -0
  70. package/src/server/index.ts +2 -0
  71. package/src/server/storage/constants.ts +22 -0
  72. package/src/server/storage/index.ts +44 -0
  73. package/src/server/storage/player.ts +156 -0
  74. package/src/server/storage/scene.ts +149 -0
  75. package/src/server/storage-url.ts +34 -0
  76. package/src/server/utils.ts +73 -0
  77. package/src/testing/runtime.ts +3 -3
  78. package/testing/runtime.js +4 -4
@@ -0,0 +1,94 @@
1
+ import { ReadWriteByteBuffer } from '@dcl/ecs/dist/serialization/ByteBuffer'
2
+ import { Schemas } from '@dcl/ecs'
3
+ import { EventSchemas, EventTypes, EventSchemaRegistry } from './registry'
4
+
5
+ // Event envelope that wraps all events with metadata
6
+ const EventEnvelope = Schemas.Map({
7
+ eventType: Schemas.String,
8
+ timestamp: Schemas.Int64
9
+ })
10
+
11
+ /**
12
+ * Encode an event into a binary buffer
13
+ * @param eventType - The type of event from the registry
14
+ * @param data - The event data matching the schema
15
+ * @param registry - Optional custom registry (defaults to EventSchemas)
16
+ * @returns Binary buffer containing the encoded event
17
+ */
18
+ export function encodeEvent<T extends EventSchemaRegistry = typeof EventSchemas, K extends keyof T = keyof T>(
19
+ eventType: K,
20
+ data: EventTypes<T>[K],
21
+ registry: T = EventSchemas as T
22
+ ): Uint8Array {
23
+ const buffer = new ReadWriteByteBuffer()
24
+
25
+ // Write envelope with event type and timestamp
26
+ EventEnvelope.serialize(
27
+ {
28
+ eventType: eventType as string,
29
+ timestamp: Date.now()
30
+ },
31
+ buffer
32
+ )
33
+
34
+ // Get the schema for this event type
35
+ const schema = registry[eventType]
36
+ if (!schema) {
37
+ throw new Error(`Unknown event type: ${String(eventType)}`)
38
+ }
39
+
40
+ // Write the typed payload
41
+ schema.serialize(data, buffer)
42
+
43
+ return buffer.toBinary()
44
+ }
45
+
46
+ /**
47
+ * Decode a binary buffer into an event
48
+ * @param data - Binary buffer containing the encoded event
49
+ * @param registry - Optional custom registry (defaults to EventSchemas)
50
+ * @returns Decoded event with type, payload, and timestamp
51
+ */
52
+ export function decodeEvent<T extends EventSchemaRegistry = typeof EventSchemas>(
53
+ data: Uint8Array,
54
+ registry: T = EventSchemas as T
55
+ ): {
56
+ eventType: keyof T
57
+ payload: EventTypes<T>[keyof T]
58
+ timestamp: number
59
+ } {
60
+ const buffer = new ReadWriteByteBuffer()
61
+ buffer.writeBuffer(data, false)
62
+
63
+ // Read envelope
64
+ const envelope = EventEnvelope.deserialize(buffer)
65
+ const eventType = envelope.eventType as keyof T
66
+
67
+ // Get the schema for this event type
68
+ const schema = registry[eventType]
69
+ if (!schema) {
70
+ throw new Error(`Unknown event type: ${String(eventType)}`)
71
+ }
72
+
73
+ // Read the typed payload
74
+ const payload = schema.deserialize(buffer)
75
+
76
+ return {
77
+ eventType,
78
+ payload,
79
+ timestamp: envelope.timestamp
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Validate if an event type exists in the registry
85
+ * @param eventType - The event type to check
86
+ * @param registry - Optional custom registry (defaults to EventSchemas)
87
+ * @returns True if the event type exists
88
+ */
89
+ export function isValidEventType<T extends EventSchemaRegistry = typeof EventSchemas>(
90
+ eventType: string,
91
+ registry: T = EventSchemas as T
92
+ ): eventType is Extract<keyof T, string> {
93
+ return eventType in registry
94
+ }
@@ -0,0 +1,18 @@
1
+ import { ISchema } from '@dcl/ecs'
2
+
3
+ // Base type for event schema registry
4
+ export type EventSchemaRegistry = Record<string, ISchema>
5
+
6
+ // Type extraction from schemas
7
+ export type EventTypes<T extends EventSchemaRegistry = EventSchemaRegistry> = {
8
+ [K in keyof T]: T[K] extends ISchema<infer U> ? U : never
9
+ }
10
+
11
+ // Global interface that users can augment with their own events
12
+ export type RegisteredEvents = EventSchemaRegistry
13
+
14
+ // Default empty registry
15
+ export const EventSchemas = {} as RegisteredEvents
16
+
17
+ // Helper to ensure user events conform to the registry type
18
+ export type ValidateEventRegistry<T extends EventSchemaRegistry> = T
@@ -2,9 +2,46 @@ import { sendBinary } from '~system/CommunicationsController'
2
2
  import { engine } from '@dcl/ecs'
3
3
  import { addSyncTransport } from './message-bus-sync'
4
4
  import { getUserData } from '~system/UserIdentity'
5
+ import { isServer as isServerApi } from '~system/EngineApi'
6
+ import { Atom } from '../atom'
7
+
8
+ // Create isServer atom for consistent state
9
+ const isServerAtom = Atom<boolean>(false)
10
+ void isServerApi({}).then((response) => {
11
+ isServerAtom.swap(!!response.isServer)
12
+ })
13
+
14
+ // Helper function to check if running on server
15
+ export function isServer(): boolean {
16
+ return isServerAtom.getOrNull() ?? false
17
+ }
5
18
 
6
19
  // initialize sync transport for sdk engine
7
- const { getChildren, syncEntity, parentEntity, getParent, myProfile, removeParent, getFirstChild, isStateSyncronized } =
8
- addSyncTransport(engine, sendBinary, getUserData)
20
+ const {
21
+ getChildren,
22
+ syncEntity,
23
+ parentEntity,
24
+ getParent,
25
+ myProfile,
26
+ removeParent,
27
+ getFirstChild,
28
+ isStateSyncronized,
29
+ binaryMessageBus,
30
+ eventBus
31
+ } = addSyncTransport(engine, sendBinary, getUserData, isServerApi, 'network')
32
+
33
+ // Re-export the room messaging system
34
+ export { registerMessages, getRoom } from './events'
9
35
 
10
- export { getFirstChild, getChildren, syncEntity, parentEntity, getParent, myProfile, removeParent, isStateSyncronized }
36
+ export {
37
+ getFirstChild,
38
+ getChildren,
39
+ syncEntity,
40
+ parentEntity,
41
+ getParent,
42
+ myProfile,
43
+ removeParent,
44
+ isStateSyncronized,
45
+ binaryMessageBus,
46
+ eventBus
47
+ }
@@ -1,27 +1,53 @@
1
- import { IEngine, Transport, RealmInfo, PlayerIdentityData } from '@dcl/ecs'
1
+ import { IEngine, Transport, RealmInfo } from '@dcl/ecs'
2
2
  import { type SendBinaryRequest, type SendBinaryResponse } from '~system/CommunicationsController'
3
3
 
4
4
  import { syncFilter } from './filter'
5
5
  import { engineToCrdt } from './state'
6
- import { BinaryMessageBus, CommsMessage, decodeString, encodeString } from './binary-message-bus'
6
+ import { BinaryMessageBus, CommsMessage } from './binary-message-bus'
7
7
  import { fetchProfile } from './utils'
8
8
  import { entityUtils } from './entities'
9
+ import { createServerValidator } from './server'
9
10
  import { GetUserDataRequest, GetUserDataResponse } from '~system/UserIdentity'
10
11
  import { definePlayerHelper } from '../players'
11
12
  import { serializeCrdtMessages } from '../internal/transports/logger'
13
+ import { IsServerRequest, IsServerResponse } from '~system/EngineApi'
14
+ import { Atom } from '../atom'
15
+ import { setGlobalRoom, Room } from './events/implementation'
12
16
 
13
17
  export type IProfile = { networkId: number; userId: string }
14
18
  // user that we asked for the inital crdt state
19
+ export const AUTH_SERVER_PEER_ID = 'authoritative-server'
20
+ export const DEBUG_NETWORK_MESSAGES = () => (globalThis as any).DEBUG_NETWORK_MESSAGES ?? false
21
+
22
+ // Test environment detection without 'as any'
23
+ const isTestEnvironment = (): boolean => {
24
+ try {
25
+ if (typeof globalThis === 'undefined') return false
26
+ const globalWithProcess = globalThis as unknown as { process?: { env?: { NODE_ENV?: string } } }
27
+ return globalWithProcess.process?.env?.NODE_ENV === 'test'
28
+ } catch {
29
+ return false
30
+ }
31
+ }
32
+
15
33
  export function addSyncTransport(
16
34
  engine: IEngine,
17
35
  sendBinary: (msg: SendBinaryRequest) => Promise<SendBinaryResponse>,
18
- getUserData: (value: GetUserDataRequest) => Promise<GetUserDataResponse>
36
+ getUserData: (value: GetUserDataRequest) => Promise<GetUserDataResponse>,
37
+ isServerFn: (request: IsServerRequest) => Promise<IsServerResponse>,
38
+ name: string
19
39
  ) {
20
- const DEBUG_NETWORK_MESSAGES = () => (globalThis as any).DEBUG_NETWORK_MESSAGES ?? false
21
40
  // Profile Info
22
41
  const myProfile: IProfile = {} as IProfile
23
42
  fetchProfile(myProfile!, getUserData)
24
43
 
44
+ const isServerAtom = Atom<boolean>()
45
+ const isRoomReadyAtom = Atom<boolean>(false)
46
+
47
+ void isServerFn({}).then(($: IsServerResponse) => {
48
+ return isServerAtom.swap(!!$.isServer)
49
+ })
50
+
25
51
  // Entity utils
26
52
  const entityDefinitions = entityUtils(engine, myProfile)
27
53
 
@@ -40,108 +66,190 @@ export function addSyncTransport(
40
66
  const players = definePlayerHelper(engine)
41
67
 
42
68
  let stateIsSyncronized = false
43
- let transportInitialzed = false
44
69
 
70
+ /**
71
+ * We need to wait till 2 ticks that is when the engine is ready to send new messages.
72
+ * The first tick is for the client engine processing the CRDT messages,
73
+ * and the second one are the messages created by the main() function.
74
+ * So to avoid sending those messages, that all the clients have, through the network we put this validation here.
75
+ */
76
+ let tick = 0
77
+ const TRANSPORT_INITIALIZED_NUMBER = isTestEnvironment() ? 0 : 2
45
78
  // Add Sync Transport
46
79
  const transport: Transport = {
47
80
  filter: syncFilter(engine),
48
81
  send: async (messages) => {
49
- for (const message of [messages].flat()) {
50
- if (message.byteLength && transportInitialzed) {
82
+ if (tick <= TRANSPORT_INITIALIZED_NUMBER) tick++
83
+ for (const message of tick > TRANSPORT_INITIALIZED_NUMBER ? [messages].flat() : []) {
84
+ if (message.byteLength) {
51
85
  DEBUG_NETWORK_MESSAGES() &&
52
86
  console.log(...Array.from(serializeCrdtMessages('[NetworkMessage sent]:', message, engine)))
53
- binaryMessageBus.emit(CommsMessage.CRDT, message)
87
+
88
+ // Convert regular messages to network messages for broadcasting with chunking
89
+ for (const chunk of serverValidator.convertRegularToNetworkMessage(message)) {
90
+ binaryMessageBus.emit(CommsMessage.CRDT, chunk)
91
+ }
54
92
  }
55
93
  }
56
94
  const peerMessages = getMessagesToSend()
57
- let totalSize = 0
58
- for (const message of peerMessages) {
59
- for (const data of message.data) {
60
- totalSize += data.byteLength
61
- }
62
- }
63
- if (totalSize) {
64
- DEBUG_NETWORK_MESSAGES() && console.log('Sending network messages: ', totalSize / 1024, 'KB')
65
- }
66
95
  const response = await sendBinary({ data: [], peerData: peerMessages })
67
96
  binaryMessageBus.__processMessages(response.data)
68
- transportInitialzed = true
69
97
  },
70
- type: 'network'
98
+ type: name
71
99
  }
100
+
101
+ // Server validation setup
102
+ const serverValidator = createServerValidator({
103
+ engine,
104
+ binaryMessageBus
105
+ })
106
+
107
+ // Initialize Event Bus with registered schemas
108
+ const eventBus = new Room(engine, binaryMessageBus, isServerAtom, isRoomReadyAtom)
109
+
110
+ // Set global eventBus instance
111
+ setGlobalRoom(eventBus)
112
+
72
113
  engine.addTransport(transport)
73
114
  // End add sync transport
74
115
 
75
116
  // Receive & Process CRDT_STATE
76
- binaryMessageBus.on(CommsMessage.RES_CRDT_STATE, (value) => {
77
- const { sender, data } = decodeCRDTState(value)
78
- if (sender !== myProfile.userId) return
117
+ binaryMessageBus.on(CommsMessage.REQ_CRDT_STATE, async (data, sender) => {
118
+ DEBUG_NETWORK_MESSAGES() && console.log('[REQ_CRDT_STATE]', sender, Date.now())
119
+ const chunks = engineToCrdt(engine)
120
+ if (chunks.length === 0) {
121
+ DEBUG_NETWORK_MESSAGES() && console.log('[Emiting empty state:]', sender, Date.now())
122
+ binaryMessageBus.emit(CommsMessage.RES_CRDT_STATE, new Uint8Array(), [sender])
123
+ } else {
124
+ for (const chunk of chunks) {
125
+ DEBUG_NETWORK_MESSAGES() && console.log('[Emiting:]', sender, Date.now())
126
+ binaryMessageBus.emit(CommsMessage.RES_CRDT_STATE, chunk, [sender])
127
+ }
128
+ }
129
+ })
130
+ binaryMessageBus.on(CommsMessage.RES_CRDT_STATE, async (data, sender) => {
131
+ requestingState = false
132
+ elapsedTimeSinceRequest = 0
133
+ if (isServerAtom.getOrNull() || sender !== AUTH_SERVER_PEER_ID) return
79
134
  DEBUG_NETWORK_MESSAGES() && console.log('[Processing CRDT State]', data.byteLength / 1024, 'KB')
80
- transport.onmessage!(data)
135
+ if (data.byteLength > 0) {
136
+ transport.onmessage!(serverValidator.processClientMessages(data, sender))
137
+ }
81
138
  stateIsSyncronized = true
82
- })
83
-
84
- // Answer to REQ_CRDT_STATE
85
- binaryMessageBus.on(CommsMessage.REQ_CRDT_STATE, async (_, userId) => {
86
- DEBUG_NETWORK_MESSAGES() && console.log(`Sending CRDT State to: ${userId}`)
87
139
 
88
- for (const chunk of engineToCrdt(engine)) {
89
- binaryMessageBus.emit(CommsMessage.RES_CRDT_STATE, encodeCRDTState(userId, chunk), [userId])
140
+ // IMPORTANT: Only mark room as ready AFTER state is synchronized
141
+ // This ensures comms is truly connected and working
142
+ const realmInfo = RealmInfo.getOrNull(engine.RootEntity)
143
+ if (realmInfo) {
144
+ DEBUG_NETWORK_MESSAGES() && console.log('[isRoomReady] Marking room as ready after state sync')
145
+ isRoomReadyAtom.swap(true)
90
146
  }
91
147
  })
92
148
 
93
- // Process CRDT messages here
94
- binaryMessageBus.on(CommsMessage.CRDT, (value) => {
149
+ // received message from the network
150
+ binaryMessageBus.on(CommsMessage.CRDT, (value, sender) => {
151
+ const isServer = isServerAtom.getOrNull()
95
152
  DEBUG_NETWORK_MESSAGES() &&
96
- console.log(Array.from(serializeCrdtMessages('[NetworkMessage received]:', value, engine)))
97
- transport.onmessage!(value)
98
- })
99
-
100
- async function requestState(retryCount: number = 1) {
101
- let players = Array.from(engine.getEntitiesWith(PlayerIdentityData))
102
- DEBUG_NETWORK_MESSAGES() && console.log(`Requesting state. Players connected: ${players.length - 1}`)
103
-
104
- if (!RealmInfo.getOrNull(engine.RootEntity)?.isConnectedSceneRoom) {
105
- DEBUG_NETWORK_MESSAGES() && console.log(`Aborting Requesting state?. Disconnected`)
106
- return
153
+ console.log(
154
+ transport.type,
155
+ ...Array.from(serializeCrdtMessages('[NetworkMessage received]:', value, engine)),
156
+ isServer
157
+ )
158
+ if (isServer) {
159
+ transport.onmessage!(serverValidator.processServerMessages(value, sender))
160
+ } else if (sender === AUTH_SERVER_PEER_ID) {
161
+ // Process network messages from server and convert to regular messages
162
+ transport.onmessage!(serverValidator.processClientMessages(value, sender))
107
163
  }
164
+ })
108
165
 
109
- binaryMessageBus.emit(CommsMessage.REQ_CRDT_STATE, new Uint8Array())
166
+ // received authoritative message from server - force apply to fix invalid local state
167
+ binaryMessageBus.on(CommsMessage.CRDT_AUTHORITATIVE, (value, sender) => {
168
+ // Only accept authoritative messages from authoritative server
169
+ if (sender !== AUTH_SERVER_PEER_ID) return
110
170
 
111
- // Wait ~5s for the response.
112
- await sleep(5000)
171
+ // DEBUG_NETWORK_MESSAGES() &&
172
+ console.log('[AUTHORITATIVE] Received authoritative message from server:', value.byteLength, 'bytes')
113
173
 
114
- players = Array.from(engine.getEntitiesWith(PlayerIdentityData))
174
+ // Process authoritative messages by forcing them through normal CRDT processing
175
+ // but with a timestamp that's guaranteed to be accepted
176
+ const authoritativeBuffer = serverValidator.processClientMessages(value, sender, true)
177
+ if (authoritativeBuffer.byteLength > 0) {
178
+ // Apply authoritative message through normal transport, but the server's messages
179
+ // should be processed as authoritative with special timestamp handling
180
+ transport.onmessage!(authoritativeBuffer)
115
181
 
116
- if (!stateIsSyncronized) {
117
- if (players.length > 1 && retryCount <= 2) {
118
- DEBUG_NETWORK_MESSAGES() &&
119
- console.log(`Requesting state again ${retryCount} (no response). Players connected: ${players.length - 1}`)
120
- void requestState(retryCount + 1)
121
- } else {
122
- DEBUG_NETWORK_MESSAGES() && console.log('No active players. State syncronized')
123
- stateIsSyncronized = true
124
- }
182
+ DEBUG_NETWORK_MESSAGES() && console.log('[AUTHORITATIVE] Applied server authoritative message to local state')
125
183
  }
126
- }
184
+ })
127
185
 
128
186
  players.onEnterScene((player) => {
129
187
  DEBUG_NETWORK_MESSAGES() && console.log('[onEnterScene]', player.userId)
188
+ if (!isServerAtom.getOrNull() && myProfile.userId === player.userId) {
189
+ requestState()
190
+ }
130
191
  })
131
192
 
132
193
  // Asks for the REQ_CRDT_STATE when its connected to comms
133
194
  RealmInfo.onChange(engine.RootEntity, (value) => {
195
+ const isServer = isServerAtom.getOrNull()
196
+
134
197
  if (!value?.isConnectedSceneRoom) {
135
- DEBUG_NETWORK_MESSAGES() && console.log('Disconnected from comms')
136
- stateIsSyncronized = false
198
+ // Only react when actually transitioning from ready to not ready
199
+ if (isRoomReadyAtom.getOrNull() === true) {
200
+ DEBUG_NETWORK_MESSAGES() && console.log('Disconnected from comms')
201
+ isRoomReadyAtom.swap(false)
202
+ if (!isServer) {
203
+ stateIsSyncronized = false
204
+ }
205
+ }
137
206
  }
138
207
 
139
208
  if (value?.isConnectedSceneRoom) {
140
- DEBUG_NETWORK_MESSAGES() && console.log('Connected to comms')
209
+ requestState()
210
+
211
+ // For servers, mark as ready immediately when connected
212
+ // (servers don't need to sync state from anyone)
213
+ if (isServer && isRoomReadyAtom.getOrNull() === false) {
214
+ DEBUG_NETWORK_MESSAGES() && console.log('[isRoomReady] Server marking room as ready')
215
+ isRoomReadyAtom.swap(true)
216
+ }
217
+ // For clients, room will be marked ready after receiving CRDT state (above)
218
+ }
219
+ })
220
+
221
+ let requestingState = false
222
+ let elapsedTimeSinceRequest = 0
223
+ const STATE_REQUEST_RETRY_INTERVAL = 2.0 // seconds
224
+
225
+ /**
226
+ * Why we have to request the state if we have a server that can send us the state when we joined?
227
+ * The thing is that when the server detects a new JOIN_PARTICIPANT on livekit room, it sends automatically the state to that peer.
228
+ * But in unity, it takes more time, so that message is not being delivered to the client.
229
+ * So instead, when we are finally connected to the room, we request the state, and then the server answers with the state :)
230
+ *
231
+ * If no response is received within 2 seconds, the request is automatically retried.
232
+ */
233
+ function requestState() {
234
+ if (isServerAtom.getOrNull()) return
235
+ if (RealmInfo.getOrNull(engine.RootEntity)?.isConnectedSceneRoom && !requestingState) {
236
+ requestingState = true
237
+ elapsedTimeSinceRequest = 0
238
+ DEBUG_NETWORK_MESSAGES() && console.log('Requesting state...')
239
+ binaryMessageBus.emit(CommsMessage.REQ_CRDT_STATE, new Uint8Array())
141
240
  }
241
+ }
142
242
 
143
- if (value?.isConnectedSceneRoom && !stateIsSyncronized) {
144
- void requestState()
243
+ // System to retry state request if no response is received within the retry interval
244
+ engine.addSystem((dt: number) => {
245
+ if (requestingState && !stateIsSyncronized) {
246
+ elapsedTimeSinceRequest += dt
247
+ if (elapsedTimeSinceRequest >= STATE_REQUEST_RETRY_INTERVAL) {
248
+ DEBUG_NETWORK_MESSAGES() && console.log('State request timed out, retrying...')
249
+ elapsedTimeSinceRequest = 0
250
+ requestingState = false
251
+ requestState()
252
+ }
145
253
  }
146
254
  })
147
255
 
@@ -153,56 +261,12 @@ export function addSyncTransport(
153
261
  return stateIsSyncronized
154
262
  }
155
263
 
156
- function sleep(ms: number) {
157
- return new Promise<void>((resolve) => {
158
- let timer = 0
159
- function sleepSystem(dt: number) {
160
- timer += dt
161
- if (timer * 1000 >= ms) {
162
- engine.removeSystem(sleepSystem)
163
- resolve()
164
- }
165
- }
166
- engine.addSystem(sleepSystem)
167
- })
168
- }
169
-
170
264
  return {
171
265
  ...entityDefinitions,
172
266
  myProfile,
173
- isStateSyncronized
267
+ isStateSyncronized,
268
+ binaryMessageBus,
269
+ eventBus,
270
+ isRoomReadyAtom
174
271
  }
175
272
  }
176
-
177
- /**
178
- * Messages Protocol Encoding
179
- *
180
- * CRDT: Plain Uint8Array
181
- *
182
- * CRDT_STATE_RES { sender: string, data: Uint8Array}
183
- */
184
- function decodeCRDTState(data: Uint8Array) {
185
- let offset = 0
186
- const r = new Uint8Array(data)
187
- const view = new DataView(r.buffer)
188
- const senderLength = view.getUint8(offset)
189
- offset += 1
190
- const sender = decodeString(data.subarray(1, senderLength + 1))
191
- offset += senderLength
192
- const state = r.subarray(offset)
193
-
194
- return { sender, data: state }
195
- }
196
-
197
- function encodeCRDTState(address: string, data: Uint8Array) {
198
- // address to uint8array
199
- const addressBuffer = encodeString(address)
200
- const addressOffset = 1
201
- const messageLength = addressOffset + addressBuffer.byteLength + data.byteLength
202
-
203
- const serializedMessage = new Uint8Array(messageLength)
204
- serializedMessage.set(new Uint8Array([addressBuffer.byteLength]), 0)
205
- serializedMessage.set(addressBuffer, 1)
206
- serializedMessage.set(data, addressBuffer.byteLength + 1)
207
- return serializedMessage
208
- }