218 lines
7.4 KiB
C#
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|