本文介绍了可以使资源无效的RAII的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是一个业余爱好者 C ++ DirectX 程序员,所以我所拥有的大部分知识都来自于旧游戏开发书籍,其中的代码设计只是为了进行演示和运行,因此即使是最简单的程序,也给我带来了许多设计注意事项。在开发这样一个程序的过程中,我最近了解到 RAII ,所以我决定尝试一下这种设计模式,因为据我了解,一个对象应该在构造,这大大简化了程序可以使用对象的方式。以前,我一直在使用 create()&我的某些对象中的 destroy()模式导致大多数成员函数中的大量验证检查。

I'm a hobbyist C++ and DirectX programmer, so most of the knowledge I have is from old game development books in which the code designs are just to get something up and running as a demonstration, leaving me with a lot of design considerations for even the simplest of programs. During development of such a program, I recently learned of RAII, so I decided to give this design pattern a shot because from what I understood, an object should be usable and valid upon construction and this greatly simplifies the way objects can be used by the program. Previously, I had been using a create() & destroy() pattern in some of my objects which lead to a lot of validation checking in most member functions.

在程序的体系结构中,很少有图形对象围绕 DirectX 资源进行包装,其中之一是纹理目的。例如,如果要渲染图块地图,则可以有 Tile 对象,这些对象是用指向 AnimatedImage 对象的指针构造的使用指向纹理对象的指针构造。

In the program's architecture, I have very few graphics objects that are wrappers around DirectX resources, one of them being a Texture object. If I want to render tile map for example, I could have Tile objects that are constructed with pointers to AnimatedImage objects which are constructed with pointers to Texture objects.

使用 DirectX 是图形设备有时会丢失的情况,例如在程序执行过程中更新视频卡的驱动程序。当这些事件发生时,必须释放并重新获取现有的图形资源以继续正常渲染,包括破坏和重建 Texture 对象。这使得 RAII 设计模式的使用似乎不是最佳选择。我需要重新创建纹理对象,重新创建 AnimatedImage 对象,然后重新创建 Tile 对象。这似乎是一个极度的麻烦,因为一些重新创建的对象将不仅仅包含图像数据。

The problem with using DirectX is that there are times in which the graphics device becomes lost, such as a driver update for the video card during program execution. When these events happen, the existing graphics resources must be released and reacquired to continue rendering normally, including the destruction and reconstruction of the Texture objects. This makes the use of an RAII design pattern seem like it may not be the best choice. I would need recreate the Texture objects, recreate the AnimatedImage objects, and then recreate the Tile objects. It seems like an extreme hassle because some objects that are recreated will contain more than just image data.

因此,如果我们从一些示例代码开始(不是精确的,但是它达到其目的):

So if we start out with some example code (not exact, but it serves its purpose):

// Construct graphics objects into some structure we will pass around.
graphics.pDevice = new GraphicsDevice(windowHandle, screenWitdth, screenHeight);
graphics.pTexture1 = new Texture(graphics.pDevice, width1, height1, pPixelData1);
graphics.pTexture2 = new Texture(graphics.pDevice, width2, height2, pPixelData2);
graphics.pSpriteBuffer = new SpriteBuffer(graphics.pDevice, maxSprites);

在构建图块对象的程序中的其他位置:

Elsewhere in the program that builds objects for a tile map:

// Construct some in-game animations.
images.pGrass = new AnimatedImage(graphics.pTexture1, coordinates1[4], duration1);
images.pWater = new AnimatedImage(graphics.pTexture2, coordinates2[4], duration2);

// Construct objects to display the animation and contain physical attributes.
thisMap.pMeadowTile = new Tile(images.pGrass, TILE_ATTRIBUTE_SOFT);
thisMap.pPondTile = new Tile(images.pWater, TILE_ATTRIBUTE_SWIMMABLE);

然后在渲染例程中:

