This commit is contained in:
陈思海 2025-04-01 15:21:02 +08:00
parent 55da0d4f00
commit aa1da4de29
32 changed files with 120 additions and 603 deletions

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: a22aa56c3da6d724e97aafd9f3e7ed67
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,11 +0,0 @@
public interface IMixedObjectFactory<T> where T : class
{
T Create(string typeName);
void Destroy(string typeName, T obj);
void Reset(string typeName, T obj);
bool Validate(string typeName, T obj);
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 30f89ee61eb3f0949b5300bd9b7cc577
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,8 +0,0 @@
using System;
public interface IMixedObjectPool<T> : IDisposable where T : class
{
T Allocate(string typeName);
void Free(string typeName, T obj);
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 3f4c13a4827ebad4a9ff08e636fbc67e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,28 +0,0 @@
public interface IObjectFactory<T> where T : class
{
/// <summary>
/// 创建对象
/// </summary>
/// <returns></returns>
T Create();
/// <summary>
/// 销毁对象
/// </summary>
/// <param name="obj"></param>
void Destroy(T obj);
/// <summary>
/// 重置对象
/// </summary>
/// <param name="obj"></param>
void Reset(T obj);
/// <summary>
/// 验证对象
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
bool Validate(T obj);
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 77a642084db01624c8e5876605195d49
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,23 +0,0 @@
using System;
public interface IObjectPool : IDisposable
{
/// <summary>
/// 从池子中分配一个可用对象,没有的话就创建一个
/// </summary>
/// <returns></returns>
object Allocate();
/// <summary>
/// 将对象回收到池子中去,如果池中的对象数量已经超过了 maxSize则直接销毁该对象
/// </summary>
/// <param name="obj"></param>
void Free(object obj);
}
public interface IObjectPool<T> : IObjectPool, IDisposable where T : class
{
new T Allocate();
void Free(T obj);
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: dd12164a5eea20e41bf7f3b7704f4b33
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,5 +0,0 @@
public interface IPooledObject
{
void Free();
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: b174ccb64b3938c449d4a69a3262d8d5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,101 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
public class MixedObjectPool<T> : IMixedObjectPool<T> where T : class
{
private const int DEFAULT_MAX_SIZE_PER_TYPE = 10;
private readonly ConcurrentDictionary<string, List<T>> entries;
private readonly ConcurrentDictionary<string, int> typeSize;
private readonly IMixedObjectFactory<T> factory;
private int defaultMaxSizePerType;
public MixedObjectPool(IMixedObjectFactory<T> factory) : this(factory, DEFAULT_MAX_SIZE_PER_TYPE)
{
}
public MixedObjectPool(IMixedObjectFactory<T> factory, int defaultMaxSizePerType)
{
this.factory = factory;
this.defaultMaxSizePerType = defaultMaxSizePerType;
if (defaultMaxSizePerType <= 0)
{
throw new ArgumentException("The maxSize must be greater than 0.");
}
entries = new ConcurrentDictionary<string, List<T>>();
typeSize = new ConcurrentDictionary<string, int>();
}
public T Allocate(string typeName)
{
if (entries.TryGetValue(typeName, out List<T> list) && list.Count > 0)
{
T obj = list[0];
list.RemoveAt(0);
return obj;
}
return factory.Create(typeName);
}
public void Free(string typeName, T obj)
{
if (obj == null) return;
if (!factory.Validate(typeName, obj))
{
factory.Destroy(typeName, obj);
return;
}
int maxSize = GetMaxSize(typeName);
List<T> list = entries.GetOrAdd(typeName, n => new List<T>());
if (list.Count >= maxSize)
{
factory.Destroy(typeName, obj);
return;
}
factory.Reset(typeName, obj);
list.Add(obj);
}
public int GetMaxSize(string typeName)
{
if (typeSize.TryGetValue(typeName, out int size))
{
return size;
}
return defaultMaxSizePerType;
}
public void SetMaxSize(string typeName, int value)
{
typeSize.AddOrUpdate(typeName, value, (key, oldValue) => value);
}
protected virtual void Clear()
{
foreach (var kv in entries)
{
string typeName = kv.Key;
List<T> list = kv.Value;
if (list == null || list.Count <= 0) continue;
list.ForEach(e => factory.Destroy(typeName, e));
list.Clear();
}
entries.Clear();
typeSize.Clear();
}
public void Dispose()
{
Clear();
GC.SuppressFinalize(this);
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 0a62e68f43eac4b419140191eb09ea56
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,108 +0,0 @@
using System;
using System.Threading;
public class ObjectPool<T> : IObjectPool<T> where T : class
{
private int maxSize;
private int initialSize;
protected readonly T[] entries = null;
protected readonly IObjectFactory<T> factory;
public ObjectPool(IObjectFactory<T> factory) : this(factory, Environment.ProcessorCount * 2)
{
}
public ObjectPool(IObjectFactory<T> factory, int maxSize) : this(factory, 0, maxSize)
{
}
public ObjectPool(IObjectFactory<T> factory, int initialSize, int maxSize)
{
this.factory = factory;
this.initialSize = initialSize;
this.maxSize = maxSize;
this.entries = new T[maxSize];
if (maxSize < initialSize)
{
throw new ArgumentException("The maxSize must be greater than or equal to the initialSize.");
}
for (int i = 0; i < initialSize; i++)
{
entries[i] = factory.Create();
}
}
public int MaxSize => maxSize;
public int InitialSize => initialSize;
public virtual T Allocate()
{
for (var i = 0; i < entries.Length; i++)
{
T value = entries[i];
if (value == null) continue;
if (Interlocked.CompareExchange(ref entries[i], null, value) == value)
{
return value;
}
}
return factory.Create();
}
public virtual void Free(T obj)
{
if (obj == null) return;
if (!factory.Validate(obj))
{
factory.Destroy(obj);
return;
}
factory.Reset(obj);
for (var i = 0; i < entries.Length; i++)
{
if (Interlocked.CompareExchange(ref entries[i], obj, null) == null)
{
return;
}
}
factory.Destroy(obj);
}
object IObjectPool.Allocate()
{
return Allocate();
}
public void Free(object obj)
{
Free((T)obj);
}
protected virtual void Clear()
{
for (var i = 0; i < entries.Length; i++)
{
var value = Interlocked.Exchange(ref entries[i], null);
if (value != null)
{
factory.Destroy(value);
}
}
}
public void Dispose()
{
Clear();
GC.SuppressFinalize(this);
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 30999e5e03e2b434996100b09960468f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,36 +0,0 @@
using UnityEngine;
public class UnityComponentFactory<T> : IObjectFactory<T> where T : Component
{
private T template;
private Transform parent;
public UnityComponentFactory(T template, Transform parent)
{
this.template = template;
this.parent = parent;
}
public T Create()
{
T obj = Object.Instantiate(template, parent);
return obj;
}
public void Destroy(T obj)
{
Object.Destroy(obj.gameObject);
}
public void Reset(T obj)
{
obj.gameObject.SetActive(false);
obj.gameObject.transform.position = Vector3.zero;
obj.gameObject.transform.rotation = Quaternion.identity;
}
public bool Validate(T obj)
{
return true;
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 3e2e3a0444783a7469d494ad630dc705
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,35 +0,0 @@
using UnityEngine;
public class UnityGameObjectFactory : IObjectFactory<GameObject>
{
protected GameObject template;
protected Transform parent;
public UnityGameObjectFactory(GameObject template, Transform parent)
{
this.template = template;
this.parent = parent;
}
public virtual GameObject Create()
{
return Object.Instantiate(template, parent);
}
public virtual void Reset(GameObject obj)
{
obj.SetActive(false);
obj.transform.position = Vector3.zero;
obj.transform.rotation = Quaternion.identity;
}
public virtual void Destroy(GameObject obj)
{
Object.Destroy(obj);
}
public virtual bool Validate(GameObject obj)
{
return true;
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 7373bce96f2a515499c52b060ad9e01e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,61 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UnityMixedComponentFactory<T> : IMixedObjectFactory<T> where T : Component
{
protected T template;
protected Transform parent;
protected List<T> list;
private Dictionary<string, T> dict = new();
public UnityMixedComponentFactory(T template, Transform parent)
{
this.template = template;
this.parent = parent;
}
public UnityMixedComponentFactory(List<T> list, Transform parent)
{
this.list = list;
this.parent = parent;
foreach (var data in list)
{
dict[data.name] = data;
}
}
public UnityMixedComponentFactory(Dictionary<string, T> dict, Transform parent)
{
this.dict = dict;
this.parent = parent;
}
public T Create(string typeName)
{
T obj = Object.Instantiate(dict[typeName], parent);
obj.transform.position = Vector3.zero;
obj.transform.rotation = Quaternion.identity;
return obj;
}
public void Destroy(string typeName, T obj)
{
Object.Destroy(obj.gameObject);
}
public void Reset(string typeName, T obj)
{
obj.gameObject.SetActive(false);
obj.transform.position = Vector3.zero;
obj.transform.rotation = Quaternion.identity;
}
public bool Validate(string typeName, T obj)
{
return true;
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 5c2d58d006be3fc458b8c6761d76bc88
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,40 +0,0 @@
using UnityEngine;
public class UnityMixedGameObjectFactory : IMixedObjectFactory<GameObject>
{
protected GameObject template;
protected Transform parent;
public UnityMixedGameObjectFactory(GameObject template, Transform parent)
{
this.template = template;
this.parent = parent;
}
public GameObject Create(string typeName)
{
GameObject obj = Object.Instantiate(template, parent);
GameObject model = Object.Instantiate(Resources.Load<GameObject>("ObjectPools/" + typeName), obj.transform);
model.transform.position = Vector3.zero;
model.transform.rotation = Quaternion.identity;
return obj;
}
public void Destroy(string typeName, GameObject obj)
{
Object.Destroy(obj);
}
public void Reset(string typeName, GameObject obj)
{
obj.SetActive(false);
obj.transform.position = Vector3.zero;
obj.transform.rotation = Quaternion.identity;
}
public bool Validate(string typeName, GameObject obj)
{
return true;
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 16c8995b85215c6458119d581eea60b0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -236,6 +236,11 @@ namespace AlicizaX.UI.RecyclerView
}
}
private void OnDestroy()
{
viewProvider?.Dispose();
}
public void Reset()
{
viewProvider?.Reset();

View File

@ -45,5 +45,6 @@ namespace AlicizaX.UI.RecyclerView
public virtual void BindChoiceState(bool state)
{
}
}
}

View File

@ -0,0 +1,35 @@
using AlicizaX.ObjectPool;
using UnityEngine;
namespace AlicizaX.UI.RecyclerView
{
internal class ViewHolderObject : ObjectBase
{
public static ViewHolderObject Create(string location, UnityEngine.Object target)
{
ViewHolderObject item = MemoryPool.Acquire<ViewHolderObject>();
item.Initialize(location, target);
return item;
}
protected override void OnUnspawn()
{
base.OnUnspawn();
(Target as ViewHolder).gameObject.SetActive(false);
}
protected override void OnSpawn()
{
base.OnSpawn();
(Target as ViewHolder).gameObject.SetActive(true);
}
protected override void Release(bool isShutdown)
{
if (Target != null)
{
GameObject.Destroy((Target as ViewHolder).gameObject);
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6a241cb39eb24df0ae5db5a7a764dd27
timeCreated: 1743476681

View File

@ -0,0 +1,43 @@
using AlicizaX.ObjectPool;
using UnityEngine;
namespace AlicizaX.UI.RecyclerView
{
internal static class ViewHolderObjectPool
{
private static readonly IObjectPool<ViewHolderObject> _ObjectPool;
private const string OBJECTPOOLNAME = "ViewHolderObjectPool";
static ViewHolderObjectPool()
{
_ObjectPool = GameApp.ObjectPool.CreateSingleSpawnObjectPool<ViewHolderObject>(name: OBJECTPOOLNAME, autoReleaseInterval: 60, capacity: 60, expireTime: float.MaxValue, 0);
}
public static ViewHolder Allocate(ViewHolder template, Transform parent)
{
ViewHolder viewHolder = null;
ViewHolderObject viewHolderObject = _ObjectPool.Spawn(template.name);
if (viewHolderObject != null)
{
viewHolder = (ViewHolder)viewHolderObject.Target;
}
else
{
viewHolder = UnityEngine.Object.Instantiate(template, parent);
_ObjectPool.Register(ViewHolderObject.Create(template.name, viewHolder), true);
}
return viewHolder;
}
public static void Free(ViewHolder viewHolder)
{
_ObjectPool.Unspawn(viewHolder);
}
public static void Release(ViewHolder viewHolder)
{
_ObjectPool.ReleaseObject(viewHolder);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cae2bcb0572045019267379f5dab4e23
timeCreated: 1743486723

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AlicizaX.ObjectPool;
using UnityEngine;
namespace AlicizaX.UI.RecyclerView
@ -10,7 +11,6 @@ namespace AlicizaX.UI.RecyclerView
[SerializeField] private ViewHolder chatLeftViewHolder;
[SerializeField] private ViewHolder chatRightViewHolder;
private IMixedObjectPool<ViewHolder> objectPool;
private Dictionary<string, ViewHolder> dict = new();
public MixedViewProvider(RecyclerView recyclerView, ViewHolder[] templates) : base(recyclerView, templates)
@ -19,9 +19,6 @@ namespace AlicizaX.UI.RecyclerView
{
dict[template.name] = template;
}
UnityMixedComponentFactory<ViewHolder> factory = new(dict, recyclerView.Content);
objectPool = new MixedObjectPool<ViewHolder>(factory);
}
public override ViewHolder GetTemplate(string viewName)
@ -30,6 +27,7 @@ namespace AlicizaX.UI.RecyclerView
{
throw new NullReferenceException("ViewProvider templates can not null or empty.");
}
return dict[viewName];
}
@ -39,25 +37,23 @@ namespace AlicizaX.UI.RecyclerView
{
throw new NullReferenceException("ViewProvider templates can not null or empty.");
}
return dict.Values.ToArray();
}
public override ViewHolder Allocate(string viewName)
{
var viewHolder = objectPool.Allocate(viewName);
viewHolder.gameObject.SetActive(true);
return viewHolder;
return ViewHolderObjectPool.Allocate(GetTemplate(viewName), recyclerView.Content);
}
public override void Free(string viewName, ViewHolder viewHolder)
{
objectPool.Free(viewName, viewHolder);
ViewHolderObjectPool.Free(viewHolder);
}
public override void Reset()
{
Clear();
objectPool.Dispose();
}
}
}

View File

@ -1,15 +1,15 @@
using System;
using AlicizaX.ObjectPool;
using UnityEngine;
namespace AlicizaX.UI.RecyclerView
{
public sealed class SimpleViewProvider : ViewProvider
{
private readonly IObjectPool<ViewHolder> objectPool;
public SimpleViewProvider(RecyclerView recyclerView, ViewHolder[] templates) : base(recyclerView, templates)
{
UnityComponentFactory<ViewHolder> factory = new(GetTemplate(), recyclerView.Content);
objectPool = new ObjectPool<ViewHolder>(factory, 100);
}
public override ViewHolder GetTemplate(string viewName = "")
@ -18,6 +18,7 @@ namespace AlicizaX.UI.RecyclerView
{
throw new NullReferenceException("ViewProvider templates can not null or empty.");
}
return templates[0];
}
@ -27,25 +28,24 @@ namespace AlicizaX.UI.RecyclerView
{
throw new NullReferenceException("ViewProvider templates can not null or empty.");
}
return templates;
}
public override ViewHolder Allocate(string viewName)
{
var viewHolder = objectPool.Allocate();
viewHolder.gameObject.SetActive(true);
return viewHolder;
return ViewHolderObjectPool.Allocate(GetTemplate(),recyclerView.Content);
}
public override void Free(string viewName, ViewHolder viewHolder)
{
objectPool.Free(viewHolder);
ViewHolderObjectPool.Free(viewHolder);
}
public override void Reset()
{
Clear();
objectPool.Dispose();
}
}
}
}

View File

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using AlicizaX.UI.Runtime;
using UnityEngine;
namespace AlicizaX.UI.RecyclerView
@ -6,7 +8,7 @@ namespace AlicizaX.UI.RecyclerView
/// <summary>
/// 提供和管理 ViewHolder
/// </summary>
public abstract class ViewProvider
public abstract class ViewProvider : IDisposable
{
private readonly List<ViewHolder> viewHolders = new();
@ -83,6 +85,7 @@ namespace AlicizaX.UI.RecyclerView
return viewHolder;
}
}
return null;
}
@ -100,6 +103,7 @@ namespace AlicizaX.UI.RecyclerView
return i;
}
}
return -1;
}
@ -109,6 +113,7 @@ namespace AlicizaX.UI.RecyclerView
{
Free(viewHolder.Name, viewHolder);
}
viewHolders.Clear();
}
@ -127,5 +132,16 @@ namespace AlicizaX.UI.RecyclerView
{
return Adapter == null ? 0 : Adapter.GetItemCount();
}
public void Dispose()
{
foreach (var viewHolder in viewHolders)
{
Free(viewHolder.Name, viewHolder);
ViewHolderObjectPool.Release(viewHolder);
}
viewHolders.Clear();
}
}
}