用于处理后台消费者/生产者任务的设计或模式

时间:2014-05-08 13:12:02

标签: java opengl lwjgl

背景:

我有一个使用LWJGL实现的3D引擎,主要目的是渲染一个大型的世界' - 地形,植被,动画生物,天气,昼/夜循环等。引擎管理渲染所需的资源,如各种纹理,网格,模型等。

当玩家导航这个世界时,引擎会缓存不再需要的各种类型的数据丢弃/释放资源,并按需加载/缓存新需要的数据。这由一组后台线程处理,这些后台线程执行I / O任务(加载图像,加载新的地形' chunk',构建模型网格等)以及要在OpenGL上执行的任务队列上下文(分配新纹理,将数据上传到VBO,删除纹理等)。

当前设计:

除了具有依赖关系的任务之外,这一切都很好用。我将举例说明 - 让我们说引擎需要将新纹理应用于场景中的对象。

这包括以下步骤:

  • 从文件系统加载纹理图像(I / O后台任务)
  • 在GPU上分配纹理(OpenGL上下文任务)
  • 将图像上传到纹理(OpengL)
  • 将纹理附加到对象的材质(OpenGL,但不是真的,但避免渲染问题)

这里有一些问题需要解决:

  1. 理想情况下,我希望这些步骤(或任务)是原子的和可重复使用的,例如引擎需要为系统的其他几个部分加载图像 - 代码完全相同,因此可以(并且应该)封装和重复使用。
  2. 其中一些可以并行运行(例如加载图像和分配 纹理),其中一些必须是顺序的,例如无法上传 图像,直到我们加载它并分配纹理。
  3. 某些步骤具有资源依赖性'在前面的步骤中,例如在 特别是上传步骤需要分配的纹理ID和 图像。
  4. 前两个是相对简单的,最后一个是我正在努力的 - 我似乎无法想出一个体面的设计,允许可重复使用和相对原子的任务当存在任务间依赖时,将它们链接在一起。

    一些伪代码:

    interface Task implements Runnable {
        TaskQueue getQueue();
    }
    
    // Generic load-an-image task
    class LoadImageTask implements Task {
        private final String path;
        private BufferedImage image;
    
        public LoadImageTask( String path ) {
            this.path = path;
        }
    
        TaskQueue getQueue() { return TaskQueue.BACKGROUND; }
    
        public void run() {
            // load the image from the given location
        }
    }
    
    // Uploads an image to a given texture
    class UploadTextureTask implements Task {
        private BufferedImage image;
        private Texture texture;
    
        ...
    
        TaskQueue getQueue() { return TaskQueue.RENDER_THREAD; }
    
        public void run() {
            texture.buffer( image );
        }
    }
    
    // Example task manager for the scenario outlined above
    class Example extends TaskManager {
        ...
    
        // Load the texture image
        final LoadImageTask loadImage = new LoadImageTask( ... );
        add( loadImage );
    
        // Allocate a texture
        final AllocateTextureTask allocate = ...
        add( allocate );
    
        // Upload texture image
        final UploadTextureTask upload = ...
        add( upload, loadImage, allocate );
    }
    

    add中的TaskManager方法在后台注册相关队列上的任务。每个任务完成后,经理都会收到通知。当所有当前任务完成时(在这种情况下为loadImageallocate),经理将启动序列中的下一个任务。

    请注意,对add任务的最终upload调用会告诉管理员它对loadallocate任务有依赖性。

    问题:

    这个问题是关于资源'由生产者'返回的任务(例如LoadImageTask)传递给'消费者'任务(例如UploadTextureTask)当这些类没有以任何方式耦合时?

    一种(垃圾)方式是使用一些自定义代码,在该任务完成时调用loadImage.getImage()并将其传递给'链。使用uploadTask.setImage(),但如果必须为每个用例编写大致相同的任务管理代码,这几乎使整个方法毫无意义。

    我尝试使用通用getter / setter方法定义使用者 producer 接口,并链接具有正确匹配数据类型的相关任务,但如果a任务消耗多个资源(如上面的上传任务)。

    我还考虑过使用FutureCallable(后台任务队列无论如何都使用Executor线程池)但它们实际上是针对阻塞整个线程而不是资源管理SE。此系统中的任务数量可以是数千个,其中大多数是当前正在运行或正在排队的待处理任务。阻止尚未启动的任务似乎毫无意义。另外,了解未来何时完成的唯一方法是通过调用isDone来调用已完成任务的另一层复杂性。

    我已经在这里以及OpenGL开发者和游戏网站上研究过这个问题但是没有遇到任何好的设计 - 它们似乎只是由剪切和粘贴代码或某种共享状态组成(通常是到处都有很多令人讨厌的铸造。也许我不是在寻找正确的条款?或者也许我的整个方法都是垃圾?

    有没有人实施或遇到类似的东西?欢迎提出任何建议,批评,指示。 (并为墙上的文字道歉)

1 个答案:

答案 0 :(得分:0)

Java 8中的一些新功能似乎可以解决这类问题,例如: CompletableFuture,它使用Executor s,Runnable任务等与上述现有设计很好地集成。