import { FrameGraphPass } from "./Passes/pass.js";
import { FrameGraphRenderPass } from "./Passes/renderPass.js";
import { FrameGraphObjectListPass } from "./Passes/objectListPass.js";
import { FrameGraphRenderContext } from "./frameGraphRenderContext.js";
import { FrameGraphContext } from "./frameGraphContext.js";
import { FrameGraphTextureManager } from "./frameGraphTextureManager.js";
import { Observable } from "../Misc/observable.js";
import { _RetryWithInterval } from "../Misc/timingTools.js";
import { Logger } from "../Misc/logger.js";
import { UniqueIdGenerator } from "../Misc/uniqueIdGenerator.js";
var FrameGraphPassType;
(function (FrameGraphPassType) {
    FrameGraphPassType[FrameGraphPassType["Normal"] = 0] = "Normal";
    FrameGraphPassType[FrameGraphPassType["Render"] = 1] = "Render";
    FrameGraphPassType[FrameGraphPassType["ObjectList"] = 2] = "ObjectList";
})(FrameGraphPassType || (FrameGraphPassType = {}));
/**
 * Class used to implement a frame graph
 * @experimental
 */
export class FrameGraph {
    /**
     * Gets the engine used by the frame graph
     */
    get engine() {
        return this._engine;
    }
    /**
     * Gets the scene used by the frame graph
     */
    get scene() {
        return this._scene;
    }
    /**
     * Gets the list of tasks in the frame graph
     */
    get tasks() {
        return this._tasks;
    }
    /**
     * Gets the node render graph linked to the frame graph (if any)
     * @returns the linked node render graph or null if none
     */
    getLinkedNodeRenderGraph() {
        return this._linkedNodeRenderGraph;
    }
    /**
     * Constructs the frame graph
     * @param scene defines the scene the frame graph is associated with
     * @param debugTextures defines a boolean indicating that textures created by the frame graph should be visible in the inspector (default is false)
     * @param _linkedNodeRenderGraph defines the linked node render graph (if any)
     */
    constructor(scene, debugTextures = false, _linkedNodeRenderGraph = null) {
        this._linkedNodeRenderGraph = _linkedNodeRenderGraph;
        this._tasks = [];
        this._initAsyncPromises = [];
        this._currentProcessedTask = null;
        this._whenReadyAsyncCancel = null;
        /**
         * Name of the frame graph
         */
        this.name = "Frame Graph";
        /**
         * Gets the unique id of the frame graph
         */
        this.uniqueId = UniqueIdGenerator.UniqueId;
        /**
         * Gets or sets a boolean indicating that texture allocation should be optimized (that is, reuse existing textures when possible to limit GPU memory usage) (default: true)
         */
        this.optimizeTextureAllocation = true;
        /**
         * Observable raised when the node render graph is built
         */
        this.onBuildObservable = new Observable();
        /**
         * Indicates whether the execution of the frame graph is paused (default is false)
         */
        this.pausedExecution = false;
        this._built = false; // TODO: to be removed when build() is removed
        this._scene = scene;
        this._engine = scene.getEngine();
        this._importPromise = this._engine.isWebGPU ? import("../Engines/WebGPU/Extensions/engine.multiRender.js") : import("../Engines/Extensions/engine.multiRender.js");
        this.textureManager = new FrameGraphTextureManager(this._engine, debugTextures, scene);
        this._passContext = new FrameGraphContext(this._engine, this.textureManager, scene);
        this._renderContext = new FrameGraphRenderContext(this._engine, this.textureManager, scene);
        this._scene.addFrameGraph(this);
    }
    /**
     * Gets the class name of the frame graph
     * @returns the class name
     */
    getClassName() {
        return "FrameGraph";
    }
    /**
     * Gets a task by name
     * @param name Name of the task to get
     * @returns The task or undefined if not found
     */
    getTaskByName(name) {
        return this._tasks.find((t) => t.name === name);
    }
    /**
     * Gets all tasks of a specific type
     * @param taskType Type of the task(s) to get
     * @returns The list of tasks of the specified type
     */
    getTasksByType(taskType) {
        return this._tasks.filter((t) => t instanceof taskType);
    }
    /**
     * Adds a task to the frame graph
     * @param task Task to add
     */
    addTask(task) {
        if (this._currentProcessedTask !== null) {
            throw new Error(`FrameGraph.addTask: Can't add the task "${task.name}" while another task is currently building (task: ${this._currentProcessedTask.name}).`);
        }
        this._tasks.push(task);
        this._initAsyncPromises.push(task.initAsync());
    }
    /**
     * Adds a pass to a task. This method can only be called during a Task.record execution.
     * @param name The name of the pass
     * @param whenTaskDisabled If true, the pass will be added to the list of passes to execute when the task is disabled (default is false)
     * @returns The render pass created
     */
    addPass(name, whenTaskDisabled = false) {
        return this._addPass(name, FrameGraphPassType.Normal, whenTaskDisabled);
    }
    /**
     * Adds a render pass to a task. This method can only be called during a Task.record execution.
     * @param name The name of the pass
     * @param whenTaskDisabled If true, the pass will be added to the list of passes to execute when the task is disabled (default is false)
     * @returns The render pass created
     */
    addRenderPass(name, whenTaskDisabled = false) {
        return this._addPass(name, FrameGraphPassType.Render, whenTaskDisabled);
    }
    /**
     * Adds an object list pass to a task. This method can only be called during a Task.record execution.
     * @param name The name of the pass
     * @param whenTaskDisabled If true, the pass will be added to the list of passes to execute when the task is disabled (default is false)
     * @returns The object list pass created
     */
    addObjectListPass(name, whenTaskDisabled = false) {
        return this._addPass(name, FrameGraphPassType.ObjectList, whenTaskDisabled);
    }
    _addPass(name, passType, whenTaskDisabled = false) {
        if (!this._currentProcessedTask) {
            throw new Error("FrameGraph: A pass must be created during a Task.record execution only.");
        }
        let pass;
        switch (passType) {
            case FrameGraphPassType.Render:
                pass = new FrameGraphRenderPass(name, this._currentProcessedTask, this._renderContext, this._engine);
                break;
            case FrameGraphPassType.ObjectList:
                pass = new FrameGraphObjectListPass(name, this._currentProcessedTask, this._passContext, this._engine);
                break;
            default:
                pass = new FrameGraphPass(name, this._currentProcessedTask, this._passContext);
                break;
        }
        this._currentProcessedTask._addPass(pass, whenTaskDisabled);
        return pass;
    }
    /** @internal */
    async _whenAsynchronousInitializationDoneAsync() {
        if (this._initAsyncPromises.length > 0) {
            await Promise.all(this._initAsyncPromises);
            this._initAsyncPromises.length = 0;
        }
    }
    /**
     * @deprecated Use buildAsync instead
     */
    build() {
        void this.buildAsync(false);
    }
    /**
     * Builds the frame graph.
     * This method should be called after all tasks have been added to the frame graph (FrameGraph.addTask) and before the graph is executed (FrameGraph.execute).
     * @param waitForReadiness If true, the method will wait for the frame graph to be ready before returning (default is true)
     */
    async buildAsync(waitForReadiness = true) {
        this.textureManager._releaseTextures(false);
        this.pausedExecution = true;
        this._built = false;
        try {
            await this._importPromise;
            await this._whenAsynchronousInitializationDoneAsync();
            for (const task of this._tasks) {
                task._reset();
                this._currentProcessedTask = task;
                this.textureManager._isRecordingTask = true;
                task.record();
                this.textureManager._isRecordingTask = false;
                this._currentProcessedTask = null;
            }
            this.textureManager._allocateTextures(this.optimizeTextureAllocation ? this._tasks : undefined);
            for (const task of this._tasks) {
                task._checkTask();
            }
            for (const task of this._tasks) {
                task.onTexturesAllocatedObservable.notifyObservers(this._renderContext);
            }
            this._built = true;
            this.onBuildObservable.notifyObservers(this);
            if (waitForReadiness) {
                await this.whenReadyAsync();
            }
        }
        catch (e) {
            this._tasks.length = 0;
            this._currentProcessedTask = null;
            this.textureManager._isRecordingTask = false;
            throw e;
        }
        finally {
            this.pausedExecution = false;
            this._built = true;
        }
    }
    /**
     * Checks if the frame graph is ready to be executed.
     * Note that you can use the whenReadyAsync method to wait for the frame graph to be ready.
     * @returns True if the frame graph is ready to be executed, else false
     */
    isReady() {
        let ready = this._renderContext._isReady();
        for (const task of this._tasks) {
            ready && (ready = task.isReady());
        }
        return ready;
    }
    /**
     * Returns a promise that resolves when the frame graph is ready to be executed.
     * In general, calling “await buildAsync()” should suffice, as this function also waits for readiness by default.
     * @param timeStep Time step in ms between retries (default is 16)
     * @param maxTimeout Maximum time in ms to wait for the graph to be ready (default is 10000)
     * @returns The promise that resolves when the graph is ready
     */
    async whenReadyAsync(timeStep = 16, maxTimeout = 10000) {
        let firstNotReadyTask = null;
        // TODO: to be removed when build() is removed
        await new Promise((resolve) => {
            const checkBuilt = () => {
                if (this._built) {
                    resolve(void 0);
                    return;
                }
                setTimeout(checkBuilt, 16);
            };
            checkBuilt();
        });
        // END TODO
        return await new Promise((resolve, reject) => {
            this._whenReadyAsyncCancel = _RetryWithInterval(() => {
                let ready = this._renderContext._isReady();
                for (const task of this._tasks) {
                    const taskIsReady = task.isReady();
                    if (!taskIsReady && !firstNotReadyTask) {
                        firstNotReadyTask = task;
                    }
                    ready && (ready = taskIsReady);
                }
                return ready;
            }, () => {
                this._whenReadyAsyncCancel = null;
                resolve();
            }, (err, isTimeout) => {
                this._whenReadyAsyncCancel = null;
                if (!isTimeout) {
                    Logger.Error("FrameGraph: An unexpected error occurred while waiting for the frame graph to be ready.");
                    if (err) {
                        Logger.Error(err);
                        if (err.stack) {
                            Logger.Error(err.stack);
                        }
                    }
                }
                else {
                    Logger.Error(`FrameGraph: Timeout while waiting for the frame graph to be ready.${firstNotReadyTask ? ` First task not ready: ${firstNotReadyTask.name}` : ""}`);
                    if (err) {
                        Logger.Error(err);
                    }
                }
                reject(new Error(err));
            }, timeStep, maxTimeout);
        });
    }
    /**
     * Executes the frame graph.
     */
    execute() {
        if (this.pausedExecution) {
            return;
        }
        this._renderContext.bindRenderTarget();
        this.textureManager._updateHistoryTextures();
        for (const task of this._tasks) {
            task._execute();
        }
        this._renderContext.bindRenderTarget(undefined, undefined, true); // restore default framebuffer
    }
    /**
     * Clears the frame graph (remove the tasks and release the textures).
     * The frame graph can be built again after this method is called.
     */
    clear() {
        this._whenReadyAsyncCancel?.();
        this._whenReadyAsyncCancel = null;
        for (const task of this._tasks) {
            task._reset();
        }
        this._tasks.length = 0;
        this.textureManager._releaseTextures();
        this._currentProcessedTask = null;
    }
    /**
     * Disposes the frame graph
     */
    dispose() {
        this._whenReadyAsyncCancel?.();
        this._whenReadyAsyncCancel = null;
        this.clear();
        this.textureManager._dispose();
        this._renderContext._dispose();
        this._scene.removeFrameGraph(this);
    }
}
//# sourceMappingURL=frameGraph.js.map