UNPKG

@polygonjs/polygonjs

Version:

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

506 lines (466 loc) 18.4 kB
import {BaseJsNodeType} from '../../_Base'; import {TypedNodeTraverser} from '../../../utils/shaders/NodeTraverser'; import {pushOnArrayAtEntry} from '../../../../../core/MapUtils'; import {JsFunctionName} from '../../../utils/shaders/ShaderName'; import {JsDefinitionType, BaseJsDefinition, JsDefinitionTypeMap, TypedJsDefinition} from '../../utils/JsDefinition'; import {TypedJsDefinitionCollection} from '../../utils/JsDefinitionCollection'; import {ParamConfigsController} from '../../../../nodes/utils/code/controllers/ParamConfigsController'; import {JsLinesCollectionController} from './JsLinesCollectionController'; import {CodeFormatter} from './CodeFormatter'; import {LineType} from './LineType'; import {JsParamConfig} from './JsParamConfig'; import {ParamType} from '../../../../poly/ParamType'; import {NodeContext} from '../../../../poly/NodeContext'; import {CoreGraphNodeId} from '../../../../../core/graph/CoreGraph'; import {arrayUniq} from '../../../../../core/ArrayUtils'; import {BaseJsShaderAssembler} from '../assemblers/_Base'; // import {sanitizeName} from '../../../../../core/String'; import {triggerableMethodCalls} from '../assemblers/actor/ActorAssemblerUtils'; import {setToArray} from '../../../../../core/SetUtils'; import {ActorBuilderNode} from '../../../../scene/utils/ActorsManager'; import {JsType} from '../../../../poly/registers/nodes/types/Js'; import {CodeJsNode} from '../../Code'; // import {connectedTriggerableNodes} from '../assemblers/actor/ActorAssemblerUtils'; type RootNodesForJsFunctionMethod = (shader_name: JsFunctionName, rootNodes: BaseJsNodeType[]) => BaseJsNodeType[]; // let nextId = 1; export interface CodeBuilderSetCodeLinesOptions { otherFragmentShaderCollectionController?: JsLinesCollectionController; actor: { triggeringNodes: Set<BaseJsNodeType>; triggerableNodes: Set<BaseJsNodeType>; functionNode: ActorBuilderNode; // triggerNodesByType: Map<string, Set<BaseJsNodeType>>; }; } export class JsCodeBuilder { // private _id = (nextId += 1); private _param_configs_controller: ParamConfigsController<JsParamConfig<ParamType>> = new ParamConfigsController(); private _param_configs_set_allowed: boolean = true; private _shadersCollectionController: JsLinesCollectionController | undefined; private _lines: Map<JsFunctionName, Map<LineType, string[]>> = new Map(); // _function_declared: Map<ShaderName, Map<string, boolean>> = new Map(); constructor( private _nodeTraverser: TypedNodeTraverser<NodeContext.JS>, private _rootNodesByShaderName: RootNodesForJsFunctionMethod, private _assembler: BaseJsShaderAssembler ) {} nodeTraverser() { return this._nodeTraverser; } shaderNames() { return this._nodeTraverser.shaderNames(); } buildFromNodes( rootNodes: BaseJsNodeType[], paramNodes: BaseJsNodeType[], setCodeLinesOptions?: CodeBuilderSetCodeLinesOptions ) { this._nodeTraverser.traverse(rootNodes); const nodesByShaderName: Map<JsFunctionName, BaseJsNodeType[]> = new Map(); for (const shaderName of this.shaderNames()) { const nodes = this._nodeTraverser.nodesForShaderName(shaderName); nodesByShaderName.set(shaderName, nodes); } const sortedNodes = this._nodeTraverser.sortedNodes(); for (const shaderName of this.shaderNames()) { const rootNodesForShader = this._rootNodesByShaderName(shaderName, rootNodes); for (const rootNode of rootNodesForShader) { pushOnArrayAtEntry(nodesByShaderName, shaderName, rootNode); } } // ensure nodes are not added if already present const sorted_node_ids: Map<CoreGraphNodeId, boolean> = new Map(); for (const node of sortedNodes) { sorted_node_ids.set(node.graphNodeId(), true); } for (const rootNode of rootNodes) { if (!sorted_node_ids.get(rootNode.graphNodeId())) { sortedNodes.push(rootNode); sorted_node_ids.set(rootNode.graphNodeId(), true); } } for (const node of sortedNodes) { node.reset_code(); } for (const node of paramNodes) { node.reset_code(); } // compile code nodes // compiling code nodes here cannot work, // as this may recreate new connections on the node, // which will in turn retrigger this function, // which easily becomes an infinite loop const nodesToBeComputed = [...sortedNodes]; if (setCodeLinesOptions?.actor) { const {triggeringNodes, triggerableNodes} = setCodeLinesOptions.actor; nodesToBeComputed.push(...triggeringNodes, ...triggerableNodes); } const codeNodes = nodesToBeComputed.filter((n) => n.type() == JsType.CODE) as CodeJsNode[]; for (const node of codeNodes) { if (!node.compiled()) { // try { // node.functionNode()?.dirtyController.setForbiddenTriggerNodes([node]); const functionNode = node.functionNode(); if (functionNode) { node.setForbiddenTriggerNodes(functionNode); } node.compile({triggerFunctionNode: false}); node.clearForbiddenTriggerNodes(); // node.functionNode()?.dirtyController.setForbiddenTriggerNodes([]); if (node.states.error.active() || !node.compiled()) { const message = `failed to generated code (see node ${node.path()})`; // node.functionNode()?.states.error.set(message); // this._setCodeLines([], setCodeLinesOptions); throw new Error(message); // return; } // else { // console.log('OK'); //} // } catch (err) { // console.log('NOT OK', node.path(), node.states.error.active()); // node.states.error.set(`failed to generated code`); // node.functionNode()?.states.error.set(`node ${node.path()} failed to generated code`); // } } } // const param_promises = sorted_nodes.map(node=>{ // return node.eval_all_params() // }) // await Promise.all(param_promises) this._shadersCollectionController = new JsLinesCollectionController( this.shaderNames(), this.shaderNames()[0], this._assembler ); this.reset(); for (const shaderName of this.shaderNames()) { let notUniqNodes = nodesByShaderName.get(shaderName) || []; const nodes: BaseJsNodeType[] = []; arrayUniq(notUniqNodes, nodes); this._shadersCollectionController.setCurrentShaderName(shaderName); if (nodes) { for (const node of nodes) { node.setLines(this._shadersCollectionController); } } if (setCodeLinesOptions?.actor) { const {triggeringNodes, triggerableNodes} = setCodeLinesOptions.actor; // triggering nodes for (const triggeringNode of triggeringNodes) { // const currentTriggerableNodes = new Set<BaseJsNodeType>(); // connectedTriggerableNodes({ // triggeringNodes: new Set([triggeringNode]), // triggerableNodes: currentTriggerableNodes, // recursive: false, // }); // const triggerableMethodNames = setToArray(currentTriggerableNodes).map((n) => // nodeMethodName(n) // ); // const triggerableMethodCalls = triggerableMethodNames // .map((methodName) => `this.${methodName}(${0})`) // .join('\n'); const _triggerableMethodCalls = triggerableMethodCalls(triggeringNode); triggeringNode.setTriggeringLines(this._shadersCollectionController, _triggerableMethodCalls); } // triggerable nodes for (const triggerableNode of triggerableNodes) { try { triggerableNode.setTriggerableLines(this._shadersCollectionController); } catch (err) { triggerableNode.states.error.set(`failed to generate code`); throw new Error(`node ${triggerableNode.path()} failed to generated code`); } } } // // // create triggerable methods // // // if (setCodeLinesOptions) { // const shadersCollectionController = this._shadersCollectionController; // shadersCollectionController.withAllowActionLines(() => { // const {functionNode, triggerNodesByType} = setCodeLinesOptions; // const nodeMethodName = (node: BaseJsNodeType) => // sanitizeName(node.path().replace(functionNode.path(), '')); // // const triggerableFunctionLines: string[] = []; // triggerNodesByType.forEach((triggerNodes, nodeType) => { // const triggerableNodes: Set<BaseJsNodeType> = new Set(); // connectedTriggerableNodes({triggerNodes, triggerableNodes, recursive: true}); // for (let node of triggerableNodes) { // // const shadersCollectionController = new ShadersCollectionController( // // this.shaderNames(), // // this.shaderNames()[0], // // this // // ); // // shadersCollectionController.setAllowActionLines(true); // node.setLines(shadersCollectionController); // const bodyLines = shadersCollectionController.bodyLines(ShaderName.FRAGMENT, node); // console.log(bodyLines); // if (bodyLines) { // const methodName = nodeMethodName(node); // const wrappedLines = `${methodName}(){ // ${bodyLines.join('\n')} // }`; // // triggerableFunctionLines.push(wrappedLines); // console.log({wrappedLines}); // shadersCollectionController._addBodyLines(node, [wrappedLines], shaderName); // } // } // }); // }); // } // trigger node lines // const triggerNodes = setCodeLinesOptions?.actor.triggerNodes; // if (triggerNodes) { // for (let triggerNode of triggerNodes) { // triggerNode.addConstructorInitFunctionLines(this._shadersCollectionController); // } // } } // set param configs if (this._param_configs_set_allowed) { for (const param_node of paramNodes) { try { param_node.states.error.clear(); param_node.setParamConfigs(); } catch (err) { const message: string = (err as any).message || 'failed to create spare param'; param_node.states.error.set(message); throw new Error(`${param_node.name()} cannot create spare parameter`); } } this.setParamConfigs(paramNodes); } // finalize this._setCodeLines(sortedNodes, setCodeLinesOptions); } shadersCollectionController() { return this._shadersCollectionController; } disallow_new_param_configs() { this._param_configs_set_allowed = false; } allow_new_param_configs() { this._param_configs_set_allowed = true; } private reset() { for (let shader_name of this.shaderNames()) { const lines_map = new Map(); // for (let line_type of LINE_TYPES) { // lines_map.set(line_type, []); // } this._lines.set(shader_name, lines_map); // this._function_declared.set(shader_name, new Map()); } } param_configs() { return this._param_configs_controller.list() || []; } lines(shader_name: JsFunctionName, line_type: LineType) { return this._lines.get(shader_name)?.get(line_type) || []; } all_lines() { return this._lines; } setParamConfigs(nodes: BaseJsNodeType[]) { this._param_configs_controller.reset(); for (const node of nodes) { const param_configs = node.param_configs(); if (param_configs) { for (const param_config of param_configs) { this._param_configs_controller.push(param_config); } } } } private _setCodeLines(nodes: BaseJsNodeType[], options?: CodeBuilderSetCodeLinesOptions) { for (const shaderName of this.shaderNames()) { const additionalDefinitions: BaseJsDefinition[] = []; // if (shaderName == JsFunctionName.MAIN) { if (this._shadersCollectionController && options && options.otherFragmentShaderCollectionController) { // for (const shaderName of options.otherShadersCollectionController.shaderNames()) { // this._linesControllerByShaderName.set(shaderName, new LinesController(shaderName)); options.otherFragmentShaderCollectionController.traverseDefinitions( shaderName, (definition: BaseJsDefinition) => { additionalDefinitions.push(definition); } ); // } } // } this._addCodeLines(nodes, shaderName, additionalDefinitions, options); } } private _addCodeLines( nodes: BaseJsNodeType[], shaderName: JsFunctionName, additionalDefinitions?: BaseJsDefinition[], options?: CodeBuilderSetCodeLinesOptions ) { // this.addDefinitions(nodes, shaderName, JsDefinitionType.PRECISION, LineType.DEFINE, additionalDefinitions); // this.addDefinitions( // nodes, // shaderName, // JsDefinitionType.FUNCTION, // LineType.FUNCTION_DECLARATION, // additionalDefinitions // ); // this.addDefinitions(nodes, shaderName, JsDefinitionType.UNIFORM, LineType.DEFINE, additionalDefinitions); // this.addDefinitions(nodes, shaderName, JsDefinitionType.VARYING, LineType.DEFINE, additionalDefinitions); // this.addDefinitions(nodes, shaderName, JsDefinitionType.ATTRIBUTE, LineType.DEFINE, additionalDefinitions); // this.addDefinitions(nodes, shaderName, JsDefinitionType.INIT, LineType.DEFINE, additionalDefinitions); const allNodes = options && options.actor ? nodes .concat(setToArray(options.actor.triggeringNodes, [])) .concat(setToArray(options.actor.triggerableNodes, [])) : nodes; this.addDefinitions(nodes, shaderName, JsDefinitionType.LOCAL_FUNCTION, LineType.DEFINE, additionalDefinitions); this.addDefinitions(allNodes, shaderName, JsDefinitionType.COMPUTED, LineType.MEMBER, additionalDefinitions); this.addDefinitions(nodes, shaderName, JsDefinitionType.CONSTANT, LineType.MEMBER, additionalDefinitions); this.addDefinitions(allNodes, shaderName, JsDefinitionType.REF, LineType.MEMBER, additionalDefinitions); // console.log( // 'triggerNodes', // options?.triggerNodes?.map((n) => n.name()) // ); // const initFunctionNodes = nodes.concat(options?.actor.triggerNodes || []); this.addDefinitions( allNodes, shaderName, JsDefinitionType.INIT_FUNCTION, LineType.CONSTRUCTOR, additionalDefinitions ); this.addDefinitions(allNodes, shaderName, JsDefinitionType.WATCH, LineType.CONSTRUCTOR, additionalDefinitions); if (options?.actor.triggeringNodes) { this.addDefinitions( // we currently add all nodes here, // so that nodes which have an intersection output // can have their triggering method added, // even if only the intersection output is used allNodes, //setToArray(options.actor.triggeringNodes), shaderName, JsDefinitionType.TRIGGERING, LineType.BODY, additionalDefinitions ); } if (options?.actor.triggerableNodes) { this.addDefinitions( setToArray(options.actor.triggerableNodes, []), shaderName, JsDefinitionType.TRIGGERABLE, LineType.BODY, additionalDefinitions ); } this.add_code_line_for_nodes_and_line_type(allNodes, shaderName, LineType.BODY); } private addDefinitions( nodes: BaseJsNodeType[], shaderName: JsFunctionName, definitionType: JsDefinitionType, lineType: LineType, additionalDefinitions?: BaseJsDefinition[] ) { if (!this._shadersCollectionController) { return; } const definitions: TypedJsDefinition<JsDefinitionType>[] = []; for (const node of nodes) { let nodeDefinitions = this._shadersCollectionController.definitions(shaderName, node); if (nodeDefinitions) { nodeDefinitions = nodeDefinitions.filter((d) => d.definitionType() == definitionType); for (const definition of nodeDefinitions) { definitions.push(definition); } } } if (additionalDefinitions) { const filteredAdditionalDefinitions = additionalDefinitions.filter( (d) => d.definitionType() == definitionType ); for (const definition of filteredAdditionalDefinitions) { definitions.push(definition); } } if (definitions.length == 0) { return; } const collection = new TypedJsDefinitionCollection<JsDefinitionType>(definitions); const uniqDefinitions = collection.uniq(); if (collection.errored) { // TODO: handle error throw `code builder error: ${collection.error_message}`; } const definitions_by_node_id: Map<CoreGraphNodeId, BaseJsDefinition[]> = new Map(); const nodeIds: Map<CoreGraphNodeId, boolean> = new Map(); for (const definition of uniqDefinitions) { const nodeId = definition.node().graphNodeId(); if (!nodeIds.has(nodeId)) { nodeIds.set(nodeId, true); } pushOnArrayAtEntry(definitions_by_node_id, nodeId, definition); } const lines_for_shader = this._lines.get(shaderName)!; // gather const definitionClass = JsDefinitionTypeMap[definitionType]; definitionClass.gather(definitions, lines_for_shader, lineType); // process definition for each node nodeIds.forEach((_, nodeId) => { const definitions = definitions_by_node_id.get(nodeId); if (definitions) { const first_definition = definitions[0]; if (first_definition) { const comment = CodeFormatter.nodeComment(first_definition.node(), lineType); pushOnArrayAtEntry(lines_for_shader, lineType, comment); for (const definition of definitions) { const line = CodeFormatter.lineWrap(first_definition.node(), definition.line(), lineType); pushOnArrayAtEntry(lines_for_shader, lineType, line); } const separator = CodeFormatter.post_line_separator(lineType); pushOnArrayAtEntry(lines_for_shader, lineType, separator); } } }); } add_code_line_for_nodes_and_line_type(nodes: BaseJsNodeType[], shaderName: JsFunctionName, lineType: LineType) { nodes = nodes.filter((node) => { if (this._shadersCollectionController) { const lines = this._shadersCollectionController.bodyLines(shaderName, node); return lines && lines.length > 0; } }); var nodesCount = nodes.length; for (let i = 0; i < nodesCount; i++) { const isLast = i == nodes.length - 1; this.add_code_line_for_node_and_line_type(nodes[i], shaderName, lineType, isLast); } } add_code_line_for_node_and_line_type( node: BaseJsNodeType, shaderName: JsFunctionName, lineType: LineType, isLast: boolean ): void { if (!this._shadersCollectionController) { return; } const lines = this._shadersCollectionController.bodyLines(shaderName, node); if (lines && lines.length > 0) { const lines_for_shader = this._lines.get(shaderName)!; const comment = CodeFormatter.nodeComment(node, lineType); pushOnArrayAtEntry(lines_for_shader, lineType, comment); lines.forEach((line) => { line = CodeFormatter.lineWrap(node, line, lineType); pushOnArrayAtEntry(lines_for_shader, lineType, line); }); if (!(lineType == LineType.BODY && isLast)) { const separator = CodeFormatter.post_line_separator(lineType); pushOnArrayAtEntry(lines_for_shader, lineType, separator); } } } }