Unity 游戏框架搭建 2017 (二十) 安全的对象池

6 篇文章 1 订阅
4 篇文章 1 订阅

上篇文章介绍了,只需通过实现 IObjectFactory 接口和继承 Pool 类,就可以很方便地实现一个SimpleObjectPool。SimpleObjectPool 可以满足大部分的对象池的需求。而笔者通常将 SimpleObjectPool 用于项目开发,原因是接入比较方便,适合在发现性能瓶颈时迅速接入,不需要更改瓶颈对象的内部代码,而且代码精简较容易掌控。

本篇内容会较多:)

新的需求来了

当我们把对象池应用在框架开发中,我们就有了新的需求。

  • 要保证使用时安全。
  • 易用性。

现在让我们思考下 SimpleObjectPool 哪里不安全?

贴上 SimpleObjectPool 的源码:

		public class SimpleObjectPool<T> : Pool<T>
		{
			readonly Action<T> mResetMethod;
	
			public SimpleObjectPool(Func<T> factoryMethod, Action<T> resetMethod = null,int initCount = 0)
			{
				mFactory = new CustomObjectFactory<T>(factoryMethod);
				mResetMethod = resetMethod;
	
				for (int i = 0; i < initCount; i++)
				{
					mCacheStack.Push(mFactory.Create());
				}
			}
	
			public override bool Recycle(T obj)
			{
				mResetMethod.InvokeGracefully(obj);
				mCacheStack.Push(obj);
				return true;
			}
		}

首先不安全的地方是泛型 T,在上篇文章中我们说泛型是灵活的体现,但是在框架设计中未约束的泛型却有可能是未知的隐患。我们很有可能在写代码时把 SimpleObjectPool<Fish> 写成 SimpleObjectPool<Fit>,而如果恰好你的工程里有 Fit 类,再加上使用var来声明变量而不是具体的类型(笔者较喜欢用var),那么这个错误要过好久才能发现。

为了解决这个问题,我们要给泛型T加上约束。要求可被对象池管理的对象必须是某种类型。是什么类型呢?就是IPoolAble类型。

public interface IPoolable
{
			
}

然后我们要给对象池类的泛型加上类型约束,本文的对象池我们叫SafeObjectPool。

public class SafeObjectPool<T> : Pool<T> where T : IPoolable

OK,第一个安全问题解决了。

第二个安全问题来了,我们有可能将一个 IPoolable 对象回收两次。为了解决这个问题,我们可以在SafeObjectPool 维护一个已经分配过的对象容器来记录对象是否被回收过,也可以在 IPoolable 对象中增加是否被回收的标记。这两种方式笔者倾向于后者,维护一个容器的成本相比只是在对象上增加标记的成本来说高太多了。

我们在 IPoolable 接口上增加一个 bool 变量来表示对象是否被回收过。

public interface IPoolAble
{        
	bool IsRecycled { get; set; }
}

接着在进行 Allocate 和 Recycle 时进行标记和拦截。

public class SafeObjectPool<T> : Pool<T> where T : IPoolAble
{
	...
	public override T Allocate()
	{
		T result = base.Allocate();
		result.IsRecycled = false;
		return result;
	}
	
	public override bool Recycle(T t)
	{
		if (t == null || t.IsRecycled)
		{
			return false;
		}
	
		t.IsRecycled = true;
		mCacheStack.Push(t);
	
		return true;
	}
}

OK,第二个安全问题解决了。接下来第三个不是安全问题,是职责问题。我们再次观察下上篇文章中的 SimpleObjectPool

public class SimpleObjectPool<T> : Pool<T>
{
	readonly Action<T> mResetMethod;
	
	public SimpleObjectPool(Func<T> factoryMethod, Action<T> resetMethod = null,int initCount = 0)
	{
		mFactory = new CustomObjectFactory<T>(factoryMethod);
		mResetMethod = resetMethod;
	
		for (int i = 0; i < initCount; i++)
		{
			mCacheStack.Push(mFactory.Create());
		}
	}
	
	public override bool Recycle(T obj)
	{
		mResetMethod.InvokeGracefully(obj);
		mCacheStack.Push(obj);
		return true;
	}
}

可以看到,对象回收时的重置操作是由构造函数传进来的 mResetMethod 来完成的。当然,上篇忘记说了,这也是灵活的体现:)通过将重置的控制权开放给开发者,这样在接入 SimpleObjectPool 时,不需要更改对象内部的代码。

在框架设计中我们要收敛一些了,重置的操作要由对象自己来完成,我们要在 IPoolable 接口增加一个接收重置事件的方法。

