使用弱引用和GC进行渲染

本文关键字:GC 引用 | 更新日期: 2023-09-27 17:59:04

问题

我最近开始学习C#。我是通过制作一个游戏来做到这一点的(因为我在C++中对此非常熟悉)。

要绘制到后缓冲区的对象在构造时通过发送带有对对象的弱引用的事件进行"注册"。由于C++智能指针被引用计数,并且(因此)一旦最后一个引用超出范围,对象就会被销毁,这对我来说很好。然而,在C#中,情况肯定不是这样。

考虑以下代码片段:

foreach(WeakReference<DrawableObject> drawable in drawables.ToList())
{
    DrawableObject target;
    drawable.TryGetTarget(out target);
    if(target != null)
    {
        spriteBatch.Draw(target.Texture, target.Position, target.Rectangle, target.Colour);
    }
    else
    {
        drawables.Remove(drawable);
    }
}

C#中的主要问题(我相信我很可能还没有理解其他问题)是,在对对象的最后一个强引用超出范围后,target不能保证是null


潜在解决方案

强制垃圾收集

我可以提前在适当的时候强制调用GC,但在对这个想法进行了一些研究后,我发现这很可能是一个坏主意和/或代码中其他问题的迹象。

使用Hide()方法

虽然这种方法对任何游戏都是必不可少的,但必须显式地调用每个可绘制对象的Hide()是乏味的,并且给了更多的错误空间。此外,何时应该调用所有这些Hide()?我也不能依赖于父对象被及时收集,所以把它们放在Finalizer中并不能解决问题。

实现IDisposable

与上面类似,使用using语句的替代方案(我不知道该放在哪里,因为对象需要保持活动状态,直到它们的父对象超出范围)。


问题

我想不出的任何解决方案似乎都不能恰当地解决这个问题。当然,我错过了不以这种方式使用弱引用的想法,但如果没有找到合适的解决方案来处理当前状态下的游戏,我将不得不考虑这样做。所有这些都归结为几个相关的问题:

  • 这是强制垃圾收集的合适用例吗?

  • 如果不是,我如何确定对象是否是在GC的下一次运行时收集

  • 有没有办法在最后一个调用对象的方法对它的引用超出了范围(隐式或显式),而不是等待GC的到来

当然,我也非常感谢任何能完全避免这个问题的建议。

使用弱引用和GC进行渲染

我将尝试回答这个问题。。

你在寻找的是"根据游戏规则,我已经死了"的逻辑。不是"那个对象死了,因为垃圾回收器这么说"的逻辑。

为此。。你需要把你的思维转向这样的东西:

class DrawableObject {
    public bool IsDead { get; set; }
    public int Health { get; set; }
    // ...
    public void GetShot(int amount) {
        Health -= amount;
        if (Health <= 0)
            IsDead = true;
    }
}

然后:

foreach (var drawable in drawables) {
    DrawableObject target;
    drawable.TryGetTarget(out target);
    if(target == null || target.IsDead) {
        drawables.Remove(drawable);
    }
    else {
        spriteBatch.Draw(target.Texture, target.Position, target.Rectangle, target.Colour);
    }
}

如果它的null基于您的TryGetXXX逻辑。。或者它已经被你的游戏逻辑标记为死亡。。将其从可绘制对象列表中删除。之后,让垃圾收集器随时清理它。

TLDR:将你的对象标记为死,并将你的逻辑建立在你自己的"我死了"标志的基础上。