Add resource pool manager
This commit is contained in:
@@ -133,7 +133,7 @@ namespace XGame.ActorBehavior
|
||||
target.Version = NextActorVersion(actorId);
|
||||
for (int i = target.SpawnedEffects.Count - 1; i >= 0; i--)
|
||||
{
|
||||
DestroyUnityObject(target.SpawnedEffects[i]);
|
||||
ReleaseEffectInstance(target.SpawnedEffects[i]);
|
||||
}
|
||||
target.SpawnedEffects.Clear();
|
||||
}
|
||||
@@ -154,24 +154,18 @@ namespace XGame.ActorBehavior
|
||||
ActorPresentationTarget startTarget = null;
|
||||
bool actorBoundEffect = !hasExplicitPosition && actors.TryGetValue(actorId, out startTarget);
|
||||
int startVersion = actorBoundEffect && startTarget != null ? startTarget.Version : 0;
|
||||
UObject loaded = null;
|
||||
yield return XResLoader.coLoadRes(prefabPath, typeof(GameObject), obj => loaded = obj);
|
||||
|
||||
GameObject prefab = loaded as GameObject;
|
||||
if (prefab == null)
|
||||
{
|
||||
Debug.LogWarning("[ActorBehaviorPresentation] effect prefab load failed: " + prefabPath);
|
||||
yield break;
|
||||
}
|
||||
GameObject instance = null;
|
||||
yield return LoadEffectInstance(actorId, prefabPath, position, hasExplicitPosition, startTarget, obj => instance = obj);
|
||||
|
||||
if (actorBoundEffect && (!actors.TryGetValue(actorId, out ActorPresentationTarget currentTarget) || currentTarget.Version != startVersion))
|
||||
{
|
||||
ReleaseEffectInstance(instance);
|
||||
yield break;
|
||||
}
|
||||
|
||||
GameObject instance = InstantiateEffect(actorId, prefab, position, hasExplicitPosition);
|
||||
if (instance == null)
|
||||
{
|
||||
Debug.LogWarning("[ActorBehaviorPresentation] effect prefab load failed: " + prefabPath);
|
||||
yield break;
|
||||
}
|
||||
|
||||
@@ -185,7 +179,7 @@ namespace XGame.ActorBehavior
|
||||
}
|
||||
else if (defaultEffectLifetime > 0f)
|
||||
{
|
||||
Destroy(instance, defaultEffectLifetime);
|
||||
StartCoroutine(ReleaseEffectAfterLifetime(instance, defaultEffectLifetime));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,27 +190,35 @@ namespace XGame.ActorBehavior
|
||||
{
|
||||
target.SpawnedEffects.Remove(instance);
|
||||
}
|
||||
DestroyUnityObject(instance);
|
||||
ReleaseEffectInstance(instance);
|
||||
}
|
||||
|
||||
private GameObject InstantiateEffect(int actorId, GameObject prefab, BehaviorVector3 position, bool hasExplicitPosition)
|
||||
private IEnumerator ReleaseEffectAfterLifetime(GameObject instance, float lifetime)
|
||||
{
|
||||
actors.TryGetValue(actorId, out ActorPresentationTarget target);
|
||||
yield return new WaitForSeconds(lifetime);
|
||||
ReleaseEffectInstance(instance);
|
||||
}
|
||||
|
||||
private IEnumerator LoadEffectInstance(int actorId, string prefabPath, BehaviorVector3 position, bool hasExplicitPosition, ActorPresentationTarget startTarget, System.Action<GameObject> action)
|
||||
{
|
||||
ActorPresentationTarget target = startTarget;
|
||||
if (target == null)
|
||||
{
|
||||
actors.TryGetValue(actorId, out target);
|
||||
}
|
||||
|
||||
if (target != null && !hasExplicitPosition && attachActorEffects)
|
||||
{
|
||||
Transform parent = target.EffectRoot != null ? target.EffectRoot : target.ActorTransform;
|
||||
GameObject instance = Instantiate(prefab, parent);
|
||||
instance.transform.localPosition = Vector3.zero;
|
||||
instance.transform.localRotation = Quaternion.identity;
|
||||
return instance;
|
||||
yield return ResourcePoolManager.coLoadGameObject(prefabPath, parent, Vector3.zero, Quaternion.identity, action);
|
||||
yield break;
|
||||
}
|
||||
|
||||
Vector3 worldPosition = hasExplicitPosition ? ToUnity(position) : GetActorPosition(target);
|
||||
Quaternion worldRotation = target != null && target.ActorTransform != null
|
||||
? target.ActorTransform.rotation
|
||||
: Quaternion.identity;
|
||||
return Instantiate(prefab, worldPosition, worldRotation);
|
||||
yield return ResourcePoolManager.coLoadGameObjectWorld(prefabPath, worldPosition, worldRotation, action);
|
||||
}
|
||||
|
||||
private int NextActorVersion(int actorId)
|
||||
@@ -264,6 +266,19 @@ namespace XGame.ActorBehavior
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReleaseEffectInstance(GameObject instance)
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ResourcePoolManager.Release(instance))
|
||||
{
|
||||
DestroyUnityObject(instance);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ActorPresentationTarget
|
||||
{
|
||||
public readonly Animator Animator;
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UObject = UnityEngine.Object;
|
||||
|
||||
namespace XGame
|
||||
{
|
||||
public static class ResourcePoolManager
|
||||
{
|
||||
private const string MainName = "Main";
|
||||
private const string PoolRootName = "Cache";
|
||||
|
||||
private static readonly Dictionary<string, Stack<GameObject>> Pools = new Dictionary<string, Stack<GameObject>>();
|
||||
private static readonly HashSet<GameObject> PooledObjects = new HashSet<GameObject>();
|
||||
|
||||
public static IEnumerator coLoadGameObject(string path, Transform parent, Vector3 localPosition, Quaternion localRotation, Action<GameObject> action)
|
||||
{
|
||||
GameObject instance = TryGet(path, parent, localPosition, localRotation);
|
||||
if (instance != null)
|
||||
{
|
||||
action?.Invoke(instance);
|
||||
yield break;
|
||||
}
|
||||
|
||||
UObject loaded = null;
|
||||
yield return XResLoader.coLoadRes(path, typeof(GameObject), obj => loaded = obj);
|
||||
|
||||
GameObject prefab = loaded as GameObject;
|
||||
if (prefab == null)
|
||||
{
|
||||
action?.Invoke(null);
|
||||
yield break;
|
||||
}
|
||||
|
||||
instance = UObject.Instantiate(prefab, parent);
|
||||
instance.transform.localPosition = localPosition;
|
||||
instance.transform.localRotation = localRotation;
|
||||
Mark(instance, path);
|
||||
RestartEffects(instance);
|
||||
action?.Invoke(instance);
|
||||
}
|
||||
|
||||
public static IEnumerator coLoadGameObjectWorld(string path, Vector3 position, Quaternion rotation, Action<GameObject> action)
|
||||
{
|
||||
GameObject instance = TryGetWorld(path, position, rotation);
|
||||
if (instance != null)
|
||||
{
|
||||
action?.Invoke(instance);
|
||||
yield break;
|
||||
}
|
||||
|
||||
UObject loaded = null;
|
||||
yield return XResLoader.coLoadRes(path, typeof(GameObject), obj => loaded = obj);
|
||||
|
||||
GameObject prefab = loaded as GameObject;
|
||||
if (prefab == null)
|
||||
{
|
||||
action?.Invoke(null);
|
||||
yield break;
|
||||
}
|
||||
|
||||
instance = UObject.Instantiate(prefab, position, rotation);
|
||||
Mark(instance, path);
|
||||
RestartEffects(instance);
|
||||
action?.Invoke(instance);
|
||||
}
|
||||
|
||||
public static GameObject TryGet(string path, Transform parent, Vector3 localPosition, Quaternion localRotation)
|
||||
{
|
||||
GameObject instance = Pop(path);
|
||||
if (instance == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Transform transform = instance.transform;
|
||||
transform.SetParent(parent, false);
|
||||
transform.localPosition = localPosition;
|
||||
transform.localRotation = localRotation;
|
||||
instance.SetActive(true);
|
||||
RestartEffects(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static GameObject TryGetWorld(string path, Vector3 position, Quaternion rotation)
|
||||
{
|
||||
GameObject instance = Pop(path);
|
||||
if (instance == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Transform transform = instance.transform;
|
||||
transform.SetParent(null, false);
|
||||
transform.SetPositionAndRotation(position, rotation);
|
||||
instance.SetActive(true);
|
||||
RestartEffects(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static bool Release(GameObject instance)
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PooledResourceMarker marker = instance.GetComponent<PooledResourceMarker>();
|
||||
return marker != null && Release(marker.Path, instance);
|
||||
}
|
||||
|
||||
public static bool Release(string path, GameObject instance)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path) || instance == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string key = NormalizePath(path);
|
||||
if (PooledObjects.Contains(instance))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Mark(instance, key);
|
||||
StopEffects(instance);
|
||||
instance.SetActive(false);
|
||||
instance.transform.SetParent(EnsurePoolRoot(), false);
|
||||
|
||||
Stack<GameObject> pool;
|
||||
if (!Pools.TryGetValue(key, out pool))
|
||||
{
|
||||
pool = new Stack<GameObject>();
|
||||
Pools.Add(key, pool);
|
||||
}
|
||||
|
||||
pool.Push(instance);
|
||||
PooledObjects.Add(instance);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static int GetPooledCount(string path)
|
||||
{
|
||||
Stack<GameObject> pool;
|
||||
return Pools.TryGetValue(NormalizePath(path), out pool) ? pool.Count : 0;
|
||||
}
|
||||
|
||||
public static void Clear(string path)
|
||||
{
|
||||
string key = NormalizePath(path);
|
||||
Stack<GameObject> pool;
|
||||
if (!Pools.TryGetValue(key, out pool))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DestroyPool(pool);
|
||||
Pools.Remove(key);
|
||||
}
|
||||
|
||||
public static void ClearAll()
|
||||
{
|
||||
foreach (Stack<GameObject> pool in Pools.Values)
|
||||
{
|
||||
DestroyPool(pool);
|
||||
}
|
||||
|
||||
Pools.Clear();
|
||||
PooledObjects.Clear();
|
||||
}
|
||||
|
||||
private static GameObject Pop(string path)
|
||||
{
|
||||
Stack<GameObject> pool;
|
||||
string key = NormalizePath(path);
|
||||
if (!Pools.TryGetValue(key, out pool))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
while (pool.Count > 0)
|
||||
{
|
||||
GameObject instance = pool.Pop();
|
||||
PooledObjects.Remove(instance);
|
||||
if (instance != null)
|
||||
{
|
||||
Mark(instance, key);
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
Pools.Remove(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void Mark(GameObject instance, string path)
|
||||
{
|
||||
PooledResourceMarker marker = instance.GetComponent<PooledResourceMarker>();
|
||||
if (marker == null)
|
||||
{
|
||||
marker = instance.AddComponent<PooledResourceMarker>();
|
||||
}
|
||||
|
||||
marker.Path = NormalizePath(path);
|
||||
}
|
||||
|
||||
private static Transform EnsurePoolRoot()
|
||||
{
|
||||
GameObject main = GameObject.Find(MainName);
|
||||
if (main == null)
|
||||
{
|
||||
main = new GameObject(MainName);
|
||||
}
|
||||
|
||||
Transform poolRoot = main.transform.Find(PoolRootName);
|
||||
if (poolRoot != null)
|
||||
{
|
||||
return poolRoot;
|
||||
}
|
||||
|
||||
GameObject cacheObject = new GameObject(PoolRootName);
|
||||
Transform cacheTransform = cacheObject.transform;
|
||||
cacheTransform.SetParent(main.transform, false);
|
||||
cacheTransform.localPosition = Vector3.zero;
|
||||
cacheTransform.localRotation = Quaternion.identity;
|
||||
cacheTransform.localScale = Vector3.one;
|
||||
return cacheTransform;
|
||||
}
|
||||
|
||||
private static void DestroyPool(Stack<GameObject> pool)
|
||||
{
|
||||
while (pool.Count > 0)
|
||||
{
|
||||
GameObject instance = pool.Pop();
|
||||
PooledObjects.Remove(instance);
|
||||
DestroyUnityObject(instance);
|
||||
}
|
||||
}
|
||||
|
||||
private static void StopEffects(GameObject instance)
|
||||
{
|
||||
ParticleSystem[] particles = instance.GetComponentsInChildren<ParticleSystem>(true);
|
||||
for (int i = 0; i < particles.Length; i++)
|
||||
{
|
||||
particles[i].Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RestartEffects(GameObject instance)
|
||||
{
|
||||
ParticleSystem[] particles = instance.GetComponentsInChildren<ParticleSystem>(true);
|
||||
for (int i = 0; i < particles.Length; i++)
|
||||
{
|
||||
particles[i].Clear(true);
|
||||
particles[i].Play(true);
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizePath(string path)
|
||||
{
|
||||
return string.IsNullOrEmpty(path) ? string.Empty : path.Replace('\\', '/');
|
||||
}
|
||||
|
||||
private static void DestroyUnityObject(UObject obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
UObject.Destroy(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
UObject.DestroyImmediate(obj);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class PooledResourceMarker : MonoBehaviour
|
||||
{
|
||||
public string Path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7426300ebda743f0bc14267a6403a810
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user