public interface IPoolAble
{
	void OnRecycled();
			
	bool IsRecycled { get; set; }
}

当 SafeObjectPool 回收对象时来触发它。

public class SafeObjectPool<T> : Pool<T> where T : IPoolAble
{
	...
	public override bool Recycle(T t)
	{
		if (t == null || t.IsRecycled)
		{
			return false;
		}
	
		t.IsRecycled = true;
		t.OnRecycled();
		mCacheStack.Push(t);
	
		return true;
	}
}

同样地,在 SimpleObjectPool 中,创建对象的控制权我们也开放了出去,在 SafeObjectPool 中我们要收回来。还记得上篇文章的 CustomObjectFactory 嘛?

public class CustomObjectFactory<T> : IObjectFactory<T>
{
	public CustomObjectFactory(Func<T> factoryMethod)
	{
		mFactoryMethod = factoryMethod;
	}
			
	protected Func<T> mFactoryMethod;
	
	public T Create()
	{
		return mFactoryMethod();
	}
}

CustomObjectFactory 不管要创建对象的构造方法是私有的还是公有的,只要开发者有办法搞出个对象就可以。现在我们要加上限制,大部分对象是 new 出来的。所以我们要设计一个可以 new 出对象的工厂。我们叫它 DefaultObjectFactory。

public class DefaultObjectFactory<T> : IObjectFactory<T> where T : new()
{
	public T Create()
	{
		return new T();
	}
}

注意下对泛型 T 的约束:)
接下来我们在构造 SafeObjectPool 时,创建一个 DefaultObjectFactory。

public class SafeObjectPool<T> : Pool<T> where T : IPoolAble, new()
{
	public SafeObjectPool()
	{
		mFactory = new DefaultObjectFactory<T>();
	}
	...

注意 SafeObjectPool 的泛型也要加上 new() 的约束。
这样安全的 SafeObjectPool 已经完成了。
我们先测试下:

class Msg : IPoolAble
{
	public void OnRecycled()
	{
		Log.I("OnRecycled");
	}
				
	public bool IsRecycled { get; set; }
}
			
private void Start()
{
	var msgPool = new SafeObjectPool<Msg>();
				
	msgPool.Init(100,50); // max count:100 init count: 50
				
	Log.I("msgPool.CurCount:{0}", msgPool.CurCount);
				
	var fishOne = msgPool.Allocate();
				
	Log.I("msgPool.CurCount:{0}", msgPool.CurCount);
				
	msgPool.Recycle(fishOne);
	
	Log.I("msgPool.CurCount:{0}", msgPool.CurCount);
				
	for (int i = 0; i < 10; i++)
	{
		msgPool.Allocate();
	}
				
	Log.I("msgPool.CurCount:{0}", msgPool.CurCount);
}

由于是框架级的对象池,例子将上文的 Fish 改成 Msg。

输出结果:

OnRecycled 
OnRecycled
... x50
msgPool.CurCount:50
msgPool.CurCount:49
OnRecycled
msgPool.CurCount:50
msgPool.CurCount:40

OK,测试结果没问题。不过,难道要让用户自己去维护 Msg 的对象池?

改进:

以上只是保证了机制的安全,这还不够。

我们想要用户获取一个 Msg 对象应该像 new Msg() 一样自然。要做到这样,我们需要做一些工作。

首先,Msg 的对象池全局只有一个就够了,为了实现这个需求,我们会想到用单例,但是 SafeObjectPool 已经继承了 Pool 了,不能再继承 QSingleton 了。还记得以前介绍的 QSingletonProperty 嘛?是时候该登场了,代码如下所示。

    /// <summary>
    /// Object pool.
    /// </summary>
    public class SafeObjectPool<T> : Pool<T>, ISingleton where T : IPoolAble, new()
    {
        #region Singleton
        protected void OnSingletonInit()
        {
        }

        public SafeObjectPool()
        {
            mFactory = new DefaultObjectFactory<T>();
        }

        public static SafeObjectPool<T> Instance
        {
            get { return QSingletonProperty<SafeObjectPool<T>>.Instance; }
        }

        public void Dispose()
        {
            QSingletonProperty<SafeObjectPool<T>>.Dispose();
        }
        #endregion

注意,构造方法的访问权限改成了 protected.

我们现在不想让用户通过 SafeObjectPool 来 Allocate 和 Recycle 池对象了,那么 Allocate 和 Recycle 的控制权就要交给池对象来管理。

由于控制权交给池对象管理这个需求不是必须的,所以我们要再提供一个接口

