import { ReadWriteByteBuffer } from '@dcl/ecs/dist/serialization/ByteBuffer' import { CrdtMessageHeader, CrdtMessageProtocol, CrdtMessageType, IEngine, PutComponentOperation, PutNetworkComponentOperation, SyncComponents as _SyncComponents, NetworkEntity as _NetworkEntity, INetowrkEntity, VideoEvent, AudioEvent, EngineInfo, GltfContainerLoadingState, PhysicsCombinedForce, PhysicsCombinedImpulse, PointerEventsResult, RaycastResult, RealmInfo, TweenState, UiDropdown, UiDropdownResult, UiInput, UiInputResult, UiText, UiTransform, TriggerAreaResult, ComponentDefinition } from '@dcl/ecs' import { LIVEKIT_MAX_SIZE } from '@dcl/ecs/dist/systems/crdt' export const NOT_SYNC_COMPONENTS: ComponentDefinition[] = [ VideoEvent, TweenState, AudioEvent, EngineInfo, GltfContainerLoadingState, PhysicsCombinedForce, PhysicsCombinedImpulse, PointerEventsResult, RaycastResult, RealmInfo, UiDropdown, UiDropdownResult, UiInput, UiInputResult, UiTransform, UiText, TriggerAreaResult ] export const NOT_SYNC_COMPONENTS_IDS = NOT_SYNC_COMPONENTS.map(($) => $.componentId) export const NOT_SYNC_COMPONENTS_NAMES: string[] = [ 'asset-packs::Script', // ComponentName from: https://github.com/decentraland/asset-packs/blob/main/src/enums.ts 'asset-packs::ActionTypes' ] export function shouldSyncComponent(component: ComponentDefinition): boolean { return !( NOT_SYNC_COMPONENTS_IDS.includes(component.componentId) || NOT_SYNC_COMPONENTS_NAMES.includes(component.componentName) ) } export function getDesyncedComponents(engine: IEngine): ComponentDefinition[] { return [...NOT_SYNC_COMPONENTS, ...NOT_SYNC_COMPONENTS_NAMES.map(($) => engine.getComponentOrNull($))].filter( Boolean ) as ComponentDefinition[] } export function engineToCrdt(engine: IEngine): Uint8Array[] { const crdtBuffer = new ReadWriteByteBuffer() const networkBuffer = new ReadWriteByteBuffer() const NetworkEntity = engine.getComponent(_NetworkEntity.componentId) as INetowrkEntity const chunks: Uint8Array[] = [] for (const itComponentDefinition of engine.componentsIter()) { if (!shouldSyncComponent(itComponentDefinition)) { continue } itComponentDefinition.dumpCrdtStateToBuffer(crdtBuffer, (entity) => { const isNetworkEntity = NetworkEntity.has(entity) return isNetworkEntity }) } let header: CrdtMessageHeader | null while ((header = CrdtMessageProtocol.getHeader(crdtBuffer))) { if (header.type === CrdtMessageType.PUT_COMPONENT) { const message = PutComponentOperation.read(crdtBuffer)! const networkEntity = NetworkEntity.getOrNull(message.entityId) // Check if adding this message would exceed the size limit const currentBufferSize = networkBuffer.toBinary().byteLength const messageSize = message.data.byteLength if ((currentBufferSize + messageSize) / 1024 > LIVEKIT_MAX_SIZE) { // If the current buffer has content, save it as a chunk if (currentBufferSize > 0) { chunks.push(networkBuffer.toCopiedBinary()) networkBuffer.resetBuffer() } // If the message itself is larger than the limit, we need to handle it specially // For now, we'll skip it to prevent infinite loops if (messageSize / 1024 > LIVEKIT_MAX_SIZE) { console.error( `Message too large (${messageSize} bytes), skipping component ${message.componentId} for entity ${message.entityId}` ) continue } } if (networkEntity) { PutNetworkComponentOperation.write( networkEntity.entityId, message.timestamp, message.componentId, networkEntity.networkId, message.data, networkBuffer ) } } else { crdtBuffer.incrementReadOffset(header.length) } } // Add any remaining data as the final chunk if (networkBuffer.currentWriteOffset() > 0) { chunks.push(networkBuffer.toBinary()) } return chunks }