import { BuildableComponent } from "../package/component/buildable_component.js";
import { Reporter } from "../reporter/reporter.js";
import { Configuration } from "./configuration.js";
import { checkPropTypes } from '../lib.js';
// consideration:
// might be helpful to create phantom connectors instances that can be either connected or not
// simply to track state
// but these will not have an id, so the connection can not refer to them directly
// then what is the use?
const emptyConnectorTemplate = {
connectors: []
}
/**
* Base class for instantiated connectable elements, such as BlockInstance (which
* instantiates Block) and Connection (which can be seen as an instance of ConnectionType).
* @class
*/
class Connectable extends BuildableComponent {
/**
*
* @param {Reporter} reporter
* @param {Object} settings
*/
// * @param {BuildableComponent} settings.connectorTemplate
// * @param {Array<Connector>} settings.connectors
constructor(reporter, settings) {
super(
reporter,
settings
);
if (!this.constructor.connectorTemplateSetting) {
throw new Error(`${this.constructor.name} is missing static property connectorTemplateSetting`);
}
// ideally, a connectable has a connector template on construction
// alas, for a connection, the connectionType is the connection template
// and when it is constructed, the connector type is still unknown
// therefore, when a connectable doesn't specify a connector template
// on initialisation, the emptyConnectorTemplate is used as placeholder
// until the ._tree field is set
checkPropTypes(
settings,
{},
{
[this.constructor.connectorTemplateSetting]: [
BuildableComponent,
]
}
);
if (settings[this.constructor.connectorTemplateSetting]) {
this.connectorTemplate = settings[this.constructor.connectorTemplateSetting];
}
else {
this.connectorTemplate = emptyConnectorTemplate;
}
}
clone(newSettings = {}) {
const cl = super.clone(newSettings);
cl.connectorTemplate = this.connectorTemplate;
return cl;
}
/**
* The Component that specifies which connectors this Connectable has
* @type {Component}
*/
connectorTemplate;
/**
* All connections of which this connectable is a part
* @type {Array<Connection>}
*/
connections;
/**
* All equal connections (no parent/child relationship) of which this connectable is a part
* @type {Array<Connection>}
*/
equalConnections;
/**
* All unequal connections (with parent/child relationship) of which this connection is a part
* @type {Array<Connection>}
*/
unequalConnections;
/**
* All other connectables this one is connected to
* @type {Array<Connectable>}
*/
connectedTo;
/**
* A Connectable that acts as a sibling to this one
* @type {(Array<Connectable>)}
*/
siblings;
/**
* A Connectable that acts as a parent to this one
* @type {(Connection)}
*/
parentConnection;
/**
* A Connectable that acts as a parent to this one
* @type {(Connectable)}
*/
parent;
/**
* Connectables that acts as children to this one
* @type {Array<Connection>}
*/
childConnections;
/**
* Connectables that acts as children to this one
* @type {Array<Connectable>}
*/
children;
/**
*
*/
childMaterialAssignments;
get siblingChains() {
const siblingsInAChain = [];
const chains = {};
for ( let sibling of this.siblings || [] ) {
// console.log(`${this.label} find siblings of sibling ${sibling.label}`);
const chain = [ sibling, ...Connectable.findSiblings(sibling,[this, ...siblingsInAChain])]; // safeguard against circular designs
chains[ sibling.id ] = chain;
siblingsInAChain.push(...chain);
}
return chains;
};
findMaterialAssignments() {
if ( this._tree && this._tree.materialAssignments ) {
return this._tree.materialAssignments.filter(ma => ma.blockInstance === this);
}
else {
return [];
}
}
static findSiblings = (connectable, excludeList = []) => {
// console.log( 'conn', connectable, 'excl', excludeList);
return ( connectable.siblings || [] )
.filter(sibling => !excludeList.includes(sibling))
.map(sibling => [ sibling, ...Connectable.findSiblings( sibling, [connectable, ...excludeList])])
.flat();
}
_updateConnections() {
// console.log( 'update connections', this._tree.connections.length )
this.materialAssignments = this.findMaterialAssignments();
this.connections = (this._tree.connections || []).filter(cnn =>
(cnn.from.connectable === this && this.connectorTemplate.connectors.includes(cnn.from.connector))
||
(cnn.to.connectable === this && this.connectorTemplate.connectors.includes(cnn.to.connector))
);
this.equalConnections = this.connections.filter(cnn => cnn.type.parent === undefined);
this.unequalConnections = this.connections.filter(cnn => cnn.type.parent !== undefined);
this.connectedTo = this.connections.map(cnn => cnn.from.connectable === this ? cnn.to.connectable : cnn.from.connectable);
this.siblings = this.equalConnections.map(cnn => cnn.from.connectable === this ? cnn.to.connectable : cnn.from.connectable);
// this.siblingChains = {};
// It is known that the unequal connections can not have the same connector type twice
// and it is known that one of the two sides must be the parent. So, if the from side
// connectable equals this and the from side connector is the parent type, this is
// the parent, otherwise this is the child
const parentConnection = this.unequalConnections.find(cnn => {
const mySide = cnn.from.connectable === this ? 'from' : 'to';
return cnn[mySide].connector === cnn.type.parent;
});
if (parentConnection) {
this.parentConnection = parentConnection;
const parentSide = parentConnection.from.connectable === this ? 'to' : 'from';
this.parent = parentConnection[parentSide].connectable;
}
const childConnections = this.unequalConnections
.filter(cnn => cnn !== parentConnection);
if (childConnections) {
this.childConnections = childConnections;
this.children = childConnections
.map(ucnn => ucnn.connectables.find(ctb => ctb !== this));
this.childMaterialAssignments = this.children.map(child => child.findMaterialAssignments()).flat();
}
}
_onTreeSet() {
// console.error( 'tree set', this._tree.label)
console.assert(this._tree instanceof Configuration, `${this.constructor.name} can not be added to a ${this._tree.constructor.name}`);
const component = this;
if (!this._tree.findComponentById(this.connectorTemplate.id)) {
throw new Error(`ConnectorTemplate ${this.connectorTemplate.label} not in tree`);
}
this._tree.on(
'change',
({ cls, type }) => {
// console.log( 'change', type, cls)
if (cls.find( cl => cl.name === 'Connection')) {
component._updateConnections();
}
},
this.id
);
this._updateConnections();
}
_onTreeUnset() {
this._tree.removeListeners('change', this.id);
this.connections = undefined;
this.connectedTo = undefined;
this.materialAssignments = undefined;
this.childMaterialAssignments = undefined;
}
}
export { Connectable };