    public interface IPoolType
    {
        void Recycle2Cache();
    }

为什么只有一个 Recycle2Cache,没有 Allocate 相关的方法呢?

因为在池对象创建之前我们没有任何池对象,只能用静态方法创建。这就需要池对象提供一个静态的 Allocate 了。使用方法如下所示。

class Msg : IPoolAble,IPoolType
{
	#region IPoolAble 实现
	
	public void OnRecycled()
	{
		Log.I("OnRecycled");
	}
				
	public bool IsRecycled { get; set; }
	
	#endregion
			
				
	#region IPoolType 实现
	
	public static Msg Allocate()
	{
		return SafeObjectPool<Msg>.Instance.Allocate();
	}
				
	public void Recycle2Cache()
	{
		SafeObjectPool<Msg>.Instance.Recycle(this);
	}
				
	#endregion
}

贴上测试代码:

SafeObjectPool<Msg>.Instance.Init(100, 50);			
				
Log.I("msgPool.CurCount:{0}", SafeObjectPool<Msg>.Instance.CurCount);
				
var fishOne = Msg.Allocate();
				
Log.I("msgPool.CurCount:{0}", SafeObjectPool<Msg>.Instance.CurCount);
				
fishOne.Recycle2Cache();
	
Log.I("msgPool.CurCount:{0}", SafeObjectPool<Msg>.Instance.CurCount);
				
for (int i = 0; i < 10; i++)
{
	Msg.Allocate();
}
				
Log.I("msgPool.CurCount:{0}", SafeObjectPool<Msg>.Instance.CurCount);

测试结果:

OnRecycled 
OnRecycled
... x50
msgPool.CurCount:50
msgPool.CurCount:49
OnRecycled
msgPool.CurCount:50
msgPool.CurCount:40

测试结果一致,现在贴上 SafeObejctPool 的全部代码。这篇文章内容好多,写得我都快吐了- -。

using System;
	
/// <summary>
/// I cache type.
/// </summary>
public interface IPoolType
{
	void Recycle2Cache();
}
	
/// <summary>
/// I pool able.
/// </summary>
public interface IPoolAble
{
	void OnRecycled();
			
	bool IsRecycled { get; set; }
}
	
/// <summary>
/// Count observer able.
/// </summary>
public interface ICountObserveAble
{
	int CurCount { get; }
}
	
/// <summary>
/// Object pool.
/// </summary>
public class SafeObjectPool<T> : Pool<T>, ISingleton where T : IPoolAble, new()
{
	#region Singleton
	public void OnSingletonInit()
	{
	}
	
	protected SafeObjectPool()
	{
		mFactory = new DefaultObjectFactory<T>();
	}
	
	public static SafeObjectPool<T> Instance
	{
		get { return QSingletonProperty<SafeObjectPool<T>>.Instance; }
	}
	
	public void Dispose()
	{
		QSingletonProperty<SafeObjectPool<T>>.Dispose();
	}
	#endregion
	
	/// <summary>
	/// Init the specified maxCount and initCount.
	/// </summary>
	/// <param name="maxCount">Max Cache count.</param>
	/// <param name="initCount">Init Cache count.</param>
	public void Init(int maxCount, int initCount)
	{
		if (maxCount > 0)
		{
			initCount = Math.Min(maxCount, initCount);
	
			mMaxCount = maxCount;
		}
	
		if (CurCount < initCount)
		{
			for (int i = CurCount; i < initCount; ++i)
			{
				Recycle(mFactory.Create());
			}
		}
	}
	
	/// <summary>
	/// Gets or sets the max cache count.
	/// </summary>
	/// <value>The max cache count.</value>
	public int MaxCacheCount
	{
		get { return mMaxCount; }
		set
		{
			mMaxCount = value;
	
			if (mCacheStack != null)
			{
				if (mMaxCount > 0)
				{
					if (mMaxCount < mCacheStack.Count)
					{
						int removeCount = mMaxCount - mCacheStack.Count;
						while (removeCount > 0)
						{
							mCacheStack.Pop();
							--removeCount;
						}
					}
				}
			}
		}
	}
	
	/// <summary>
	/// Allocate T instance.
	/// </summary>
	public override T Allocate()
	{
		T result = base.Allocate();
		result.IsRecycled = false;
		return result;
	}
	
