@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
506 lines (466 loc) • 18.4 kB
text/typescript
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);
}
}
}
}