public ulong CalculateVersionHash() { if (m_RpcData.Length >= ushort.MaxValue) { throw new InvalidOperationException(String.Format("RpcSystem does not support more than {0} RPCs", ushort.MaxValue)); } for (int i = 0; i < m_RpcData.Length; ++i) { if (m_RpcData[i].TypeHash == 0) #if ENABLE_UNITY_COLLECTIONS_CHECKS { throw new InvalidOperationException(String.Format("Missing RPC registration for {0} which is used to send data", m_RpcData[i].RpcType.GetManagedType())); } #else { throw new InvalidOperationException("Missing RPC registration for RPC which is used to send"); } #endif } m_RpcData.Sort(); m_RpcTypeHashToIndex.Clear(); for (int i = 0; i < m_RpcData.Length; ++i) { m_RpcTypeHashToIndex.Add(m_RpcData[i].TypeHash, i); } ulong hash = m_RpcData[0].TypeHash; for (int i = 0; i < m_RpcData.Length; ++i) { hash = TypeHash.CombineFNV1A64(hash, m_RpcData[i].TypeHash); } m_CanRegister = false; return(hash); }
public static ulong ComputeVariantHash(Mono.Cecil.TypeReference variantType, Mono.Cecil.CustomAttribute attribute) { var hash = TypeHash.FNV1A64(attribute.AttributeType.FullName); var componentType = attribute.ConstructorArguments[0].Value as Mono.Cecil.TypeReference; var variantName = variantType.FullName; hash = TypeHash.CombineFNV1A64(hash, TypeHash.FNV1A64(componentType.FullName)); hash = TypeHash.CombineFNV1A64(hash, TypeHash.FNV1A64(variantName)); return(hash); }
public ulong CalculateGhostCollectionHash() { if (!m_GhostTypeCollection.IsCreated) { throw new InvalidOperationException("GhostCollectionSystem.CalculateGhostCollectionHash must be called after the system has configured the ghost collection"); } m_GhostTypeCollectionHash = 0; if (m_GhostTypeCollection.Length > 0) { m_GhostTypeCollectionHash = HashGhostType(m_GhostTypeCollection[0]); for (int i = 1; i < m_GhostTypeCollection.Length; ++i) { var ghotsTypeHash = HashGhostType(m_GhostTypeCollection[i]); m_GhostTypeCollectionHash = TypeHash.CombineFNV1A64(m_GhostTypeCollectionHash, ghotsTypeHash); } } return(m_GhostTypeCollectionHash); }
//Hash requirements: // R0: if components are different or in different order the hash should change // R1: different size, owneroffsets, maskbits, partialcomponents etc must result in a different hash // R2: if a ghost present the same components, with the same fields but different [GhostField] attributes (such as, subType, interpoled, composite) // must result in a different hash, even though the resulting serialization sizes and masks are the same public ulong CalculateComponentCollectionHash() { //Lazy create the component collection if not created when we want to compute the component collection hash if (!m_ComponentCollectionInitialized) { CreateComponentCollection(); } ulong componentCollectionHash = 0; var ghostComponentCollection = EntityManager.GetBuffer <GhostComponentSerializer.State>(m_CollectionSingleton); for (int i = 0; i < ghostComponentCollection.Length; ++i) { var comp = ghostComponentCollection[i]; if (comp.SerializerHash != 0) { componentCollectionHash = TypeHash.CombineFNV1A64(componentCollectionHash, comp.SerializerHash); } } return(componentCollectionHash); }
//Hash requirements: // R0: if components are different or in different order the hash should change // R1: different size, owneroffsets, maskbits, partialcomponents etc must result in a different hash // R2: if a ghost present the same components, with the same fields but different [GhostField] attributes (such as, subType, interpoled, composite) // must result in a different hash, even though the resulting serialization sizes and masks are the same public ulong CalculateComponentCollectionHash() { //Lazy create the component collection if not created when we want to compute the component collection hash if (!m_GhostComponentCollection.IsCreated) { CreateComponentCollection(); } ulong componentCollectionHash = 0; if (m_GhostComponentCollection.Length > 0) { for (int i = 0; i < m_GhostComponentCollection.Length; ++i) { var comp = m_GhostComponentCollection[i]; if (comp.SerializerHash != 0) { componentCollectionHash = TypeHash.CombineFNV1A64(componentCollectionHash, comp.SerializerHash); } } } return(componentCollectionHash); }
protected override void OnUpdate() { using (var context = new BlobAssetComputationContext <int, GhostPrefabMetaData>(BlobAssetStore, 16, Allocator.Temp)) { Entities.ForEach((GhostAuthoringComponent ghostAuthoring) => { bool isPrefab = !ghostAuthoring.gameObject.scene.IsValid() || ghostAuthoring.ForcePrefabConversion; var target = GetConversionTarget(this, isPrefab); // Check if the ghost is valid before starting to process if (String.IsNullOrEmpty(ghostAuthoring.prefabId)) { throw new InvalidOperationException($"The ghost {ghostAuthoring.gameObject.name} is not a valid prefab, all ghosts must be the top-level GameObject in a prefab. Ghost instances in scenes must be instances of such prefabs and changes should be made on the prefab asset, not the prefab instance"); } if (!isPrefab && ghostAuthoring.DefaultGhostMode == GhostAuthoringComponent.GhostMode.OwnerPredicted && target != NetcodeConversionTarget.Server) { throw new InvalidOperationException($"Cannot convert a owner predicted ghost {ghostAuthoring.Name} as a scene instance"); } if (!isPrefab && DstEntityManager.World.GetExistingSystem <ClientSimulationSystemGroup>() != null) { throw new InvalidOperationException($"The ghost {ghostAuthoring.gameObject.name} cannot be created on the client, either put it in a sub-scene or spawn it on the server only"); } if (ghostAuthoring.prefabId.Length != 32) { throw new InvalidOperationException("Invalid guid for ghost prefab type"); } // All ghosts should have a linked entity group DeclareLinkedEntityGroup(ghostAuthoring.gameObject); var entity = GetPrimaryEntity(ghostAuthoring); // Generate a ghost type component so the ghost can be identified by mathcing prefab asset guid var ghostType = new GhostTypeComponent(); ghostType.guid0 = Convert.ToUInt32(ghostAuthoring.prefabId.Substring(0, 8), 16); ghostType.guid1 = Convert.ToUInt32(ghostAuthoring.prefabId.Substring(8, 8), 16); ghostType.guid2 = Convert.ToUInt32(ghostAuthoring.prefabId.Substring(16, 8), 16); ghostType.guid3 = Convert.ToUInt32(ghostAuthoring.prefabId.Substring(24, 8), 16); DstEntityManager.AddComponentData(entity, ghostType); // FIXME: maybe stripping should be individual systems running before this to make sure it can be changed in a way that always triggers a reconvert - and to avoid reflection var components = DstEntityManager.GetComponentTypes(entity); if (target != NetcodeConversionTarget.Client) { // If this ghost should be usable on a server we must add a shared ghost type to make sure different ghost types // with the same archetype end up in different chunks. If conversion is client and server the client needs to remove // this at runtime DstEntityManager.AddSharedComponentData(entity, new SharedGhostTypeComponent { SharedValue = ghostType }); } if (target != NetcodeConversionTarget.Server) { // Converting to client or client and server, if client and server this should be stripped from servers at runtime DstEntityManager.AddComponentData(entity, new SnapshotData()); DstEntityManager.AddBuffer <SnapshotDataBuffer>(entity); } // All types have the ghost components DstEntityManager.AddComponentData(entity, new GhostComponent()); // No need to add the predicted ghost component for interpolated only ghosts if the data is only used by the client if (target != NetcodeConversionTarget.Client || ghostAuthoring.SupportedGhostModes != GhostAuthoringComponent.GhostModeMask.Interpolated) { DstEntityManager.AddComponentData(entity, new PredictedGhostComponent()); } if (target == NetcodeConversionTarget.Server) { // If converting server-only data we can remove all components which are not used on the server foreach (var comp in components) { var attr = comp.GetManagedType().GetCustomAttribute <GhostComponentAttribute>(); if (attr != null && (attr.PrefabType & GhostPrefabType.Server) == 0) { DstEntityManager.RemoveComponent(entity, comp); } } } else if (target == NetcodeConversionTarget.Client) { // If converting client-only data we can remove all components which are not used on the client // If the ghost is interpolated only we can also remove all componens which are not used on interpolated clients, // and if it is predicted only we can remove everything which is not used on predicted clients foreach (var comp in components) { var attr = comp.GetManagedType().GetCustomAttribute <GhostComponentAttribute>(); if (attr == null) { continue; } if ((attr.PrefabType & GhostPrefabType.Client) == 0) { DstEntityManager.RemoveComponent(entity, comp); } else if (ghostAuthoring.SupportedGhostModes == GhostAuthoringComponent.GhostModeMask.Interpolated && (attr.PrefabType & GhostPrefabType.InterpolatedClient) == 0) { DstEntityManager.RemoveComponent(entity, comp); } else if (ghostAuthoring.SupportedGhostModes == GhostAuthoringComponent.GhostModeMask.Predicted && (attr.PrefabType & GhostPrefabType.PredictedClient) == 0) { DstEntityManager.RemoveComponent(entity, comp); } } } // Even if converting for client and server we can remove components which are only for predicted clients when // the ghost is always interpolated, or components which are only for interpolated clients if the ghost is always // predicted else if (ghostAuthoring.SupportedGhostModes == GhostAuthoringComponent.GhostModeMask.Interpolated) { foreach (var comp in components) { var attr = comp.GetManagedType().GetCustomAttribute <GhostComponentAttribute>(); if (attr != null && (attr.PrefabType & (GhostPrefabType.InterpolatedClient | GhostPrefabType.Server)) == 0) { DstEntityManager.RemoveComponent(entity, comp); } } } else if (ghostAuthoring.SupportedGhostModes == GhostAuthoringComponent.GhostModeMask.Predicted) { foreach (var comp in components) { var attr = comp.GetManagedType().GetCustomAttribute <GhostComponentAttribute>(); if (attr != null && (attr.PrefabType & (GhostPrefabType.PredictedClient | GhostPrefabType.Server)) == 0) { DstEntityManager.RemoveComponent(entity, comp); } } } // This logic needs to match the logic creating LinkedEntityGroups, gather a list of all child entities var linkedEntities = new NativeList <Entity>(1, Allocator.Temp); var selfAndChildren = ghostAuthoring.gameObject.GetComponentsInChildren <Transform>(true); foreach (var transform in selfAndChildren) { foreach (var child in GetEntities(transform.gameObject)) { if (DstEntityManager.Exists(child)) { linkedEntities.Add(child); } } } // Mark all child entities as ghost children, entity 0 is the root and hsould not be marked for (int i = 1; i < linkedEntities.Length; ++i) { DstEntityManager.AddComponentData(linkedEntities[i], default(GhostChildEntityComponent)); } if (isPrefab) { var contentHash = TypeHash.FNV1A64(ghostAuthoring.Importance); contentHash = TypeHash.CombineFNV1A64(contentHash, TypeHash.FNV1A64((int)ghostAuthoring.SupportedGhostModes)); contentHash = TypeHash.CombineFNV1A64(contentHash, TypeHash.FNV1A64((int)ghostAuthoring.DefaultGhostMode)); contentHash = TypeHash.CombineFNV1A64(contentHash, TypeHash.FNV1A64((int)ghostAuthoring.OptimizationMode)); contentHash = TypeHash.CombineFNV1A64(contentHash, TypeHash.FNV1A64(ghostAuthoring.Name.ToString())); foreach (var comp in components) { contentHash = TypeHash.CombineFNV1A64(contentHash, TypeManager.GetTypeInfo(comp.TypeIndex).StableTypeHash); var attr = comp.GetManagedType().GetCustomAttribute <GhostComponentAttribute>(); if (attr != null) { contentHash = TypeHash.CombineFNV1A64(contentHash, TypeHash.FNV1A64((int)attr.PrefabType)); } } var blobHash = new Unity.Entities.Hash128(ghostType.guid0 ^ (uint)(contentHash >> 32), ghostType.guid1 ^ (uint)(contentHash), ghostType.guid2, ghostType.guid3); context.AssociateBlobAssetWithUnityObject(blobHash, ghostAuthoring.gameObject); if (context.NeedToComputeBlobAsset(blobHash)) { var builder = new BlobBuilder(Allocator.Temp); ref var root = ref builder.ConstructRoot <GhostPrefabMetaData>(); // Store importance, supported modes, default mode and name in the meta data blob asset root.Importance = ghostAuthoring.Importance; root.SupportedModes = GhostPrefabMetaData.GhostMode.Both; root.DefaultMode = GhostPrefabMetaData.GhostMode.Interpolated; if (ghostAuthoring.SupportedGhostModes == GhostAuthoringComponent.GhostModeMask.Interpolated) { root.SupportedModes = GhostPrefabMetaData.GhostMode.Interpolated; } else if (ghostAuthoring.SupportedGhostModes == GhostAuthoringComponent.GhostModeMask.Predicted) { root.SupportedModes = GhostPrefabMetaData.GhostMode.Predicted; root.DefaultMode = GhostPrefabMetaData.GhostMode.Predicted; } else if (ghostAuthoring.DefaultGhostMode == GhostAuthoringComponent.GhostMode.OwnerPredicted) { if (!DstEntityManager.HasComponent <GhostOwnerComponent>(entity)) { throw new InvalidOperationException("OwnerPrediction mode can only be used on prefabs which have a GhostOwnerComponent"); } root.DefaultMode = GhostPrefabMetaData.GhostMode.Both; } else if (ghostAuthoring.DefaultGhostMode == GhostAuthoringComponent.GhostMode.Predicted) { root.DefaultMode = GhostPrefabMetaData.GhostMode.Predicted; } root.StaticOptimization = (ghostAuthoring.OptimizationMode == GhostAuthoringComponent.GhostOptimizationMode.Static); builder.AllocateString(ref root.Name, ghostAuthoring.Name); var serverComponents = new NativeList <ulong>(components.Length, Allocator.Temp); var removeOnServer = new NativeList <ulong>(components.Length, Allocator.Temp); var removeOnClient = new NativeList <ulong>(components.Length, Allocator.Temp); var disableOnPredicted = new NativeList <ulong>(components.Length, Allocator.Temp); var disableOnInterpolated = new NativeList <ulong>(components.Length, Allocator.Temp); // Snapshot data buffers should be removed from the server, and shared ghost type from the client removeOnServer.Add(TypeManager.GetTypeInfo(ComponentType.ReadWrite <SnapshotData>().TypeIndex).StableTypeHash); removeOnServer.Add(TypeManager.GetTypeInfo(ComponentType.ReadWrite <SnapshotDataBuffer>().TypeIndex).StableTypeHash); removeOnClient.Add(TypeManager.GetTypeInfo(ComponentType.ReadWrite <SharedGhostTypeComponent>().TypeIndex).StableTypeHash); // If both interpolated and predicted clients are supported the interpolated client needs to disable the prediction component // If the ghost is interpolated only the prediction component can be removed on clients if (ghostAuthoring.SupportedGhostModes == GhostAuthoringComponent.GhostModeMask.All) { disableOnInterpolated.Add(TypeManager.GetTypeInfo(ComponentType.ReadWrite <PredictedGhostComponent>().TypeIndex).StableTypeHash); } else if (ghostAuthoring.SupportedGhostModes == GhostAuthoringComponent.GhostModeMask.Interpolated) { removeOnClient.Add(TypeManager.GetTypeInfo(ComponentType.ReadWrite <PredictedGhostComponent>().TypeIndex).StableTypeHash); } foreach (var comp in components) { var hash = TypeManager.GetTypeInfo(comp.TypeIndex).StableTypeHash; var attr = comp.GetManagedType().GetCustomAttribute <GhostComponentAttribute>(); if (attr == null) { serverComponents.Add(hash); continue; } if ((attr.PrefabType & GhostPrefabType.Server) == 0) { removeOnServer.Add(hash); } else { serverComponents.Add(hash); } // If something is not used on the client, remove it. Make sure to include things that is interpolated only if ghost // is predicted only and the other way around if ((attr.PrefabType & GhostPrefabType.Client) == 0) { removeOnClient.Add(hash); } else if (ghostAuthoring.SupportedGhostModes == GhostAuthoringComponent.GhostModeMask.Interpolated && (attr.PrefabType & GhostPrefabType.InterpolatedClient) == 0) { removeOnClient.Add(hash); } else if (ghostAuthoring.SupportedGhostModes == GhostAuthoringComponent.GhostModeMask.Predicted && (attr.PrefabType & GhostPrefabType.PredictedClient) == 0) { removeOnClient.Add(hash); } // If the prefab only supports a single mode on the client there is no need to enable / disable, if is handled by the // previous loop removing components on the client instead if (ghostAuthoring.SupportedGhostModes == GhostAuthoringComponent.GhostModeMask.All) { // Components available on predicted but not interpolated should be disabled on interpolated clients if ((attr.PrefabType & GhostPrefabType.InterpolatedClient) == 0 && (attr.PrefabType & GhostPrefabType.PredictedClient) != 0) { disableOnInterpolated.Add(hash); } if ((attr.PrefabType & GhostPrefabType.InterpolatedClient) != 0 && (attr.PrefabType & GhostPrefabType.PredictedClient) == 0) { disableOnPredicted.Add(hash); } } } var blobServerComponents = builder.Allocate(ref root.ServerComponentList, serverComponents.Length); for (int i = 0; i < serverComponents.Length; ++i) { blobServerComponents[i] = serverComponents[i]; } // A pre-spawned instance can be created in ClientServer even if the prefab is not, so anything which should // be usable on the server needs to know what to remove from the server version if (target != NetcodeConversionTarget.Client) { // Client only data never needs information about the server var blobRemoveOnServer = builder.Allocate(ref root.RemoveOnServer, removeOnServer.Length); for (int i = 0; i < removeOnServer.Length; ++i) { blobRemoveOnServer[i] = removeOnServer[i]; } } else { builder.Allocate(ref root.RemoveOnServer, 0); } if (target != NetcodeConversionTarget.Server) { var blobRemoveOnClient = builder.Allocate(ref root.RemoveOnClient, removeOnClient.Length); for (int i = 0; i < removeOnClient.Length; ++i) { blobRemoveOnClient[i] = removeOnClient[i]; } } else { builder.Allocate(ref root.RemoveOnClient, 0); } if (target != NetcodeConversionTarget.Server) { // The data for interpolated / predicted diff is required unless this is server-only var blobDisableOnPredicted = builder.Allocate(ref root.DisableOnPredictedClient, disableOnPredicted.Length); for (int i = 0; i < disableOnPredicted.Length; ++i) { blobDisableOnPredicted[i] = disableOnPredicted[i]; } var blobDisableOnInterpolated = builder.Allocate(ref root.DisableOnInterpolatedClient, disableOnInterpolated.Length); for (int i = 0; i < disableOnInterpolated.Length; ++i) { blobDisableOnInterpolated[i] = disableOnInterpolated[i]; } } else { builder.Allocate(ref root.DisableOnPredictedClient, 0); builder.Allocate(ref root.DisableOnInterpolatedClient, 0); } var blobAsset = builder.CreateBlobAssetReference <GhostPrefabMetaData>(Allocator.Persistent); context.AddComputedBlobAsset(blobHash, blobAsset); } context.GetBlobAsset(blobHash, out var blob); DstEntityManager.AddComponentData(entity, new GhostPrefabMetaDataComponent { Value = blob }); } });