UNPKG

@polygonjs/polygonjs

Version:

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

394 lines (367 loc) 12.1 kB
import {CoreWalker} from '../../core/Walker'; import {CoreGraphNode} from '../../core/graph/CoreGraphNode'; import {BaseNodeType} from '../nodes/_Base'; import {OptionsController} from './utils/OptionsController'; import {ExpressionController} from './utils/ExpressionController'; import {EmitController} from './utils/EmitController'; import {CoreParamSerializer} from './utils/CoreParamSerializer'; import {ParamStatesController} from './utils/StatesController'; import {TypedMultipleParam} from './_Multiple'; import {FloatParam} from './Float'; import {ParamType} from '../poly/ParamType'; import {ParamEvent} from '../poly/ParamEvent'; import {PolyScene} from '../scene/PolyScene'; import {ParamInitValuesTypeMap} from '../params/types/ParamInitValuesTypeMap'; import {ParamValuesTypeMap} from '../params/types/ParamValuesTypeMap'; import { ParamValueSerializedTypeMap, ParamValuePreConversionSerializedTypeMap, } from '../params/types/ParamValueSerializedTypeMap'; import {ParamInitValueSerializedTypeMap} from './types/ParamInitValueSerializedTypeMap'; import {MethodDependency} from '../expressions/MethodDependency'; import {Poly} from '../Poly'; import {arrayCopy} from '../../core/ArrayUtils'; type ComputeCallback = (value: void) => void; const TYPED_PARAM_DEFAULT_COMPONENT_NAMES: Readonly<string[]> = []; type OnDisposeCallback = () => void; export interface ParamOptions<T extends ParamType> { serializerClass?: typeof CoreParamSerializer<T>; } export abstract class TypedParam<T extends ParamType> extends CoreGraphNode { protected _default_value!: ParamInitValuesTypeMap[T]; protected _raw_input!: ParamInitValuesTypeMap[T]; protected _value!: ParamValuesTypeMap[T]; protected _node: BaseNodeType | undefined; protected _parent_param: TypedMultipleParam<any> | undefined; protected _components: FloatParam[] | undefined; protected _computeResolves: ComputeCallback[] | undefined; private _options: OptionsController = new OptionsController(this); get options(): OptionsController { return (this._options = this._options || new OptionsController(this)); } private _emitController: EmitController = new EmitController(this); get emitController(): EmitController { return (this._emitController = this._emitController || new EmitController(this)); } protected _expression_controller: ExpressionController<T> | undefined; get expressionController(): ExpressionController<T> | undefined { return this._expression_controller; } expressionParsedAsString() { return false; } protected _serializer: CoreParamSerializer<T> | undefined; get serializer(): CoreParamSerializer<T> | undefined { return this._serializer; } private _states: ParamStatesController | undefined; get states(): ParamStatesController { return (this._states = this._states || new ParamStatesController(this)); } // private _ui_data: UIData | undefined; // get ui_data(): UIData { // return (this._ui_data = this._ui_data || new UIData(this.scene, this)); // } constructor(scene: PolyScene, node: BaseNodeType, options: ParamOptions<T>) { super(scene, 'BaseParam'); if (options.serializerClass) { this._serializer = new options.serializerClass(this); } this._node = node; this._initializeParam(); } override dispose() { if (this.expressionController && this.hasExpression()) { this.set(this.rawInputSerialized()); } const _tmpCoreGraphNodes: CoreGraphNode[] = []; // if any direct predecessor is a MethodDependency, // it must be disposed here const predecessors = this.graphPredecessors(); if (predecessors) { arrayCopy(predecessors, _tmpCoreGraphNodes); for (const predecessor of _tmpCoreGraphNodes) { if (predecessor instanceof MethodDependency) { predecessor.dispose(); } } } const successors = this.graphSuccessors(); if (successors) { arrayCopy(successors, _tmpCoreGraphNodes); for (const successor of _tmpCoreGraphNodes) { if (successor instanceof TypedParam) { const input = successor.rawInputSerialized(); successor.set(successor.defaultValue()); successor.set(input); } else { successor.setDirty(); } } } this.scene().missingExpressionReferencesController.deregisterParam(this); this._expression_controller?.dispose(); super.dispose(); this._options?.dispose(); this._node = undefined; this._parent_param = undefined; this._runOnDisposeCallbacks(); } protected _initializeParam() {} postOptionsInitialize() {} // // this.addPostDirtyHook(this._remove_node_param_cache.bind(this)) // } // initialize() { // this.initComponents(); // // this.init_expression() // // this._init_ui_data() // } // accepts_visitor<T extends ParamVisitor>(visitor: T): ReturnType<T['visit_param']> { // return visitor.visit_param(this); // } // // init_expression() {} // type static type(): ParamType { return ParamType.FLOAT; // adding a type here, but just to not have a compile error } type(): T { return (this.constructor as typeof BaseParamClass).type() as T; } isNumeric(): boolean { return false; } // name override setName(name: string) { super.setName(name); } get value(): ParamValuesTypeMap[T] { return this._value; } abstract defaultValueSerialized(): ParamInitValueSerializedTypeMap[T]; abstract rawInputSerialized(): ParamInitValueSerializedTypeMap[T]; abstract valueSerialized(): ParamValueSerializedTypeMap[T]; copyValue(param: BaseParamType) { if (param.type() == this.type()) { this._copyValue(param as TypedParam<T>); } else { console.warn(`cannot copy value from ${param.type()} to ${this.type()}`); } } protected _copyValue(param: TypedParam<T>) { throw 'abstract method param._copy_value'; } valuePreConversionSerialized(): ParamValuePreConversionSerializedTypeMap[T] { return undefined as never; } convert(rawVal: any): ParamValuesTypeMap[T] | null { return null; } static areRawInputEqual(val1: any, val2: any) { return false; } isRawInputEqual(other_raw_input: ParamInitValuesTypeMap[T]) { return (this.constructor as any).areRawInputEqual(this._raw_input, other_raw_input); } isDefaultValueEqual(other_default_value: ParamInitValuesTypeMap[T]) { return (this.constructor as any).areRawInputEqual(this._default_value, other_default_value); } static areValuesEqual(val1: any, val2: any) { return false; } isValueEqual(other_val: ParamValuesTypeMap[T]) { return (this.constructor as any).areValuesEqual(this.value, other_val); } protected _cloneRawInput(raw_input: ParamInitValuesTypeMap[T]): ParamInitValuesTypeMap[T] { return raw_input; } set(raw_input: ParamInitValuesTypeMap[T]): void { this._raw_input = this._cloneRawInput(this._prefilterInvalidRawInput(raw_input)); this.emitController.emit(ParamEvent.RAW_INPUT_UPDATED); this.processRawInput(); } protected _prefilterInvalidRawInput(raw_input: any): ParamInitValuesTypeMap[T] { return raw_input as ParamInitValuesTypeMap[T]; } defaultValue() { return this._default_value; } isDefault(): boolean { return this._raw_input == this._default_value; } rawInput() { return this._raw_input; } protected processRawInput() {} private _isComputing: boolean = false; isComputing() { return this._isComputing; } async compute(): Promise<void> { if (this.scene().loadingController.isLoading()) { // TODO: // test that node path created in material builders // do not compute on load, // But they should still be able to compute an expression // and therefore .compute() is still necessary Poly.warn(`param attempt to compute ${this.path()} while scene is loading`); } if (this.isDirty()) { if (!this._isComputing) { this._isComputing = true; await this.processComputation(); this._isComputing = false; if (this._computeResolves) { const resolves = [...this._computeResolves]; this._computeResolves = undefined; for (const resolve of resolves) { resolve(); } } } else { return new Promise((resolve, reject) => { this._computeResolves = this._computeResolves || []; this._computeResolves.push(resolve); }); } } } protected async processComputation(): Promise<void> {} // set_default_value(default_value: ParamValuesTypeMap[T]) { // this._default_value = default_value; // } setInitValue(init_value: ParamInitValuesTypeMap[T]) { this._default_value = this._cloneRawInput(this._prefilterInvalidRawInput(init_value)); // this._raw_input = this._cloneRawInput(init_value); // if (this.isMultiple()) { // this.initComponents(); // } // this.set(init_value); } // eval_p(): Promise<ParamValuesTypeMap[T]> { // return new Promise((resolve, reject) => { // resolve(); // }); // } // node _setupNodeDependencies(node: BaseNodeType | null) { if (!node) { if (this._node) { this._node.params.params_node?.removeGraphInput(this); } } else { // allow callbacks after the value is set, // so that the param does not trigger the node to recompute // before all params are added this.options.allowCallback(); if (!this.parentParam()) { if (this.options.makesNodeDirtyWhenDirty()) { node.params.params_node?.addGraphInput(this, false); } else { // if the param does not make the node cook when dirty, // we still want it to run its attached callback when dirty this.dirtyController.addPostDirtyHook('run callback', async () => { await this.compute(); await this.options.executeCallback(); }); } } } if (this.components) { for (const c of this.components) { c._setupNodeDependencies(node); } } } get node() { return this._node!; } parent() { return this.node; } // hierarchy set_parent_param(param: TypedMultipleParam<any>) { param.addGraphInput(this, false); this._parent_param = param; } parentParam(): TypedMultipleParam<any> | undefined { return this._parent_param; } has_parent_param(): boolean { return this._parent_param != null; } path(): string { return this.node?.path() + '/' + this.name(); } pathRelativeTo(node: BaseNodeType): string { if (!this._node) { return this.name(); } const nodeRelativePath = CoreWalker.relativePath(node, this._node); if (nodeRelativePath.length > 0) { return CoreWalker.sanitizePath(`${nodeRelativePath}${CoreWalker.SEPARATOR}${this.name()}`); } else { return this.name(); } } // emit emit(eventName: ParamEvent): void { if (this.emitController.emitAllowed()) { this.emitController.incrementCount(eventName); this.scene().dispatchController.dispatch(this, eventName); } } // multiple get components() { return this._components; } componentNames(): Readonly<string[]> { return TYPED_PARAM_DEFAULT_COMPONENT_NAMES; } isMultiple(): boolean { return this.componentNames().length > 0; } initComponents() {} hasExpression(): boolean { return this.expressionController != null && this.expressionController.active(); // use this._expression_controller to avoid creating it } // serialize toJSON() { if (!this._serializer) { return; } return this._serializer.toJSON(); } // dispose callbacks private _onDisposeCallbacks: Set<OnDisposeCallback> | undefined; onDispose(callback: OnDisposeCallback) { this._onDisposeCallbacks = this._onDisposeCallbacks || new Set(); this._onDisposeCallbacks.add(callback); } deregisterOnDispose(callback: OnDisposeCallback) { if (this._onDisposeCallbacks) { this._onDisposeCallbacks.delete(callback); } } private _runOnDisposeCallbacks() { if (this._onDisposeCallbacks) { this._onDisposeCallbacks.forEach((callback) => { callback(); }); this._onDisposeCallbacks.clear(); this._onDisposeCallbacks = undefined; } } } export type BaseParamType = TypedParam<ParamType>; export class BaseParamClass extends TypedParam<ParamType> { defaultValueSerialized() { return 'BaseParamClass.defaultValueSerialized overriden'; } rawInputSerialized() { return 'BaseParamClass.rawInputSerialized overriden'; } valueSerialized() { return 'BaseParamClass.valueSerialized overriden'; } }