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 Cysharp.Threading.Tasks; using Unity.Mathematics; using UnityEngine; using UnityEngine.Playables; using UnityEngine.Pool; 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 { 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 { 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 { 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 _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 _registerQueue = new(); private static readonly ConcurrentQueue _unregisterQueue = new(); private static readonly List _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 _batchCommands = new(); private readonly ConcurrentQueue _spawnCommands = new(); private readonly ConcurrentQueue _despawnCommands = new(); private bool markDirty; private void Start() { ticker.Add(Tick); destroyCancellationToken.Register(() => { ticker.Remove(Tick); }); netServer.OnClientConnected += OnClientConnected; clientNetProvider.AddCommandListener(_batchCommands.Release); clientNetProvider.AddCommandListener(_spawnCommands.Enqueue); clientNetProvider.AddCommandListener(_despawnCommands.Enqueue); serverNetProvider.AddCommandListener(_spawnCommands.Enqueue); serverNetProvider.AddCommandListener(_despawnCommands.Enqueue); } private void SpawnItem(WorldItemSpawnCommand command) { if(_worldItems.ContainsKey(command.Id)) return; var instance = Instantiate(AddressableHelper.Get(command.AddressableId).As().GetPrefab(),command.Position,command.Rotation).GetComponent(); 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(x).As().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(); 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($"向{obj}发送了物品同步命令,预计同步{command.Length}个物品"); } private void Tick(float obj) { //if(gameService.CurrentState is not IGamePlayingState || !this) return; var addList = ListPool.Get(); while (_registerQueue.TryDequeue(out var item)) { addList.Add(item); } while (_unregisterQueue.TryDequeue(out var item)) { addList.Remove(item); if (!_worldItems.TryRemove(item.Id, out _)) continue; var despawnCommand = new WorldItemDespawnCommand() { Id = item.Id }; if (netServer.IsRunningServer) { serverNetProvider.AllClientCommand(despawnCommand); } } foreach (var item in addList) { if (!item || _worldItems.TryAdd(item.Id, item) is false) continue; var spawnCommand = new WorldItemSpawnCommand() { Id = item.Id, AddressableId = item.Scriptable.AddressableId, Position = item.transform.position, Rotation = item.transform.rotation, }; if (netServer.IsRunningServer) { serverNetProvider.AllClientCommand(spawnCommand); } } ListPool.Release(addList); // 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($"物品同步命令已发送,预计同步{batchCommand.Length}个物品"); } if (_batchCommands.TryGetRelease(out var command)) { //BIT4Log.Log($"预计同步{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.Scriptable.AddressableId; var itemTransform = item.transform; command.Position[i] = itemTransform.position; command.Rotation[i] = itemTransform.rotation; i++; } return command; } private void MarkDirty() { markDirty = true; } } }