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