一次性上下文对象模式

发布于 2024-12-26 15:47:36 字数 2985 浏览 1 评论 0原文

简介

我刚刚想到了一种新的设计模式。我想知道它是否存在,如果不存在,为什么不存在(或者为什么我不应该使用它)。

我正在使用 OpenGL 创建游戏。在 OpenGL 中,您经常想要“绑定”事物——即,使它们成为当前上下文一段时间,然后解除它们的绑定。例如,您可以调用 glBegin(GL_TRIANGLES) 然后绘制一些三角形,然后调用 glEnd()。我喜欢缩进中间的所有内容,这样就可以清楚地开始和结束,但是我的 IDE 喜欢取消缩进它们,因为没有大括号。然后我想我们可以做点聪明的事!它的工作原理基本上是这样的:

using(GL.Begin(GL_BeginMode.Triangles)) {
   // draw stuff
}

GL.Begin返回一个特殊的DrawBind对象(带有内部构造函数)并实现IDisposable,以便它自动调用< code>GL.End() 在块的末尾。这样一切都保持良好对齐,并且您不会忘记调用 end()。

这种模式有名字吗?

通常当我看到 using 使用时,你会这样使用它:

using(var x = new Whatever()) {
   // do stuff with `x`
}

但在这种情况下,我们不需要在“使用”对象上调用任何方法,因此我们不需要分配它除了调用相应的结束函数之外,它没有任何作用。


Anthony Pegram 的示例

,他想要我当前正在处理的代码的真实示例:

重构之前:

public void Render()
{
    _vao.Bind();
    _ibo.Bind(BufferTarget.ElementArrayBuffer);
    GL.DrawElements(BeginMode.Triangles, _indices.Length, DrawElementsType.UnsignedInt, IntPtr.Zero);
    BufferObject.Unbind(BufferTarget.ElementArrayBuffer);
    VertexArrayObject.Unbind();
}

重构后:

public void Render()
{
    using(_vao.Bind())
    using(_ibo.Bind(BufferTarget.ElementArrayBuffer))
    {
        GL.DrawElements(BeginMode.Triangles, _indices.Length, DrawElementsType.UnsignedInt, IntPtr.Zero);
    }
}

请注意,第二个好处是 _ibo.Bind 返回的对象还记得我想要哪个“BufferTarget”解除绑定。它还会引起您对 GL.DrawElements 的注意,这实际上是该函数中唯一重要的语句(做了一些值得注意的事情),并隐藏了那些冗长的取消绑定语句。

我想一个缺点是我无法用这种方法交错缓冲区目标。我不确定何时需要,但我必须保留对绑定对象的引用并手动调用 Dispose,或者手动调用 end 函数。


命名

如果没有人反对,我将把这个一次性上下文对象(DCO) 惯用语称为“一次性上下文对象(DCO)”。


问题

JasonTrue 提出了一个很好的观点,即在这种情况下(OpenGL 缓冲区)嵌套的 using 语句将无法按预期工作,因为只有一次可以绑定一个缓冲区。不过,我们可以通过扩展“绑定对象”来使用堆栈来解决这个问题:

public class BufferContext : IDisposable
{
    private readonly BufferTarget _target;
    private static readonly Dictionary<BufferTarget, Stack<int>> _handles;

    static BufferContext()
    {
        _handles = new Dictionary<BufferTarget, Stack<int>>();
    }

    internal BufferContext(BufferTarget target, int handle)
    {
        _target = target;
        if (!_handles.ContainsKey(target)) _handles[target] = new Stack<int>();
        _handles[target].Push(handle);
        GL.BindBuffer(target, handle);
    }

    public void Dispose()
    {
        _handles[_target].Pop();
        int handle = _handles[_target].Count > 0 ? _handles[_target].Peek() : 0;
        GL.BindBuffer(_target, handle);
    }
}

编辑:刚刚注意到一个问题。以前,如果您没有 Dispose() 上下文对象,实际上不会产生任何后果。上下文不会切换回原来的状态。现在,如果你忘记在某种循环中处理它,你就会遇到 stackoverflow。也许我应该限制堆栈大小......

Introduction

I just thought of a new design pattern. I'm wondering if it exists, and if not, why not (or why I shouldn't use it).

