UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

268 lines (250 loc) 9.24 kB
import {CoreFeaturesController} from './../../../../../core/FeaturesController'; import {Constructor} from '../../../../../types/GlobalTypes'; import {BaseNodeType} from '../../../_Base'; import {ParamConfig} from '../../../utils/params/ParamsConfig'; import {RootManagerNode} from '../../Root'; import {PolyScene} from '../../../../scene/PolyScene'; import {setToArray} from '../../../../../core/SetUtils'; import {Poly} from '../../../../Poly'; import {arrayUniq, arrayCompact, arrayToSet} from '../../../../../core/ArrayUtils'; import {BaseObjNodeType} from '../../../obj/_Base'; import {GeoObjNode} from '../../../obj/Geo'; class NodeGroup { public readonly totalCount: number; private _processed: Set<BaseNodeType>; private _remaining: Set<BaseNodeType> = new Set(); constructor(public readonly nodes: BaseNodeType[]) { if (CoreFeaturesController.debugLoadProgress()) { console.log(nodes); } this.totalCount = nodes.length; this._processed = new Set(); arrayToSet(nodes, this._remaining); } markNodeAsProcessed(node: BaseNodeType) { this._processed.add(node); this._remaining.delete(node); if (CoreFeaturesController.debugLoadProgress()) { console.log('markNodeAsProcessed', node.path(), { processed: setToArray(this._processed, []).map((n) => n.path()), remaining: setToArray(this._remaining, []).map((n) => n.path()), }); } } isNodeProcessed(node: BaseNodeType) { return this._processed.has(node); } processedCount(): number { return this._processed.size; } } interface NodeGroups { toCook: NodeGroup; sopGroupToUpdate: NodeGroup; } export interface OnProgressArguments { scene: PolyScene; triggerNode?: BaseNodeType; groups: NodeGroups; } export type OnProgressUpdateCallback = (progressRatio: number, args: OnProgressArguments) => void; export function RootLoadProgressParamConfig<TBase extends Constructor>(Base: TBase) { return class Mixin extends Base { /** @param when the scene loads, nodes that match the mask will update the progress bar as they cook */ nodesMask = ParamConfig.STRING('*/image* */envMap*', { cook: false, separatorBefore: true, objectMask: false, // do not use objectMask, since it is a node mask, not object }); /** @param prints which nodes match the mask in the console */ printNodes = ParamConfig.BUTTON(null, { cook: false, callback: (node: BaseNodeType) => { RootLoadProgressController.PARAM_CALLBACK_printResolve(node as RootManagerNode); }, // objectMask: false // do not use objectMask, since it is a node mask, not object }); }; } export class RootLoadProgressController { constructor(protected node: RootManagerNode) {} static async PARAM_CALLBACK_printResolve(node: RootManagerNode) { const nodes = await node.loadProgress.resolvedNodes(); console.log(nodes); const nodePaths = nodes.map((node) => node.path()).sort(); console.log(nodePaths); } async resolvedNodes() { const param = this.node.p.nodesMask; if (param.isDirty()) { await param.compute(); } const mask = param.value; const scene = this.node.scene(); const nodes = scene.nodesController.nodesFromMask(mask || ''); const nodeDisplayNodes = await this._loadDisplayNodes(); const uniqNodes: BaseNodeType[] = []; arrayUniq(nodes.concat(nodeDisplayNodes), uniqNodes); return uniqNodes; } private async _loadDisplayNodes() { const scene = this.node.scene(); // we also need to get the cameras at the obj level const cameraNodeTypes = Poly.camerasRegister.registeredNodeTypes(); const cameraNodes = cameraNodeTypes.map((type) => scene.nodesByType(type)).flat(); // and we also need to get the displayed nodes const displayNodes = this._displayNodes(); const nodes = cameraNodes.concat(displayNodes); const cameraCreatorNode = await this.cameraCreatorNode(); if (cameraCreatorNode) { nodes.push(cameraCreatorNode); } const uniqNodes: BaseNodeType[] = []; arrayUniq(nodes, uniqNodes); return uniqNodes; } private _displayNodes() { const objNodesWithDisplayNodeController = this._objectNodesWithDisplayNodeController() as GeoObjNode[]; const displayNodes: BaseNodeType[] = []; arrayCompact( objNodesWithDisplayNodeController.map((node) => node.displayNodeController.firstNonBypassedDisplayNode()), displayNodes ); return displayNodes; } private _objectNodesWithDisplayNodeController() { const scene = this.node.scene(); const objNodesWithDisplayNodeController = scene .root() .children() .filter((node) => (node as BaseObjNodeType).displayNodeController != null) .filter((node) => node.flags?.display?.active()); return objNodesWithDisplayNodeController; } // private _getNodesWithSopGroup() { // return this._displayNodes().filter((node) => (node as GeoObjNode).childrenDisplayController != null); // } public cameraCreatorNode() { return this.node.mainCameraController.cameraCreatorNode(); } private _nodeGroups: NodeGroups | undefined; // private _toCook: NodeGroup | undefined; // private _sopGroupToUpdate: NodeGroup | undefined; private _onProgressUpdateCallback: OnProgressUpdateCallback | undefined; private _runCallback(progress: number, nodeTrigger?: BaseNodeType) { this._debug2('_runCallback', {progress, nodeTrigger}); if (!(this._onProgressUpdateCallback && this._nodeGroups)) { return; } this._debug2('_onProgressUpdateCallback', this._nodeGroups); this._onProgressUpdateCallback(progress, { scene: this.node.scene(), triggerNode: undefined, groups: this._nodeGroups, }); } private _updateProgressAndRunCallback(nodeTrigger: BaseNodeType) { if (!(this._onProgressUpdateCallback && this._nodeGroups)) { return; } const totalNodesCount = this._nodeGroups.toCook.totalCount + this._nodeGroups.sopGroupToUpdate.totalCount; const processedNodesCount = this._nodeGroups.toCook.processedCount() + this._nodeGroups.sopGroupToUpdate.processedCount(); const progress = processedNodesCount / totalNodesCount; this._runCallback(progress, nodeTrigger); } async watchNodesProgress(callback: OnProgressUpdateCallback) { this._onProgressUpdateCallback = callback; const resolvedNodes = await this.resolvedNodes(); const nodesToCook = resolvedNodes.filter((node) => node.isDirty()); this._debug({nodesToCook}); const nodesToUpdateSopGroup = this._objectNodesWithDisplayNodeController() .filter((node) => { const displayNode = (node as GeoObjNode).displayNodeController.displayNode(); return displayNode != null && !displayNode.flags.bypass?.active(); }) .filter((node) => node.isDirty()); this._debug({nodesToUpdateSopGroup}); this._nodeGroups = { toCook: new NodeGroup(nodesToCook), sopGroupToUpdate: new NodeGroup(nodesToUpdateSopGroup), }; const totalNodesCount = this._nodeGroups.toCook.totalCount + this._nodeGroups.sopGroupToUpdate.totalCount; this._debug({totalNodesCount}); if (totalNodesCount == 0) { this._runCallback(1); return; } this._watchNodesWithSopGroup(); this._watchNodesToCook(); } private async _watchNodesToCook() { const nodesGroup = this._nodeGroups?.toCook; if (!nodesGroup) { return; } const callbackName = 'RootLoadProgressController'; const onNodeCooked = (node: BaseNodeType) => { if (!nodesGroup.isNodeProcessed(node)) { nodesGroup.markNodeAsProcessed(node); this._updateProgressAndRunCallback(node); node.cookController.deregisterOnCookEnd(callbackName); } }; for (const node of nodesGroup.nodes) { node.cookController.registerOnCookEnd(callbackName, () => { this._debug2('nodeToCook - completed', node.path()); onNodeCooked(node); }); // we force nodes to compute // in case they do not have a display flag on, or are not connected // as it would get the loading stuck this._debug2('nodeToCook - start', node.path()); node.compute(); } } private _watchNodesWithSopGroup() { const nodesGroup = this._nodeGroups?.sopGroupToUpdate; if (!nodesGroup) { return; } const callbackName = 'RootLoadProgressController'; const onNodeCooked = (node: BaseNodeType) => { if (!nodesGroup.isNodeProcessed(node)) { nodesGroup.markNodeAsProcessed(node); this._updateProgressAndRunCallback(node); const childrenDisplayController = (node as GeoObjNode).childrenDisplayController; childrenDisplayController.deregisterOnSopGroupUpdated(callbackName); } }; for (const node of nodesGroup.nodes) { const childrenDisplayController = (node as GeoObjNode).childrenDisplayController; this._debug2('nodeWithSopGroup - watch', node.path()); childrenDisplayController.registerOnSopGroupUpdated(callbackName, () => { this._debug2('nodeWithSopGroup - completed', node.path()); onNodeCooked(node); }); } } protected static debugActive(): boolean { return CoreFeaturesController.debugLoadProgress(); } static debug(arg0: any) { if (!this.debugActive()) { return; } console.log(arg0); } static debug2(arg0: any, arg1: any) { if (!this.debugActive()) { return; } console.log(arg0, arg1); } protected _debug(arg0: any) { RootLoadProgressController.debug(arg0); } protected _debug2(arg0: any, arg1: any) { RootLoadProgressController.debug2(arg0, arg1); } }