@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
166 lines (152 loc) • 5.32 kB
text/typescript
import {NodeEvent} from '../../../poly/NodeEvent';
import {NodeContext} from '../../../poly/NodeContext';
import {ConnectionPointTypeMap} from './connections/ConnectionMap';
import {TypedNode} from '../../_Base';
import {isNumber, isString} from '../../../../core/Type';
import {arrayUniq} from '../../../../core/ArrayUtils';
import {TypedNodeConnection} from './NodeConnection';
const _uniqOutputIndices: number[] = [];
const _usedOutputIndices: number[] = [];
export class OutputsController<NC extends NodeContext> {
private _has_outputs: boolean = false;
private _named_output_connection_points: ConnectionPointTypeMap[NC][] | undefined;
private _has_named_outputs: boolean = false;
private _connections: TypedNodeConnection<NC>[] = [];
constructor(private node: TypedNode<NC, any>) {
this.node.scene().timeController.onPlayingStateChange(this._onPlayingStateChangeBound);
}
private _onPlayingStateChangeBound = this._onPlayingStateChange.bind(this);
private _onPlayingStateChange() {
this._clearCache();
}
private _outputIndexCache: Map<number | string, number> = new Map();
private _clearCache() {
this._outputIndexCache.clear();
}
dispose() {
this.node.scene().timeController.removeOnPlayingStateChange(this._onPlayingStateChangeBound);
if (this._named_output_connection_points) {
this._named_output_connection_points.splice(0, this._named_output_connection_points.length);
}
}
setHasOneOutput() {
this._has_outputs = true;
}
setHasNoOutput() {
this._has_outputs = false;
}
hasOutputs() {
return this._has_outputs;
}
hasNamedOutputs() {
return this._has_named_outputs;
}
hasNamedOutput(name: string): boolean {
return this.getNamedOutputIndex(name) >= 0;
}
namedOutputConnectionPoints(): Readonly<ConnectionPointTypeMap[NC][]> | undefined {
return this._named_output_connection_points;
}
namedOutputConnection(index: number): Readonly<ConnectionPointTypeMap[NC]> | undefined {
if (this._named_output_connection_points) {
return this._named_output_connection_points[index];
}
}
getNamedOutputIndex(name: string): number {
if (this._named_output_connection_points) {
let i = 0;
for (const connectionPoint of this._named_output_connection_points) {
if (connectionPoint && connectionPoint.name() == name) {
return i;
}
i++;
}
}
return -1;
}
getOutputIndex(output_index_or_name: number | string): number {
let currentCache = this._outputIndexCache.get(output_index_or_name);
if (currentCache == null) {
currentCache = this._getOutputIndex(output_index_or_name);
this._outputIndexCache.set(output_index_or_name, currentCache);
}
return currentCache;
}
private _getOutputIndex(output_index_or_name: number | string): number {
if (output_index_or_name != null) {
if (isString(output_index_or_name)) {
if (this.hasNamedOutputs()) {
return this.getNamedOutputIndex(output_index_or_name);
} else {
console.warn(`node ${this.node.path()} has no named outputs`);
return -1;
}
} else {
return output_index_or_name;
}
}
return -1;
}
namedOutputConnectionPointsByName(name: string): ConnectionPointTypeMap[NC] | undefined {
if (this._named_output_connection_points) {
for (const connection_point of this._named_output_connection_points) {
if (connection_point?.name() == name) {
return connection_point;
}
}
}
}
setNamedOutputConnectionPoints(connection_points: ConnectionPointTypeMap[NC][], set_dirty: boolean = true) {
this._has_named_outputs = true;
this.node.io.connections.outputConnections(this._connections);
for (const connection of this._connections) {
if (connection) {
// assume we only work with indices for now, not with connection point names
// so we only need to check again the new max number of connection points.
if (connection.outputIndex() >= connection_points.length) {
connection.disconnect({setInput: true});
}
}
}
// update connections
this._named_output_connection_points = connection_points;
if (set_dirty && this.node.scene()) {
// why do I need this set dirty here?
// I currently have to have a flag to optionally prevent this,
// for instance from gl nodes which have their outputs updated in a post dirty hook
this.node.setDirty(this.node);
}
this.node.emit(NodeEvent.NAMED_OUTPUTS_UPDATED);
}
used_output_names(): string[] {
// target.length = 0;
const target: string[] = [];
const connectionsController = this.node.io.connections;
if (connectionsController) {
connectionsController.outputConnections(this._connections);
arrayUniq(
this._connections.map((connection) => (connection ? connection.outputIndex() : null)),
_uniqOutputIndices
);
// outputIndices = arrayUniq(output_indices);
// const used_output_indices: number[] = [];
_usedOutputIndices.length = 0;
for (const index of _uniqOutputIndices) {
if (isNumber(index)) {
_usedOutputIndices.push(index);
}
}
// const used_output_names: string[] = [];
const connectionPoints = this.namedOutputConnectionPoints();
if (connectionPoints) {
for (const index of _usedOutputIndices) {
const name = connectionPoints[index]?.name();
if (name) {
target.push(name);
}
}
}
}
return target;
}
}