@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
144 lines (132 loc) • 4.76 kB
text/typescript
import {ContainableMap} from './../../../../src/engine/containers/utils/ContainableMap';
import {ContainableClassMap} from './../../containers/utils/ContainableMap';
import {TypedNode} from '../_Base';
import {NodeContext} from '../../poly/NodeContext';
import {ContainerMap, ContainerClassMap} from '../../containers/utils/ContainerMap';
type NodeContainerControllerCallback<NC extends NodeContext> = (container: ContainerMap[NC] | undefined) => void;
export class TypedContainerController<NC extends NodeContext> {
private _callbacks: NodeContainerControllerCallback<NC>[] = [];
private _callbacksTmp: NodeContainerControllerCallback<NC>[] = [];
protected _container: ContainerMap[NC];
constructor(protected node: TypedNode<NC, any>) {
this._container = this._createContainer();
}
container() {
return this._container;
}
private _createContainer() {
const ContainerClass = ContainerClassMap[this.node.context()];
return new ContainerClass(this.node as any) as ContainerMap[NC];
}
private _createContainerWithContent() {
const container = this._createContainer();
const ContentClass = ContainableClassMap[this.node.context()];
const content = new ContentClass() as ContainableMap[NC];
container.set_content(<never>content);
return container;
}
firstNonBypassedNode() {
if (this.node.flags?.bypass?.active()) {
const inputNode = (<unknown>this.node.io.inputs.input(0)) as TypedNode<NC, any>;
return inputNode;
} else {
return this.node;
}
}
containerUnlessBypassed(): ContainerMap[NC] | undefined {
if (this.node.flags?.bypass?.active()) {
this.node.states.error.clear();
const inputNode = (<unknown>this.node.io.inputs.input(0)) as TypedNode<NC, any>;
if (inputNode) {
return inputNode.containerController.containerUnlessBypassed();
} else {
return this._createContainerWithContent();
}
} else {
return this.container();
}
}
async compute(): Promise<ContainerMap[NC]> {
if (this.node.disposed()) {
console.warn('.compute() requested from a disposed node', this.node);
}
if (this.node.flags?.bypass?.active()) {
this.node.states.error.clear();
const inputNode = (<unknown>this.node.io.inputs.input(0)) as TypedNode<NC, any>;
if (inputNode) {
const container = (await this.requestInputContainer(0)) || this._container;
this.node.cookController.endCook();
return container;
} else {
return this._createContainerWithContent();
}
}
if (this.node.isDirty()) {
return new Promise((resolve, reject) => {
this._callbacks.push(resolve as () => ContainerMap[NC]);
if (this.node.flags?.bypass?.active()) {
throw 'we should not be here';
}
this.node.cookController.cookMain();
});
}
return this._container;
}
// async requestContainerTEST(): Promise<ContainerMap[NC]> {
// if (this.node.flags?.bypass?.active()) {
// const container = await this.requestInputContainer(0);
// return container || this._container;
// }
// if (this.node.isDirty()) {
// await this.node.cookController.cook_main();
// }
// return this._container;
// }
// TODO: should I merge this into the method above?
// private process_container_request() {
// if (this.node.flags?.bypass?.active()) {
// const input_index = 0;
// this.requestInputContainer(input_index).then((container) => {
// this.node.removeDirtyState();
// if (container) {
// this.notify_requesters(container);
// } else {
// this.node.states.error.set('input invalid');
// }
// });
// } else {
// if (this.node.isDirty()) {
// this.node.cookController.cook_main();
// } else {
// this.notify_requesters();
// }
// }
// }
async requestInputContainer(inputIndex: number) {
const inputNode = (<unknown>this.node.io.inputs.input(inputIndex)) as TypedNode<NC, any>;
if (inputNode) {
return await inputNode.compute();
} else {
this.node.states.error.set(`input ${inputIndex} required`);
this.notifyRequesters();
return null;
}
}
notifyRequesters(container?: ContainerMap[NC]) {
// make a copy of the callbacks first,
// to ensure that new ones are not added to this list
// in side effects from those callbacks
// (the test suite for the File SOP is a good test for this)
this._callbacksTmp = this._callbacks.slice(); // clone
this._callbacks.splice(0, this._callbacks.length); // empty
if (!container) {
container = this.node.containerController.container();
}
let callback: NodeContainerControllerCallback<NC> | undefined;
while ((callback = this._callbacksTmp.pop())) {
callback(container);
}
this.node.scene().cookController.removeNode(this.node);
}
}
export class BaseContainerController extends TypedContainerController<any> {}