while (gameState.isRunning())
{
    graphics.pSpriteBuffer->clear();
    thisMap.bufferSprites(graphics.pSpriteBuffer);
    graphics.pSpriteBuffer->draw();
    if (graphics.pDevice->present() == RESULT_COULD_NOT_COMPLETE)
    {
         // Uh oh! The device has been lost!
         // We need to release and recreate all graphics objects or we cannot render.
         // Let's destruct the sprite buffer, textures, and graphics device.
         // But wait, our animations were constructed with textures, the pointers are
         //   no longer valid and must be destructed.
         // Come to think of it, the tiles were created with animations, so they too
         //   must be destructed, which is a hassle since their physical attributes
         //   really are unrelated to the graphics.
         // Oh no, what other objects have graphical dependencies must we consider?
    }
}

是否存在某些设计概念?这是 RAII 起作用的情况之一,但是如果存在大量的对象间依赖关系,则会付出不必要的大笔费用吗?有任何已知的设计模式专门适合这种情况吗?

Is there some design concept I am missing here or is this one of those cases in which RAII works, but at a unnecessarily large cost if there is a lot of object to object dependency? Are there any known design patterns that are specifically suited for this scenario?

以下是我想到的一些解决方法:

Here were some of the ways I have thought of approaching this:


  1. 使用 recreate()方法装备图形对象。优点是指向纹理的任何对象都可以保留该指针而不会被破坏。缺点是,如果重新获取失败,我将得到一个僵尸对象,该对象并不比 create()& destroy()模式。

  1. Equip the graphics objects with a recreate() method. The advantage is that any object that points to a Texture can retain that pointer without being destroyed. The disadvantage is that if the reacquisition fails, I would be left with a zombie object which is no better than the create() & destroy() pattern.

使用所有图形对象的注册表添加间接级别返回纹理指针的索引或指向纹理指针的指针,以便依赖图形的现有对象不不需要销毁。优点和缺点与上面相同,另外还有间接带来的额外开销的缺点。

Add a level of indirection using a registry of all graphics objects that will return an index to a Texture pointer or a pointer to a Texture pointer so that existing objects that rely on graphics don't need to be destroyed. Advantages and disadvantages are the same as above with the added disadvantage of additional overhead from the indirection.

存储程序的当前状态并退回到图形为止对象已经被重新获取,然后以其原来的状态重新生成程序。我没有想到真正的优势,但似乎最适合 RAII 。缺点是在不太常见的情况下实施此操作很复杂。

Store the current state of the program and unwind back until the graphics objects have been reacquired, then rebuild the program in the state it was in. No real advantage I can think of, but seems the most RAII appropriate. The disadvantage is the complexity in implementing this for a scenario that is not too common.

将对象的所有视觉表示与它们的物理表示完全分开。优点是实际上只需要重新创建必要的对象,这可以使程序的其余部分保持有效状态。缺点是物理和视觉对象仍然需要以某种方式相互了解,这可能会导致对象管理代码膨胀。

Completely segregate all visual representations of objects from their physical representations. The advantage is that only the necessary objects to be recreated actually are, which can leave the rest of the program in a valid state. The disadvantage is that the physical and visual objects still need to know about each other in some way, which may lead to some bloated object management code.

中止程序执行。这样做的好处是,它很容易实现,很少的工作花在很少发生的事情上。缺点是使用该程序的任何人都会感到沮丧。

Abort program execution. The advantage is that this is as easy as it gets and very little work is spent for something that does not often happen. The disadvantage is that it would be frustrating to anyone using the program.


推荐答案

第一个解决方案



在2000年代后期(至少对于台式机显卡而言),这是一个好问题。在2015年,您最好忘记DirectX 9并丢失设备,而直接使用DirectX 11(甚至即将推出的DirectX 12)。

First solution

That's a good question for the late 2000ths (at least for desktop graphics). In 2015 you'd better forget DirectX 9 with it's device lost and do DirectX 11 (or even upcoming DirectX 12).

如果您仍然希望使用已弃用的API(或者同时在移动设备上使用诸如OpenGL ES之类的东西,其中上下文丢失是常见事件),那么有一种方法可以很好地工作(等等)。基本上是您的混合

