UNPKG

@polygonjs/polygonjs

Version:

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

245 lines (223 loc) 8.72 kB
import {BaseNodeClass, BaseNodeType} from '../../../nodes/_Base'; import {BaseOperation} from '../../../operations/_Base'; import {NodeContext} from '../../NodeContext'; import {PolyEngine} from '../../../Poly'; import {PolyDictionary} from '../../../../types/GlobalTypes'; import {isArray} from '../../../../core/Type'; export interface OperationRegisterOptions { printWarnings?: boolean; } export interface NodeRegisterOptions { only?: string[]; except?: string[]; userAllowed?: boolean; polyNode?: boolean; printWarnings?: boolean; } // export interface BaseNodeConstructor { // new (): BaseNode; // } export type BaseNodeConstructor = typeof BaseNodeClass; type NodeConstructorByType = Map<string, BaseNodeConstructor>; type NodeConstructorByTypeByContext = Map<NodeContext, NodeConstructorByType>; type TabMenuByTypeByContext = Map<NodeContext, Map<string, string[]>>; type RegisterOptionsByTypeByContext = Map<NodeContext, Map<string, NodeRegisterOptions>>; export type BaseOperationConstructor = typeof BaseOperation; type OperationConstructorByType = Map<string, BaseOperationConstructor>; type OperationConstructorByTypeByContext = Map<NodeContext, OperationConstructorByType>; export type OnNodeRegisterCallback = (poly: PolyEngine) => void; export type OnOperationRegisterCallback = (poly: PolyEngine) => void; export class NodesRegister { private _nodesRegister: NodeConstructorByTypeByContext = new Map(); private _nodesRegisterCategories: TabMenuByTypeByContext = new Map(); private _nodesRegisterOptions: RegisterOptionsByTypeByContext = new Map(); constructor(private poly: PolyEngine) {} private static type(node: BaseNodeType | BaseNodeConstructor) { return this.filterType(node.type()); } private static filterType(nodeType: string) { return nodeType.toLowerCase(); } register(node: BaseNodeConstructor, tabMenuCategory?: string | string[], options?: NodeRegisterOptions) { const context = node.context(); // if (node.type()[0].toLowerCase() != node.type()[0]) { // console.warn(`node type '${node.context()}/${node.type()}' should start with a lower case letter`); // } const nodeType = NodesRegister.type(node); let printWarnings = options?.printWarnings; if (printWarnings == null) { printWarnings = true; } let current_nodes_for_context = this._nodesRegister.get(context); if (!current_nodes_for_context) { current_nodes_for_context = new Map(); this._nodesRegister.set(context, current_nodes_for_context); } const alreadyRegisteredNode = current_nodes_for_context.get(nodeType); if (alreadyRegisteredNode) { // if the node that is already registered is a polyNode, it can be overwritten by another polyNode. const isAlreadyRegisteredNodePolyNode = this._nodesRegisterOptions.get(context)?.get(nodeType)?.polyNode == true; const isNewNodePolyNode = options?.polyNode == true; if (isAlreadyRegisteredNodePolyNode && isNewNodePolyNode) { // we don't show a warning or return if both are polyNodes } else { if (printWarnings) { console.warn(`node ${context}/${nodeType} already registered`); } return; } } current_nodes_for_context.set(nodeType, node); if (node.onRegister) { node.onRegister(this.poly); } if (tabMenuCategory) { let current_categories = this._nodesRegisterCategories.get(context); if (!current_categories) { current_categories = new Map(); this._nodesRegisterCategories.set(context, current_categories); } const savedCategory = isArray(tabMenuCategory) ? tabMenuCategory : [tabMenuCategory]; current_categories.set(nodeType, savedCategory); } if (options) { let current_options = this._nodesRegisterOptions.get(context); if (!current_options) { current_options = new Map(); this._nodesRegisterOptions.set(context, current_options); } current_options.set(nodeType, options); } this.poly.pluginsRegister.registerNode(node); } deregister(context: NodeContext, nodeType: string) { nodeType = NodesRegister.filterType(nodeType); this._nodesRegister.get(context)?.delete(nodeType); this._nodesRegisterCategories.get(context)?.delete(nodeType); this._nodesRegisterOptions.get(context)?.delete(nodeType); } isRegistered(context: NodeContext, nodeType: string): boolean { const nodes_for_context = this._nodesRegister.get(context); if (!nodes_for_context) { return false; } nodeType = NodesRegister.filterType(nodeType); return nodes_for_context.get(nodeType) != null; } nodeOptions(context: NodeContext, nodeType: string): NodeRegisterOptions | undefined { nodeType = NodesRegister.filterType(nodeType); return this._nodesRegisterOptions.get(context)?.get(nodeType); } registeredNodesForParentNode(parentNode: BaseNodeType) { const context = parentNode.childrenController?.context; if (!context) { return []; } const map = this._nodesRegister.get(context); if (map) { const nodes_for_context: BaseNodeConstructor[] = []; this._nodesRegister.get(context)?.forEach((node, type) => { nodes_for_context.push(node); }); return nodes_for_context.filter((node) => { const nodeType = NodesRegister.type(node); const options = this.nodeOptions(context, nodeType); if (!options) { return true; } else { const parentOptions = this.nodeOptions(parentNode.context(), parentNode.type()); if (parentOptions?.polyNode == true) { // if parentNode is a polyNode, we should be able to create any node, // otherwise we would not be able to create gl/subnetInput and gl/subnetOutput // which would be problematic return true; } const option_only = options['only']; const option_except = options['except']; const context_and_type = `${parentNode.context()}/${parentNode.type()}`; if (option_only) { return option_only.includes(context_and_type); } if (option_except) { return !option_except.includes(context_and_type); } return true; } }); } else { return []; } } registeredNodes(parentNode: BaseNodeType): PolyDictionary<BaseNodeConstructor> { const nodesByType: PolyDictionary<BaseNodeConstructor> = {}; const nodes = this.registeredNodesForParentNode(parentNode); for (let node of nodes) { const nodeType = NodesRegister.type(node); nodesByType[nodeType] = node; } return nodesByType; } registeredCategory(context: NodeContext, nodeType: string) { nodeType = NodesRegister.filterType(nodeType); return this._nodesRegisterCategories.get(context)?.get(nodeType); } map() { return this._nodesRegister; } } export class OperationsRegister { private _operation_register: OperationConstructorByTypeByContext = new Map(); constructor(private poly: PolyEngine) {} private static type(node: BaseOperationConstructor) { return this.filterType(node.type()); } private static filterType(nodeType: string) { return nodeType.toLowerCase(); } register(operation: BaseOperationConstructor, options?: OperationRegisterOptions) { let printWarnings = options?.printWarnings; if (printWarnings == null) { printWarnings = true; } const context = operation.context(); let current_operations_for_context = this._operation_register.get(context); if (!current_operations_for_context) { current_operations_for_context = new Map(); this._operation_register.set(context, current_operations_for_context); } const operationType = OperationsRegister.type(operation); const already_registered_operation = current_operations_for_context.get(operationType); if (already_registered_operation) { if (printWarnings) { const message = `operation ${context}/${operationType} already registered`; console.warn(message); } return; } current_operations_for_context.set(operationType, operation); if (operation.onRegister) { operation.onRegister(this.poly); } this.poly.pluginsRegister.registerOperation(operation); } registeredOperationsForContextAndParentType(context: NodeContext, parentNodeType: string) { const map = this._operation_register.get(context); if (map) { const nodes_for_context: BaseOperationConstructor[] = []; this._operation_register.get(context)?.forEach((operation, type) => { nodes_for_context.push(operation); }); return nodes_for_context; } else { return []; } } registeredOperation(context: NodeContext, operationType: string): BaseOperationConstructor | undefined { const current_operations_for_context = this._operation_register.get(context); if (current_operations_for_context) { operationType = OperationsRegister.filterType(operationType); return current_operations_for_context.get(operationType); } } }