using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using BITKit; using BITKit.Entities; using BITKit.IO; using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks.Triggers; using Microsoft.Extensions.DependencyInjection; using Net.BITKit.Quadtree; using Project.B.Map; using Unity.Mathematics; using UnityEngine; using UnityEngine.SceneManagement; using YooAsset; namespace Net.Project.B.World { public class WorldChunkService:IDisposable { private readonly IGameMapService _gameMapService; private readonly IEntitiesService _entitiesService; private readonly IAsyncTicker _asyncTicker; private readonly Quadtree _quadtree= new(default,new float2(2048,2048)); private readonly ConcurrentDictionary _nodes = new(); private readonly HashSet _inRendingThisFrame = new(); private readonly HashSet _inRendering = new(); private readonly ConcurrentDictionary _sceneHandles = new(); private readonly ConcurrentDictionary _waitLoadChunks = new(); public WorldChunkService(IAsyncTicker asyncTicker, IEntitiesService entitiesService, IGameMapService gameMapService) { _asyncTicker = asyncTicker; _entitiesService = entitiesService; _gameMapService = gameMapService; _asyncTicker.OnTickAsync += OnTickAsync; _entitiesService.OnAdd += OnAdd; _entitiesService.OnRemove += OnRemove; BITAppForUnity.OnDrawGizmo += OnDrawGizmo; _gameMapService.OnMapChanging += OnMapChanging; } private async UniTask OnMapChanging(string arg) { if (_entitiesService.QueryComponents().Length > 0) { for (var i = 0; i < 16; i++) { await UniTask.NextFrame(); } } await UniTask.WhenAll(_waitLoadChunks.Select(x => x.Value.Task).ToArray()); } private void OnDrawGizmo() { foreach (var (id,size) in _quadtree.Sizes) { Gizmos.color = Color.red; var pos = _quadtree.Positions[id]; Gizmos.DrawWireCube(new(pos.x,0,pos.y),new Vector3(size.x,0,size.y)); } } private void OnRemove(IEntity obj) { if (_nodes.TryRemove(obj.Id, out _)) _quadtree.Remove(obj.Id); } private void OnAdd(IEntity obj) { if(obj.ServiceProvider.GetService() is not {} buildingNode)return; var transform = obj.ServiceProvider.GetRequiredService(); if (buildingNode.lodGameObject != transform.gameObject) { buildingNode.GameObject = buildingNode.lodGameObject ? buildingNode.lodGameObject:transform.gameObject; } _nodes.TryAdd(obj.Id,buildingNode); var bounds = new Bounds(); var children = (buildingNode.lodGameObject ? buildingNode.lodGameObject.transform : transform).GetComponentsInChildren(); for (var index = 0; index < children.Length; index++) { var renderer = children[index]; if (index is 0) { bounds = renderer.bounds; } else { bounds.Encapsulate(renderer.bounds); } } buildingNode.Bounds = bounds; _inRendering.Add(obj.Id); _quadtree.Insert(obj.Id,((float3) buildingNode.Bounds.center).xz,((float3)buildingNode.Bounds.size).xz); if (buildingNode.lodGameObject && string.IsNullOrEmpty(buildingNode.sceneName)) { buildingNode.GameObject.SetActive(false); } } public void Dispose() { BITAppForUnity.OnDrawGizmo -= OnDrawGizmo; _entitiesService.OnRemove -= OnRemove; _entitiesService.OnAdd -= OnAdd; _asyncTicker.OnTickAsync -= OnTickAsync; } private UniTask OnTickAsync(float arg) { if (_gameMapService.TaskStatus is not TaskStatus.RanToCompletion) return UniTask.CompletedTask; var camera = Camera.main; if(!camera)return UniTask.CompletedTask; var pos = (float3)camera.transform.position; foreach (var id in _quadtree.Query(pos.xz,32)) { if(_nodes.TryGetValue(id,out var node) is false)continue; pos.y = node.Bounds.center.y; if(Vector3.Distance(pos, node.Bounds.ClosestPoint(pos)) > 32 )continue; _inRendingThisFrame.Add(id); if (!node.lodGameObject) { node.GameObject.SetActive(true); } else { if(_sceneHandles.ContainsKey(id) is false) foreach (var (packageName, hashSet) in YooAssetModHelper.PackagesManifestDictionary) { if (hashSet.Contains(node.sceneName)) { var handle = YooAssets.GetPackage(packageName) .LoadSceneAsync(node.sceneName, LoadSceneMode.Additive); _waitLoadChunks[node.sceneName] = new(); if (node.lodGameObject) { handle.Completed += x => { node.GameObject.SetActive(false); if(_waitLoadChunks.TryRemove(node.sceneName,out var cs)) { cs.TrySetResult(); } }; } _sceneHandles.TryAdd(id, handle); } } } } _inRendering.ExceptWith(_inRendingThisFrame); foreach (var id in _inRendering) { if(_nodes.TryGetValue(id,out var node) is false)continue; if (!node.lodGameObject) { node.GameObject.SetActive(false); } else { if (_sceneHandles.TryRemove(id, out var sceneHandle)) { var handle = sceneHandle.UnloadAsync(); if (node.lodGameObject) { handle.Completed += _ => node.GameObject.SetActive(true); } } } } _inRendering.Clear(); _inRendering.UnionWith(_inRendingThisFrame); _inRendingThisFrame.Clear(); return UniTask.CompletedTask; } } }