If you still want to stick with deprecated API (or if you are using same time something like OpenGL ES on mobile devices, where context loss is a common event), there is a way that works pretty well (among others). Basically it is a mix of yours

这里是:


  • 重构您的代码中带有:强制用户分配具有功能的新资源(将 new std :: make_shared 或您在其中使用的任何内容)

  • refactor your code with a Factory pattern: force user to allocate new resources with functions (wrap new, std::make_shared or whatever you use inside them)

auto resource = device-> createResource(param0,param1);

使工厂以某种方式记住资源

make factory remember resources somehow

std::vector<IResourcePtr> resources;

ResourcePtr Device::createResource(T param0, U param1)
{
    auto resource = std::make_shared<Resource>(this, param0, param1);
    resources.push_back(resource);
    return resource;
}


  • 让资源记住它的参数(可以在需要时更改它们的参数)运行时,但也应保存。对于大型或昂贵的参数对象,请使用)

    Resource::Resource(IDevice* device, T param0, U param1)
       : m_device(device)
       , m_param0(param0)
       , m_param1(param1)
    {
        create(); // private
    }
    

  • 在设备丢失事件中释放
  • ,释放所有对象,然后重新创建它们

  • on device lost event, release all objects, than recreate them

    while (rendering)
    {
        device->fixIfLost();
        ...
    }
    
    void Device::fixIfLost()
    {
        if(isLost())
        {
            for(auto&& resource: resources)
                resource->reload();
        }
    }
    
    void Resource::reload()
    {
        release(); // private
        create();  // private
    }
    


  • 您可以在此基础上构建更复杂,更智能的系统。

    You can build more complicated and intelligent system on top of that.


    它不特定于设备丢失事件。处理资源会立即失败,然后再放弃对用户的控制,就像您第一次创建资源时一样,并且失败(通过引发异常(以便用户可以处理它),使用占位符资源或关闭资源应用程序或其他任何东西-您可以自行决定)

    It is not specific to device lost event. Handle resource fails immediately, before giving up control to the user, same way that you do when you create resource first time and it fails (by throwing an exception (so user could handle it), or by using placeholder resource or by shutting down the application, or anything else -- you to decide)

    必须有。甚至没有要讨论的问题,除非您要构建俄罗斯方块。使用或类似。切勿将 Mesh 存储在 Player 内,以及 ParticleEmitter 火球

    Must have. Not even a question to discuss, unless you are building a tetris. Use MVC or modern stuff like ECS. Never store your Mesh inside Player, and your ParticleEmitter inside Fireball. Never even make them know each other.

    这非常有用。您所描述的是保存游戏 /加载游戏的机制。它还可以用于实现重播功能和游戏引擎电影。注意(要添加到第2点),您的保存数据将永远不会包含视觉表示(除非您需要数GB的保存文件)。

    It's very useful. What you've described is a "save game" / "load game" mechanics. It could also be used to implement "replay" functionality and game-engine movies. Note (as to add to point 2), your save data will never include visual representations (Unless you want multi-gigabyte save files).

    不要。大多数游戏根本不会打扰。它们以与用户执行保存游戏->退出->加载游戏相同的方式处理丢失的设备,并适当地设计了启动工具。

    Don't overengineer. Most games don't bother at all. They handle device lost in the same way as if user did "Save game" -> "Exit" -> "Load game", designing startup facilities appropriately.

    单独使用或与工厂结合使用的另一种方法是:让您的资源进行验证

    Another way to use by itself or in combination with factory is Lazy initialization: make your resource to verify both if it's valid itself and device lost.

    void Resource::apply()
    {
        if((!isValid()) || (!device->isValid()))
        {
    
        }
       // apply resource here
    }
    

    每次访问资源时都会增加一些开销,但这是一种安全且非常简单的实现方式,可确保您的资源在需要时已用完

    It adds some overhead each time resource is accessed, but it's a safe and very simple to implement way to ensure your resource is up when it's needed

    这篇关于可以使资源无效的RAII的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

    09-04 23:05