Files
Net.Like.Xue.Tokyo/Packages-Local/Com.Project.B.Unity/World/WorldChunkService.cs
2025-06-24 23:49:13 +08:00

218 lines
7.4 KiB
C#

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<int,UnityWorldChunkNode> _nodes = new();
private readonly HashSet<int> _inRendingThisFrame = new();
private readonly HashSet<int> _inRendering = new();
private readonly ConcurrentDictionary<int, SceneHandle> _sceneHandles = new();
private readonly ConcurrentDictionary<string, UniTaskCompletionSource> _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<UnityWorldChunkNode>().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<UnityWorldChunkNode>() is not {} buildingNode)return;
var transform = obj.ServiceProvider.GetRequiredService<Transform>();
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<Renderer>();
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;
}
}
}