326 lines
9.4 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
|