using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using BITKit.SceneManagement; using Cysharp.Threading.Tasks; using Quadtree; using UnityEngine; using UnityEngine.Pool; namespace BITKit.OpenWorld { public class WorldChunkService : MonoBehaviour where T : WorldChunkService { public static T Singleton { get; private set; } private readonly ConcurrentQueue _registerQueue = new(); private readonly ConcurrentQueue _unregisterQueue = new(); public void Register(IWorldChunkObject obj) => _registerQueue.Enqueue(obj); public void Unregister(IWorldChunkObject obj) => _unregisterQueue.Enqueue(obj); protected QuadtreeRoot> _quadtree; private readonly ConcurrentDictionary dictionary=new(); [SerializeReference, SubclassSelector] private ITicker ticker; [SerializeField, ReadOnly] private int count; [SerializeField, ReadOnly] private int tickTaskCount; [SerializeField] private bool drawBounds; [SerializeField, Range(0, 1024)] private int[] lodDistances; [SerializeField] private Vector3 size; private Camera _camera; private readonly HashSet cacheList = new(); private readonly Queue<(int, IWorldChunkObject)> lodQueue=new(); private bool isBusy; protected virtual void Awake() { Singleton = (T) this; } protected virtual void Start() { ticker.Add(OnTick); destroyCancellationToken.Register(Dispose); _quadtree = new QuadtreeRoot>(transform.position, size); _camera = Camera.main; } protected virtual void Dispose() { ticker.Remove(OnTick); _registerQueue.Clear(); _unregisterQueue.Clear(); } protected virtual int CalculateLod(IWorldChunkObject value,Vector3 cameraPosition,int lod)=>lod; protected virtual async void OnTick(float deltaTime) { if (!enabled) return; if (isBusy) return; isBusy = true; while (_unregisterQueue.TryDequeue(out var obj)) { _quadtree.Remove(obj); dictionary.Remove(obj.Id, out _); } while (_registerQueue.TryDequeue(out var obj)) { obj.Id = count++; _quadtree.Insert(obj); dictionary.TryAdd(obj.Id, obj); } var cameraPosition = _camera.transform.position; var cycle = 0; await UniTask.SwitchToThreadPool(); var items = _quadtree.Find(new Bounds(cameraPosition, Vector3.one * (lodDistances[^1] + lodDistances[0]))); foreach (var chunkObject in _quadtree.Find(new Bounds(cameraPosition, Vector3.one * (lodDistances[^1]+lodDistances[0])))) { if (cycle++ > 64) { cycle = 0; await UniTask.NextFrame(); if (destroyCancellationToken.IsCancellationRequested) return; } var distance = Vector3.Distance(cameraPosition, chunkObject.GetBounds().center); //var distance = Vector3.Distance(cameraPosition, chunkObject.GetBounds().ClosestPoint(cameraPosition)); if (!cacheList.Add(chunkObject.Id)) continue; var lod = -1; if (chunkObject.GetBounds().Contains(cameraPosition)) { lod = 0; } else { for (var i = 0; i < lodDistances.Length; i++) { if (!(distance < lodDistances[i])) continue; lod = i; break; } } lod = CalculateLod(chunkObject,cameraPosition,lod); if(chunkObject.Lod==lod)continue; //chunkObject.Lod = lod; lodQueue.Enqueue((lod,chunkObject)); } // for (var index = 0; index < lodDistances.Length; index++) // { // var distance = lodDistances[index]; // foreach (var chunkObject in _quadtree.Find(new Bounds(cameraPosition, Vector3.one * distance))) // { // if (cacheList.Contains(chunkObject.Id)) continue; // cacheList.Add(chunkObject.Id); // // var lod = chunkObject.Lod; // if (lod == index) continue; // try // { // chunkObject.Lod = index; // } // catch (Exception e) // { // BIT4Log.LogException(e); // } // } // } await UniTask.SwitchToMainThread(); tickTaskCount = lodQueue.Count; if (destroyCancellationToken.IsCancellationRequested) return; cycle = 0; while (lodQueue.TryDequeue(out var value)) { if (cycle++ > 8) { cycle = 0; await UniTask.NextFrame(); if (destroyCancellationToken.IsCancellationRequested) return; } value.Item2.Lod = value.Item1; } isBusy = false; cacheList.Clear(); } private void OnDrawGizmosSelected() { var pos = transform.position; Gizmos.DrawWireCube(pos,transform.rotation * size); if(drawBounds) _quadtree.CurrentRootNode.DrawBounds(true); if(!_camera)return; var cameraPosition = _camera.transform.position; foreach (var VARIABLE in lodDistances) { Gizmos.DrawWireSphere(cameraPosition, VARIABLE); } } } }