using System;
using Unity.Collections;
using Unity.Entities;
using Unity.NetCode;
using Unity.NetCode.LowLevel.Unsafe;
namespace Unity.NetCode.Samples
{
///
/// Singleton that contains all the registered client-only component types and prefab metadata.
///
/// The client-only backup systems only backup the state of the component that are registered to the
/// . The components must be registered before 'going' in game.
///
///
public struct ClientOnlyCollection : IComponentData
{
internal NativeList ClientOnlyComponentTypes;
internal NativeList BackupInfoCollection;
internal NativeHashMap GhostTypeToPrefabMetadata;
internal int ProcessedPrefabs;
///
/// Flag used to check if a component can be registered. Once the first ghost prefabs has been processed,
/// it is not possible to add other component to the collection.
///
internal bool CanRegisterComponents => ProcessedPrefabs == 0;
///
/// Call this method to register the component as client-only and make it part of the backup.
/// The registration must be done before the game start (connection goes in game).
/// A good practice is to create a system that is create after the
/// (so tha can access the singleton) and register the component once.
///
///
public void RegisterClientOnlyComponentType(in ComponentType componentType)
{
if (!CanRegisterComponents)
throw new InvalidOperationException("cannot register client-only component after prefabs has been processed or the connection is in game");
if (ClientOnlyComponentTypes.IndexOf(componentType) >= 0)
return;
ClientOnlyComponentTypes.Add(componentType);
}
internal void ProcessGhostTypePrefab(GhostTypeComponent ghostType, Entity entity, EntityManager entityManager)
{
int first = BackupInfoCollection.Length;
int componentBackupSize = 0;
int numRootComponents = 0;
AddComponentForEntity(entityManager, entity, 0, ref componentBackupSize);
numRootComponents = BackupInfoCollection.Length - first;
if (entityManager.HasBuffer(entity))
{
var leg = entityManager.GetBuffer(entity);
for (int i = 1; i < leg.Length; i++)
AddComponentForEntity(entityManager, leg[i].Value, i, ref componentBackupSize);
}
if (BackupInfoCollection.Length != first)
{
//add tick and enable bitmask array to the backup size. Buffer size are re-calculated dynamically based on the buffer
//contents by the job
var enableBitsSize = ClientOnlyBackup.EnableBitByteSize(BackupInfoCollection.Length - first);
var compDataStartOffset = GhostComponentSerializer.SnapshotSizeAligned(sizeof(int) + enableBitsSize);
componentBackupSize = GhostComponentSerializer.SnapshotSizeAligned(componentBackupSize + compDataStartOffset);
GhostTypeToPrefabMetadata.Add(ghostType, new ClientOnlyBackupMetadata
{
componentBegin = first,
componentEnd = BackupInfoCollection.Length,
numRootComponents = numRootComponents,
backupSize = componentBackupSize,
});
}
}
private void AddComponentForEntity(EntityManager entityManager, Entity entity, int entityIndex, ref int backupSize)
{
using var componentTypes = entityManager.GetComponentTypes(entity);
foreach (var componentType in componentTypes)
{
int index = ClientOnlyComponentTypes.IndexOf(componentType);
if(index < 0)
continue;
//This introduce a little bit redundancy but at least do not requires two memory fetches to get this data
var info = new ClientOnlyBackupInfo
{
ComponentType = componentType,
ComponentSize = componentType.IsBuffer
? TypeManager.GetTypeInfo(componentType.TypeIndex).ElementSize
: TypeManager.GetTypeInfo(componentType.TypeIndex).TypeSize,
ComponentIndex = index,
EntityIndex = entityIndex
};
BackupInfoCollection.Add(info);
Unity.Assertions.Assert.IsFalse(componentType.IsBuffer);
backupSize += TypeManager.GetTypeInfo(componentType.TypeIndex).TypeSize;
}
}
}
}