示例
使用Unity开发游戏使用适当的内存管理非常重要,如果你想制作流畅的游戏在手机上面运行;
根据运行的平台和制作的游戏类型不同,对于尽可能的避免开辟不需要的堆内存非常的重要;
最有效的途径的使用内存池,代码示例
public class Foo
{
public class Factory : PlaceholderFactory<Foo>
{
}
}
public class Bar
{
readonly Foo.Factory _fooFactory;
readonly List<Foo> _foos = new List<Foo>();
public Bar(Foo.Factory fooFactory)
{
_fooFactory = fooFactory;
}
public void AddFoo()
{
_foos.Add(_fooFactory.Create());
}
public void RemoveFoo()
{
_foos.RemoveAt(0);
}
}
public class TestInstaller : MonoInstaller<TestInstaller>
{
public override void InstallBindings()
{
Container.Bind<Bar>().AsSingle();
Container.BindFactory<Foo, Foo.Factory>();
}
}
这里,每次我们调用Bar.AddFoo将会开辟新的堆内存;每次我们调用Bar.RemoveFoo,Bar类将会从Foo的实例里面停止引用,因此Foo的实例内存标记为可以垃圾回收;如果这样发生的次数过多,最终垃圾回收器将变成负担并且你的游戏由于
spike(急剧上升)过高导致卡顿,使用memory pools来修复急剧上升的开销替代
public class Foo
{
public class Pool : MemoryPool<Foo>
{
}
}
public class Bar
{
readonly Foo.Pool _fooPool;
readonly List<Foo> _foos = new List<Foo>();
public Bar(Foo.Pool fooPool)
{
_fooPool = fooPool;
}
public void AddFoo()
{
_foos.Add(_fooPool.Spawn());
}
public void RemoveFoo()
{
var foo = _foos[0];
_fooPool.Despawn(foo);
_foos.Remove(foo);
}
}
public class TestInstaller : MonoInstaller<TestInstaller>
{
public override void InstallBindings()
{
Container.Bind<Bar>().AsSingle();
Container.BindMemoryPool<Foo, Foo.Pool>();
}
}
正如你看到的这样,内存池和工厂非常相似,除了部分的术语一点不同之后,
不像工厂你需要返回实例给池子相比工厂的仅仅是停止引用实例
使用了这种新的实现方式,将会在调用AddFoo()初始化的时候分配堆内存,
但你调用RemoveFoo()然后再AddFoo(),池子序列将重用之前的实例并因此节省你的堆内存开销
这样更好,我们想避免因为初始化导致内存的急剧上升;解决方法是再游戏启动的时候初始化好所有的内存开销
public class TestInstaller : MonoInstaller<TestInstaller>
{
public override void InstallBindings()
{
Container.Bind<Bar>().AsSingle();
Container.BindMemoryPool<Foo, Foo.Pool>().WithInitialSize(10);
}
}
当我们使用WithInitialSize语句绑定语法到我的池子,10个实例化的Foo将会在启动的时候立即创建作为池子的种子
Binding Syntax 绑定语法
内存池的语法大多数和工厂一样,只有部分新的绑定方法,类如 WithInitialSize 和 ExpandBy;同样,不像BindFactory,
没有必要给工厂指定参数,BindMemoryPool生成参数
和工厂一样,推荐使用内部公开类命名为Pool
public class Foo
{
public class Pool : MemoryPool<Foo>
{
}
}
通过添加生成参数来设置参数
public class Foo
{
public class Pool : MemoryPool<string, int, Foo>
{
}
}
全部的绑定语法格式
Container.BindMemoryPool<ObjectType, MemoryPoolType>()
.With(InitialSize|FixedSize)
.WithMaxSize(MaxSize)
.ExpandBy(OneAtATime|Doubling)()
.WithFactoryArguments(Factory Arguments)
.To<ResultType>()
.WithId(Identifier)
.FromConstructionMethod()
.AsScope()
.WithArguments(Arguments)
.OnInstantiated(InstantiatedCallback)
.When(Condition)
.CopyIntoAllSubContainers()
.NonLazy();
字段说明:
- InitialSize - 这个值决定启动的时候播种多少个对象到内存池里面;对于避免游戏期间急剧上升的内存有帮助
- FixedSize - 设置这个值,内存池将会初始化指定的数量种子,如果这个值超过了,则抛异常
- MaxSize - 设置这个值,如果有足够的对象已经返回到池子里面,超过了内存池的最大数量,多余的对象将会被销毁;
这个决定内存池最大开辟的内存空间 - ObjectType - 通过内存池实例化的类类型
- MemoryPoolType - MemoryPool派生类的类型,通常是一个内部类命名为Pool
- ExpandBy - 当内存池达到最大数量的时候决定内存池的怎么进行拓展,注意当使用WithFixedSize的时候,这个选项无效
- ExpandByOneAtATime - 当有需求的时候,一次只多开辟一个新对象
- ExpandByDoubling - 当内存池满了并且有一个新实例请求,内存池将开辟双倍的容量在返回请求实例之前;
这个方法在需要开辟数量多,功能小的对象非常有帮助
- WithFactoryArguments - 如果你想注入额外的参数到内存池的派生类,你可以通过这里传递,
注意WithArguments是给实例传递参数而不是内存池传递参数 - Scope - 注意和正常的绑定不一样,默认是AsCached替代AsTransient
剩下的绑定方法和正常的绑定方法一样
Resetting Items In Pool 重置池子对象
我们要注意使用内存池替代工厂必须确保完全重置了指定的实例;这个是非常有必要的,
有可能上一个状态来之之前的数 据,重新实例化之后还保留了原来的数据,内存池的实现方法有下列这些
public class Foo
{
public class Pool : MemoryPool<Foo>
{
protected override void OnCreated(Foo item)
{
// Called immediately after the item is first added to the pool
}
protected override void OnDestroyed(Foo item)
{
// Called immediately after the item is removed from the pool without also being spawned
// This occurs when the pool is shrunk either by using WithMaxSize or by explicitly shrinking the pool by calling the `ShrinkBy` / `Resize methods
}
protected override void OnSpawned(Foo item)
{
// Called immediately after the item is removed from the pool
}
protected override void OnDespawned(Foo item)
{
// Called immediately after the item is returned to the pool
}
protected override void Reinitialize(Foo foo)
{
// Similar to OnSpawned
// Called immediately after the item is removed from the pool
// This method will also contain any parameters that are passed along
// to the memory pool from the spawning code
}
}
}
大多数情况下,你大概只要实现Reinitialize方法;代码示例
public class Foo
{
Vector3 _position = Vector3.zero;
public void Move(Vector3 delta)
{
_position += delta;
}
public class Pool : MemoryPool<Foo>
{
protected override void Reinitialize(Foo foo)
{
foo._position = Vector3.zero;
}
}
}
注意我们的池子可以自由访问Foo的私有变量,因为池子是一个内部类;或者,我们避免在Foo和Foo.Pool里面重复出现
public class Foo
{
Vector3 _position;
public Foo()
{
Reset();
}
public void Move(Vector3 delta)
{
_position += delta;
}
void Reset()
{
_position = Vector3.zero;
}
public class Pool : MemoryPool<Foo>
{
protected override void Reinitialize(Foo foo)
{
foo.Reset();
}
}
}
运行时传递参数
和工厂一样,你可以在运行时生成新的实例传递参数,不同的是,替代参数注入到类里面,通过Reinitialize方法
public class Foo
{
Vector3 _position;
Vector3 _velocity;
public Foo()
{
Reset(Vector3.zero);
}
public void Tick()
{
_position += _velocity * Time.deltaTime;
}
void Reset(Vector3 velocity)
{
_position = Vector3.zero;
_velocity = Vector3.zero;
}
public class Pool : MemoryPool<Vector3, Foo>
{
protected override void Reinitialize(Vector3 velocity, Foo foo)
{
foo.Reset(velocity);
}
}
}
public class Bar
{
readonly Foo.Pool _fooPool