I'm creating a game using an OpenGL. In OpenGL, you often want to "bind" things -- i.e., make them the current context for a little while, and then unbind them. For example, you might call glBegin(GL_TRIANGLES) then you draw some triangles, then call glEnd(). I like to indent all the stuff inbetween so it's clear where it starts and ends, but then my IDE likes to unindent them because there are no braces. Then I thought we could do something clever! It basically works like this:

using(GL.Begin(GL_BeginMode.Triangles)) {
   // draw stuff
}

GL.Begin returns a special DrawBind object (with an internal constructor) and implements IDisposable so that it automatically calls GL.End() at the end of the block. This way everything stays nicely aligned, and you can't forget to call end().

Is there a name for this pattern?

Usually when I see using used, you use it like this:

using(var x = new Whatever()) {
   // do stuff with `x`
}

But in this case, we don't need to call any methods on our 'used' object, so we don't need to assign it to anything and it serves no purpose other than to call the corresponding end function.


Example

For Anthony Pegram, who wanted a real example of code I'm currently working on:

Before refactoring:

public void Render()
{
    _vao.Bind();
    _ibo.Bind(BufferTarget.ElementArrayBuffer);
    GL.DrawElements(BeginMode.Triangles, _indices.Length, DrawElementsType.UnsignedInt, IntPtr.Zero);
    BufferObject.Unbind(BufferTarget.ElementArrayBuffer);
    VertexArrayObject.Unbind();
}

After refactoring:

public void Render()
{
    using(_vao.Bind())
    using(_ibo.Bind(BufferTarget.ElementArrayBuffer))
    {
        GL.DrawElements(BeginMode.Triangles, _indices.Length, DrawElementsType.UnsignedInt, IntPtr.Zero);
    }
}

Notice that there's a 2nd benefit that the object returned by _ibo.Bind also remembers which "BufferTarget" I want to unbind. It also draws your atention to GL.DrawElements, which is really the only significant statement in that function (that does something noticeable), and hides away those lengthy unbind statements.

I guess the one downside is that I can't interlace Buffer Targets with this method. I'm not sure when I would ever want to, but I would have to keep a reference to bind object and call Dispose manually, or call the end function manually.


Naming

If no one objects, I'm dubbing this Disposable Context Object (DCO) Idiom.


Problems

JasonTrue raised a good point, that in this scenario (OpenGL buffers) nested using statements would not work as expected, as only one buffer can be bound at a time. We can remedy this, however, by expanding on "bind object" to use stacks:

public class BufferContext : IDisposable
{
    private readonly BufferTarget _target;
    private static readonly Dictionary<BufferTarget, Stack<int>> _handles;

    static BufferContext()
    {
        _handles = new Dictionary<BufferTarget, Stack<int>>();
    }

    internal BufferContext(BufferTarget target, int handle)
    {
        _target = target;
        if (!_handles.ContainsKey(target)) _handles[target] = new Stack<int>();
        _handles[target].Push(handle);
        GL.BindBuffer(target, handle);
    }

    public void Dispose()
    {
        _handles[_target].Pop();
        int handle = _handles[_target].Count > 0 ? _handles[_target].Peek() : 0;
        GL.BindBuffer(_target, handle);
    }
}

Edit: Just noticed a problem with this. Before if you didn't Dispose() of your context object there wasn't really any consequence. The context just wouldn't switch back to whatever it was. Now if you forget to Dispose of it inside some kind of loop, you're wind up with a stackoverflow. Perhaps I should limit the stack size...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

愛上了 2025-01-02 15:47:36

Asp.Net MVC 与 HtmlHelper 也使用了类似的策略。请参阅 http://msdn.microsoft。 com/en-us/library/system.web.mvc.html.formextensions.beginform.aspx使用 (Html.BeginForm()) {....}

)除了对文件句柄、数据库或网络连接、字体等非托管资源明显“需要”IDisposable 之外,至少有一个先例将该模式用于其他用途。我不认为它有一个特殊的名称,但在实践中,它似乎是 C# 习惯用法,与 C++ 习惯用法相对应,资源获取就是初始化。

当您打开文件时,您将获取并保证处理文件上下文;在您的示例中,您要获取的资源用您的话来说是“绑定上下文”。虽然我听说过“处置模式”或“使用模式”用于描述广泛的类别,但本质上您所说的是“确定性清理”;您正在控制对象的生命周期。

