BITFALL/Assets/Artists/Scripts/Item/WorldItemService.cs

326 lines
9.4 KiB
C#

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BITKit;
using BITKit.Entities;
using BITKit.Game;
using Cysharp.Threading.Tasks;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Playables;
using StringBuilder = System.Text.StringBuilder;
namespace BITFALL.Items
{
[Serializable]
public struct WorldItemBatchSyncCommand
{
public int Length;
public int[] Ids;
public bool[] IsKinematic;
public ulong[] AddressableIds;
public float3[] Position;
public quaternion[] Rotation;
}
[Serializable]
public struct WorldItemSpawnCommand
{
public int Id;
public ulong AddressableId;
public float3 Position;
public quaternion Rotation;
public class Writer:NetMessageReader<WorldItemSpawnCommand>
{
public override WorldItemSpawnCommand ReadBinary(BinaryReader reader)
{
return new WorldItemSpawnCommand()
{
Id = reader.ReadInt32(),
AddressableId = reader.ReadUInt64(),
Position = reader.ReadFloat3(),
Rotation = reader.ReadQuaternion(),
};
}
public override void WriteBinary(BinaryWriter writer, WorldItemSpawnCommand value)
{
writer.Write(value.Id);
writer.Write(value.AddressableId);
writer.WriteFloat3(value.Position);
writer.WriteQuaternion(value.Rotation);
}
}
}
[Serializable]
public struct WorldItemDespawnCommand
{
public int Id;
public class Writer:NetMessageReader<WorldItemDespawnCommand>
{
public override WorldItemDespawnCommand ReadBinary(BinaryReader reader)
{
return new WorldItemDespawnCommand()
{
Id = reader.ReadInt32(),
};
}
public override void WriteBinary(BinaryWriter writer, WorldItemDespawnCommand value)
{
writer.Write(value.Id);
}
}
}
public class WorldItemBatchSyncCommandWriter : NetMessageReader<WorldItemBatchSyncCommand>
{
public override WorldItemBatchSyncCommand ReadBinary(BinaryReader reader)
{
var length = reader.ReadInt32();
var ids = new int[length];
var isKinematic = new bool[length];
var addressableIds = new ulong[length];
var position = new float3[length];
var rotation = new quaternion[length];
for (var i = 0; i < length; i++)
{
ids[i] = reader.ReadInt32();
isKinematic[i] = reader.ReadBoolean();
addressableIds[i] = reader.ReadUInt64();
position[i] = reader.ReadFloat3();
rotation[i] = reader.ReadQuaternion();
}
return new WorldItemBatchSyncCommand
{
Length = length,
Ids = ids,
IsKinematic = isKinematic,
AddressableIds = addressableIds,
Position = position,
Rotation = rotation
};
}
public override void WriteBinary(BinaryWriter writer, WorldItemBatchSyncCommand value)
{
writer.Write(value.Length);
for (var i = 0; i < value.Length; i++)
{
writer.Write(value.Ids[i]);
writer.Write(value.IsKinematic[i]);
writer.Write(value.AddressableIds[i]);
writer.WriteFloat3(value.Position[i]);
writer.WriteQuaternion(value.Rotation[i]);
}
}
}
public class WorldItemService : MonoBehaviour
{
private static readonly ConcurrentDictionary<int,WorldItem> _worldItems = new();
public static void Register(WorldItem item)
{
_instances.TryAdd(item);
_registerQueue.Enqueue(item);
}
public static void Unregister(WorldItem item)
{
_instances.TryRemove(item);
_unregisterQueue.Enqueue(item);
}
private static readonly ConcurrentQueue<WorldItem> _registerQueue = new();
private static readonly ConcurrentQueue<WorldItem> _unregisterQueue = new();
private static readonly List<WorldItem> _instances = new();
[SerializeReference, SubclassSelector] private ITicker ticker;
[SerializeReference, SubclassSelector] private INetClient netClient;
[SerializeReference, SubclassSelector] private INetServer netServer;
[SerializeReference, SubclassSelector] private IGameService gameService;
private INetProvider clientNetProvider => netClient.Source as INetProvider;
private INetProvider serverNetProvider => netServer.Source as INetProvider;
private readonly DoubleBuffer<WorldItemBatchSyncCommand> _batchCommands = new();
private readonly ConcurrentQueue<WorldItemSpawnCommand> _spawnCommands = new();
private readonly ConcurrentQueue<WorldItemDespawnCommand> _despawnCommands = new();
private bool markDirty;
private void Start()
{
ticker.Add(Tick);
destroyCancellationToken.Register(() =>
{
ticker.Remove(Tick);
});
netServer.OnClientConnected += OnClientConnected;
clientNetProvider.AddCommandListener<WorldItemBatchSyncCommand>(_batchCommands.Release);
clientNetProvider.AddCommandListener<WorldItemSpawnCommand>(_spawnCommands.Enqueue);
clientNetProvider.AddCommandListener<WorldItemDespawnCommand>(_despawnCommands.Enqueue);
serverNetProvider.AddCommandListener<WorldItemSpawnCommand>(_spawnCommands.Enqueue);
serverNetProvider.AddCommandListener<WorldItemDespawnCommand>(_despawnCommands.Enqueue);
}
private void SpawnItem(WorldItemSpawnCommand command)
{
if(_worldItems.ContainsKey(command.Id)) return;
var instance = Instantiate(AddressableHelper.Get<ScriptableObject>(command.AddressableId).As<AssetableItem>().GetPrefab(),command.Position,command.Rotation).GetComponent<WorldItem>();
instance.WaitForInitializationComplete();
_worldItems.TryAdd(command.Id, instance);
}
private void DespawnItem(WorldItemDespawnCommand command)
{
if(_worldItems.TryRemove(command.Id,out var item) && item)
Destroy(item.gameObject);
}
private async void OnBatchSyncCommand(WorldItemBatchSyncCommand obj)
{
var batchReport = new StringBuilder();
batchReport.AppendLine($"同步了{obj.Length}个物品,预计移除{_worldItems.Count}个物品");
foreach (var x in _instances)
{
batchReport.AppendLine($"移除了:{x.Name}");
Destroy(x.gameObject);
}
_instances.Clear();
_worldItems.Clear();
_registerQueue.Clear();
_unregisterQueue.Clear();
try
{
await UniTask.NextFrame(destroyCancellationToken);
}
catch (OperationCanceledException)
{
return;
}
var gameObjects = obj.AddressableIds.Select(x=>AddressableHelper.Get<ScriptableObject>(x).As<AssetableItem>().GetPrefab()).ToArray();
for (var i = 0; i < obj.Length ;i++)
{
var id = obj.Ids[i];
var instance = Instantiate(gameObjects[i],obj.Position[i],obj.Rotation[i]).GetComponent<WorldItem>();
instance.Id = id;
instance.Rigidbody.isKinematic = obj.IsKinematic[i];
instance.WaitForInitializationComplete();
_worldItems.TryAdd(id,instance);
}
_registerQueue.Clear();
_unregisterQueue.Clear();
}
private async void OnClientConnected(int obj)
{
try
{
await Task.Delay(1000, destroyCancellationToken);
await UniTask.SwitchToMainThread(destroyCancellationToken);
}
catch (OperationCanceledException)
{
}
var command = CreateBatchSyncCommand();
serverNetProvider.ClientCommand(obj, command);
BIT4Log.Log<WorldItemService>($"向{obj}发送了物品同步命令,预计同步{command.Length}个物品");
}
private void Tick(float obj)
{
if(gameService.CurrentState is not IGamePlayingState || !this) return;
while (_registerQueue.TryDequeue(out var item))
{
if (!item || _worldItems.TryAdd(item.Id,item) is false) continue;
var spawnCommand = new WorldItemSpawnCommand()
{
Id = item.Id,
AddressableId = item.Assetable.AddressableId,
Position = item.transform.position,
Rotation = item.transform.rotation,
};
if (netServer.IsRunningServer)
{
serverNetProvider.AllClientCommand(spawnCommand);
}
}
while (_unregisterQueue.TryDequeue(out var item))
{
if (!_worldItems.TryRemove(item.Id, out _)) continue;
var despawnCommand = new WorldItemDespawnCommand()
{
Id = item.Id
};
if (netServer.IsRunningServer)
{
serverNetProvider.AllClientCommand(despawnCommand);
}
}
while (_spawnCommands.TryDequeue(out var _spawnCmd))
{
SpawnItem(_spawnCmd);
}
while (_despawnCommands.TryDequeue(out var _despawnCmd))
{
DespawnItem(_despawnCmd);
}
if (markDirty &&netServer.IsRunningServer)
{
var batchCommand = CreateBatchSyncCommand();
serverNetProvider.AllClientCommand(batchCommand);
markDirty = false;
//BIT4Log.Log<WorldItemService>($"物品同步命令已发送,预计同步{batchCommand.Length}个物品");
}
if (_batchCommands.TryGetRelease(out var command))
{
//BIT4Log.Log<WorldItemService>($"预计同步{command.Length}个物品,移除{_worldItems.Count}个物品");
OnBatchSyncCommand(command);
}
}
private WorldItemBatchSyncCommand CreateBatchSyncCommand()
{
var length = _worldItems.Count;
var command = new WorldItemBatchSyncCommand
{
Length = length,
Ids = new int[length],
IsKinematic = new bool[length],
AddressableIds = new ulong[length],
Position = new float3[length],
Rotation = new quaternion[length]
};
var i = 0;
foreach (var item in _worldItems.Values)
{
command.Ids[i] = item.Id;
command.IsKinematic[i] = item.Rigidbody.isKinematic;
command.AddressableIds[i] = item.Assetable.AddressableId;
var itemTransform = item.transform;
command.Position[i] = itemTransform.position;
command.Rotation[i] = itemTransform.rotation;
i++;
}
return command;
}
private void MarkDirty()
{
markDirty = true;
}
}
}