	/// <summary>
	/// Recycle the T instance
	/// </summary>
	/// <param name="t">T.</param>
	public override bool Recycle(T t)
	{
		if (t == null || t.IsRecycled)
		{
			return false;
		}
	
		if (mMaxCount > 0)
		{
			if (mCacheStack.Count >= mMaxCount)
			{
				t.OnRecycled();
				return false;
			}
		}
	
		t.IsRecycled = true;
		t.OnRecycled();
		mCacheStack.Push(t);
	
		return true;
	}
}

代码实现很简单,但是要考虑很多。

总结:

  • SimpleObjectPool 适合用于项目开发,渐进式,更灵活。
  • SafeObjectPool 适合用于库级开发,更多限制,要求开发者一开始就想好,更安全。

OK,今天就到这里。

转载请注明地址:凉鞋的笔记:liangxiegame.com

更多内容

  • QFramework 地址:https://github.com/liangxiegame/QFramework

  • QQ 交流群:623597263

  • Unity 进阶小班

    • 主要训练内容:
      • 框架搭建训练(第一年)
      • 跟着案例学 Shader(第一年)
      • 副业的孵化(第二年、第三年)
    • 权益、授课形式等具体详情请查看《小班产品手册》:https://liangxiegame.com/master/intro
  • 关注公众号:liangxiegame 获取第一时间更新通知及更多的免费内容。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
这批C#开发的项目源码是一系列基于C#编程语言的项目。C#是一种强类型、面向对象的编程语言,被广泛用于Microsoft的.NET平台开发。这些项目源码通过利用C#的强大功能和丰富的库,提供了各种功能和解决方案。 这批C#项目源码涵盖了多个领域和行业,包括但不限于桌面应用程序、Web应用程序、游戏开发、数据分析等。每个项目源码都经过精心设计和开发,采用了现代化的开发技术和最佳实践。其特点包括清晰的代码结构、高效的算法和数据结构、优化的性能和可扩展性等。 在这些项目源码中,使用了丰富的C#库和框架来简化开发过程,例如.NET Core、ASP.NET、WPF、Unity等。这些库和框架提供了强大的功能和工具,帮助开发人员快速构建功能完善、高效可靠的应用程序。 这批C#项目源码还涵盖了许多常见的开发需求和功能模块,如用户管理、数据访问、图形界面、网络通信等。开发人员可以通过这些源码快速搭建起基础架构,并根据实际需求进行进一步的定制和扩展。 通过使用这批C#项目源码,开发人员能够节省大量的开发时间和精力,并得到稳定和可靠的解决方案。每个项目源码都提供了详细的文档和注释,使开发人员能够更好地理解和使用源码。不论是新手还是有经验的开发人员,都可以从这批C#项目源码中获得学习和借鉴的价值,提升开发技能和项目交付质量。
学习安排:4周4个项目实战 学习形式:录播视频+在线作业考核+助教一对一辅导答疑 1. C#基本语法:基本编程(If:…else)语句、数组等。2. C#面向对象编程:封装、继承、多态性、面向接口编程、动态多态性、字符串基础等。3. .Net 框架原理讲解:多维数组、可变参数 Params、类的实例化内存分配机制等。4. 深入学习对象类型:里氏替换原则(LSP)、类的属性极其本质特性、IS ,AS 关键字、深入学习字符串理论、枚举类型以及适用场合。5. 深入学习集合特性:索引器、自定义集合、深入刨析集合类型本质(ArrayList、HashTable)、学习泛型集合、泛型约束等。6. 学习委托与事件:委托与事件的区别、匿名方法、Lambda表达式。7. 正则表达式。8. 查询表达式Linq。9. 反射、特性、动态编程。10. 多线程编程。11. Socket通讯技术。 第二模块:Unity 入门与基础 学习安排:4周3个项目实战 学习形式:录播视频+在线作业考核+助教一对一辅导答疑 课程内容:游戏Unity发展历史概述、 3D 模型基础、 地形编辑器、光源、音频、Unity脚本程序基础、GUI、3D模型动画的导入 与基本应用、物理学模拟(初级)、碰撞盒与触发器、协程、SendMessage数据传值技术等。 学习安排:4周4个项目实战 学习形式:录播视频+在线作业考核+助教一对一辅导答疑 课程内容:讲解粒子系统、Mecanim、导航寻路、Unity游戏移植技术、Mecanim 动画系统、导航寻路、项目研发常用优化策略、Unity游戏移植与手指触控识别、射线、数据持久化、对象缓冲池技术、物理学模拟(铰链关节(Hinge Joint)、弹簧关节 、固定关节、角色关节)与资源动态加载技术(AssetBundle与WWW)等。 第四模块:Unity项目实训: ARPG项目"地下守护神" 学习安排:4周1个项目实战 学习形式:录播视频+在线作业考核+助教一对一辅导答疑 课程内容:1:前端分层的MVC架构搭建,开发自定义的资源动态加载插件、音频插件、对话系统插件、事件监听插件等。2: UI粒子特效、场景淡入淡出控制。3: 单机与移动端的双输入控制系统开发。4: 数值平衡处理与开发。5: 多种设计模式在游戏研发过程中的灵活运用。6: XML技术的灵活运用,在系统解耦与资源国际化方面的技术突破。7: 开发强大灵活的日志系统插件,应对服务器与移动端的各种复杂系统调试需求。8:开发“对话系统”,与“新手导航”。9:开发基于XML的对象持久化技术。10:开发“背包系统”、“商城系统”、“公告系统”、“任务系统”。11:开发“副本”多种类怪物的战斗系统。12:开发各种战斗特效系统: “血条”、“漂字”、“连击”等功能。 第五模块:虚拟现实(VR)与增强现实技术(AR) 学习安排:1周2个项目实战 学习形式:录播视频+在线作业考核+助教一对一辅导答疑 课程内容:R虚拟现实技术开发:先从什么是虚拟现实(VR)、虚拟现实的主要特点、虚拟现实技术的基础底层原理、国际与国内的产业布局、国际权威机构未来发展趋势预测等讲起。然后主要介绍国际主要领军品牌:Oculus、HTC、SonyVR等产品规格、主要性能指标等。最后重点以一款国内著名VR产品:3Glasses 为代表,介绍VR产品的具体使用、维护、与开发项目全过程流程等。AR 增强实现开发:讲解什么是增强现实、注册高通AR账号、制作识别图、SDK下载与制作、打包发布
这批C#开发的项目源码是一系列基于C#编程语言的项目。C#是一种强类型、面向对象的编程语言,被广泛用于Microsoft的.NET平台开发。这些项目源码通过利用C#的强大功能和丰富的库,提供了各种功能和解决方案。 这批C#项目源码涵盖了多个领域和行业,包括但不限于桌面应用程序、Web应用程序、游戏开发、数据分析等。每个项目源码都经过精心设计和开发,采用了现代化的开发技术和最佳实践。其特点包括清晰的代码结构、高效的算法和数据结构、优化的性能和可扩展性等。 在这些项目源码中,使用了丰富的C#库和框架来简化开发过程,例如.NET Core、ASP.NET、WPF、Unity等。这些库和框架提供了强大的功能和工具,帮助开发人员快速构建功能完善、高效可靠的应用程序。 这批C#项目源码还涵盖了许多常见的开发需求和功能模块,如用户管理、数据访问、图形界面、网络通信等。开发人员可以通过这些源码快速搭建起基础架构,并根据实际需求进行进一步的定制和扩展。 通过使用这批C#项目源码,开发人员能够节省大量的开发时间和精力,并得到稳定和可靠的解决方案。每个项目源码都提供了详细的文档和注释,使开发人员能够更好地理解和使用源码。不论是新手还是有经验的开发人员,都可以从这批C#项目源码中获得学习和借鉴的价值,提升开发技能和项目交付质量。
这批C#开发的项目源码是一系列基于C#编程语言的项目。C#是一种强类型、面向对象的编程语言,被广泛用于Microsoft的.NET平台开发。这些项目源码通过利用C#的强大功能和丰富的库,提供了各种功能和解决方案。 这批C#项目源码涵盖了多个领域和行业,包括但不限于桌面应用程序、Web应用程序、游戏开发、数据分析等。每个项目源码都经过精心设计和开发,采用了现代化的开发技术和最佳实践。其特点包括清晰的代码结构、高效的算法和数据结构、优化的性能和可扩展性等。 在这些项目源码中,使用了丰富的C#库和框架来简化开发过程,例如.NET Core、ASP.NET、WPF、Unity等。这些库和框架提供了强大的功能和工具,帮助开发人员快速构建功能完善、高效可靠的应用程序。 这批C#项目源码还涵盖了许多常见的开发需求和功能模块,如用户管理、数据访问、图形界面、网络通信等。开发人员可以通过这些源码快速搭建起基础架构,并根据实际需求进行进一步的定制和扩展。 通过使用这批C#项目源码,开发人员能够节省大量的开发时间和精力,并得到稳定和可靠的解决方案。每个项目源码都提供了详细的文档和注释,使开发人员能够更好地理解和使用源码。不论是新手还是有经验的开发人员,都可以从这批C#项目源码中获得学习和借鉴的价值,提升开发技能和项目交付质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凉鞋的笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值