我不认为这真的是一个“新”模式,它在您的用例中脱颖而出的唯一原因是,显然您所依赖的 OpenGL 实现没有做出特别的努力来匹配 C# 习惯用法,这需要您构建您自己的代理对象。

我唯一担心的是是否有任何不明显的副作用,例如,如果您有一个嵌套上下文,其中块中更深处有类似的 using 结构(或调用堆栈) )。

A similar tactic is used with Asp.Net MVC with the HtmlHelper. See http://msdn.microsoft.com/en-us/library/system.web.mvc.html.formextensions.beginform.aspx (using (Html.BeginForm()) {....})

So there's at least one precedent for using this pattern for something other than the obvious "need" for IDisposable for unmanaged resources like file handles, database or network connections, fonts, and so on. I don't think there's a special name for it, but in practice, it seems to be the C# idiom that serves as the counterpart to the C++ idiom, Resource Acquisition is Initialization.

When you're opening a file, you're acquiring, and guaranteeing the disposal of, a file context; in your example, the resource you're acquiring is a is a "binding context", in your words. While I've heard "Dispose pattern" or "Using pattern" used to describe the broad category, essentially "deterministic cleanup" is what you're talking about; you're controlling the lifetime the object.

I don't think it's really a "new" pattern, and the only reason it stands out in your use case is that apparently the OpenGL implementation you're depending on didn't make a special effort to match C# idioms, which requires you to build your own proxy object.

The only thing I'd worry about is if there are any non-obvious side effects, if, for example, you had a nested context where there were similar using constructs deeper in your block (or call stack).

_失温 2025-01-02 15:47:36

ASP.NET/MVC 使用此(可选)模式来呈现

元素的开头和结尾,如下所示:

@using (Html.BeginForm()) {
    <div>...</div>
}

这与您的示例类似,因为您没有消耗您的值IDisposable 除了其一次性语义之外。我从来没有听说过这个名字,但我之前在其他类似的场景中使用过这种东西,并且除了了解如何通常利用 using 块之外,从未将其视为其他任何东西IDisposable 类似于我们通过实现 IEnumerable 来利用 foreach 语义。

ASP.NET/MVC uses this (optional) pattern to render the beginning and ending of a <form> element like this:

@using (Html.BeginForm()) {
    <div>...</div>
}

This is similar to your example in that you are not consuming the value of your IDisposable other than for its disposable semantics. I've never heard of a name for this, but I've used this sort of thing before in other similar scenarios, and never considered it as anything other than understanding how to generally leverage the using block with IDisposable similar to how we can tap into the foreach semanatics by implementing IEnumerable.

秋千易 2025-01-02 15:47:36

我认为这更像是一个习语而不是一种模式。模式通常更复杂,涉及多个活动部分,而习惯用法只是在代码中执行操作的巧妙方法。

在C++中它的使用相当多。每当您想要获取某些内容或进入某个范围时,您都会创建一个类的自动变量(即在堆栈上),该变量开始创建或您需要完成的任何操作入口。当您离开声明自动变量的范围时,将调用析构函数。然后,析构函数应该结束删除或任何需要清理的内容。

class Lock {
private:

  CriticalSection* criticalSection;

public:

  Lock() {
    criticalSection = new CriticalSection();
    criticalSection.Enter();
  }

  ~Lock() {
    criticalSection.Leave();
    delete criticalSection;
  }

}

void F() {
  Lock lock();

  // Everything in here is executed in a critical section and it is exception safe.
}

I would this is more an idiom than a pattern. Patterns usually are more complex involving several moving parts, and idioms are just clever ways to do things in code.

In C++ it is used quite a lot. Whenever you want to aquire something or enter a scope you create an automatic variable (i.e. on the stack) of a class that begins or creates or whatever you need to be done on entry. When you leave the scope where the automatic variable is declared the destructor is called. The destructor should then end or delete or whatever is required to clean up.

class Lock {
private:

  CriticalSection* criticalSection;

public:

  Lock() {
    criticalSection = new CriticalSection();
    criticalSection.Enter();
  }

  ~Lock() {
    criticalSection.Leave();
    delete criticalSection;
  }

}

void F() {
  Lock lock();

  // Everything in here is executed in a critical section and it is exception safe.
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文