2022-11-09 01:02:24 -06:00
|
|
|
import { Unsubscribable } from 'rxjs';
|
|
|
|
|
|
|
|
import { SceneObjectBase } from '../../core/SceneObjectBase';
|
2022-11-15 05:54:24 -06:00
|
|
|
import { SceneObject } from '../../core/types';
|
|
|
|
import { forEachSceneObjectInState } from '../../core/utils';
|
2022-11-09 01:02:24 -06:00
|
|
|
import { SceneVariable, SceneVariables, SceneVariableSetState, SceneVariableValueChangedEvent } from '../types';
|
|
|
|
|
|
|
|
export class SceneVariableSet extends SceneObjectBase<SceneVariableSetState> implements SceneVariables {
|
|
|
|
/** Variables that have changed in since the activation or since the first manual value change */
|
2022-11-15 05:54:24 -06:00
|
|
|
private variablesThatHaveChanged = new Set<SceneVariable>();
|
2022-11-09 01:02:24 -06:00
|
|
|
|
|
|
|
/** Variables that are scheduled to be validated and updated */
|
2022-11-15 05:54:24 -06:00
|
|
|
private variablesToUpdate = new Set<SceneVariable>();
|
2022-11-09 01:02:24 -06:00
|
|
|
|
|
|
|
/** Variables currently updating */
|
2022-11-15 05:54:24 -06:00
|
|
|
private updating = new Map<SceneVariable, VariableUpdateInProgress>();
|
2022-11-09 01:02:24 -06:00
|
|
|
|
|
|
|
public getByName(name: string): SceneVariable | undefined {
|
|
|
|
// TODO: Replace with index
|
|
|
|
return this.state.variables.find((x) => x.state.name === name);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Subscribes to child variable value changes
|
|
|
|
* And starts the variable value validation process
|
|
|
|
*/
|
|
|
|
public activate(): void {
|
|
|
|
super.activate();
|
|
|
|
|
|
|
|
// Subscribe to changes to child variables
|
|
|
|
this._subs.add(this.subscribeToEvent(SceneVariableValueChangedEvent, this.onVariableValueChanged));
|
|
|
|
this.validateAndUpdateAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cancel all currently running updates
|
|
|
|
*/
|
|
|
|
public deactivate(): void {
|
|
|
|
super.deactivate();
|
|
|
|
this.variablesToUpdate.clear();
|
|
|
|
|
|
|
|
for (const update of this.updating.values()) {
|
2022-12-12 06:01:27 -06:00
|
|
|
update.subscription?.unsubscribe();
|
2022-11-09 01:02:24 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This loops through variablesToUpdate and update all that that can.
|
|
|
|
* If one has a dependency that is currently in variablesToUpdate it will be skipped for now.
|
|
|
|
*/
|
|
|
|
private updateNextBatch() {
|
2022-11-15 05:54:24 -06:00
|
|
|
// If we have nothing more to update and variable values changed we need to update scene objects that depend on these variables
|
|
|
|
if (this.variablesToUpdate.size === 0 && this.variablesThatHaveChanged.size > 0) {
|
|
|
|
this.notifyDependentSceneObjects();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const variable of this.variablesToUpdate) {
|
2022-11-09 01:02:24 -06:00
|
|
|
if (!variable.validateAndUpdate) {
|
|
|
|
throw new Error('Variable added to variablesToUpdate but does not have validateAndUpdate');
|
|
|
|
}
|
|
|
|
|
2022-11-25 07:20:56 -06:00
|
|
|
// Ignore it if it's already started
|
|
|
|
if (this.updating.has(variable)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-11-09 01:02:24 -06:00
|
|
|
// Wait for variables that has dependencies that also needs updates
|
|
|
|
if (this.hasDependendencyInUpdateQueue(variable)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-12-12 06:01:27 -06:00
|
|
|
const variableToUpdate: VariableUpdateInProgress = {
|
2022-11-09 01:02:24 -06:00
|
|
|
variable,
|
2022-12-12 06:01:27 -06:00
|
|
|
};
|
|
|
|
|
|
|
|
this.updating.set(variable, variableToUpdate);
|
|
|
|
variableToUpdate.subscription = variable.validateAndUpdate().subscribe({
|
|
|
|
next: () => this.validateAndUpdateCompleted(variable),
|
|
|
|
error: (err) => this.handleVariableError(variable, err),
|
2022-11-09 01:02:24 -06:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A variable has completed it's update process. This could mean that variables that depend on it can now be updated in turn.
|
|
|
|
*/
|
|
|
|
private validateAndUpdateCompleted(variable: SceneVariable) {
|
2022-11-15 05:54:24 -06:00
|
|
|
const update = this.updating.get(variable);
|
2022-12-12 06:01:27 -06:00
|
|
|
update?.subscription?.unsubscribe();
|
2022-11-09 01:02:24 -06:00
|
|
|
|
2022-11-15 05:54:24 -06:00
|
|
|
this.updating.delete(variable);
|
|
|
|
this.variablesToUpdate.delete(variable);
|
2022-11-09 01:02:24 -06:00
|
|
|
this.updateNextBatch();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* TODO handle this properly (and show error in UI).
|
|
|
|
* Not sure if this should be handled here on in MultiValueVariable
|
|
|
|
*/
|
|
|
|
private handleVariableError(variable: SceneVariable, err: Error) {
|
2022-12-12 06:01:27 -06:00
|
|
|
const update = this.updating.get(variable);
|
|
|
|
update?.subscription?.unsubscribe();
|
|
|
|
|
|
|
|
this.updating.delete(variable);
|
|
|
|
this.variablesToUpdate.delete(variable);
|
2022-11-09 01:02:24 -06:00
|
|
|
variable.setState({ loading: false, error: err });
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if the variable has any dependencies that is currently in variablesToUpdate
|
|
|
|
*/
|
|
|
|
private hasDependendencyInUpdateQueue(variable: SceneVariable) {
|
2022-11-15 05:54:24 -06:00
|
|
|
if (!variable.variableDependency) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const otherVariable of this.variablesToUpdate.values()) {
|
|
|
|
if (variable.variableDependency?.hasDependencyOn(otherVariable.state.name)) {
|
|
|
|
return true;
|
2022-11-09 01:02:24 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Extract dependencies from all variables and add those that needs update to the variablesToUpdate map
|
|
|
|
* Then it will start the update process.
|
|
|
|
*/
|
|
|
|
private validateAndUpdateAll() {
|
|
|
|
for (const variable of this.state.variables) {
|
|
|
|
if (variable.validateAndUpdate) {
|
2022-11-15 05:54:24 -06:00
|
|
|
this.variablesToUpdate.add(variable);
|
2022-11-09 01:02:24 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.updateNextBatch();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This will trigger an update of all variables that depend on it.
|
|
|
|
* */
|
|
|
|
private onVariableValueChanged = (event: SceneVariableValueChangedEvent) => {
|
2022-11-15 05:54:24 -06:00
|
|
|
const variableThatChanged = event.payload;
|
|
|
|
|
|
|
|
this.variablesThatHaveChanged.add(variableThatChanged);
|
2022-11-09 01:02:24 -06:00
|
|
|
|
|
|
|
// Ignore this change if it is currently updating
|
2022-11-15 05:54:24 -06:00
|
|
|
if (this.updating.has(variableThatChanged)) {
|
2022-11-09 01:02:24 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-11-15 05:54:24 -06:00
|
|
|
// Add variables that depend on the changed variable to the update queue
|
|
|
|
for (const otherVariable of this.state.variables) {
|
|
|
|
if (otherVariable.variableDependency) {
|
|
|
|
if (otherVariable.variableDependency.hasDependencyOn(variableThatChanged.state.name)) {
|
|
|
|
this.variablesToUpdate.add(otherVariable);
|
2022-11-09 01:02:24 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.updateNextBatch();
|
|
|
|
};
|
2022-11-15 05:54:24 -06:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Walk scene object graph and update all objects that depend on variables that have changed
|
|
|
|
*/
|
|
|
|
private notifyDependentSceneObjects() {
|
|
|
|
if (!this.parent) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.traverseSceneAndNotify(this.parent);
|
|
|
|
this.variablesThatHaveChanged.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Recursivly walk the full scene object graph and notify all objects with dependencies that include any of changed variables
|
|
|
|
*/
|
|
|
|
private traverseSceneAndNotify(sceneObject: SceneObject) {
|
|
|
|
// No need to notify variables under this SceneVariableSet
|
|
|
|
if (this === sceneObject) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sceneObject.variableDependency) {
|
|
|
|
sceneObject.variableDependency.variableValuesChanged(this.variablesThatHaveChanged);
|
|
|
|
}
|
|
|
|
|
|
|
|
forEachSceneObjectInState(sceneObject.state, (child) => this.traverseSceneAndNotify(child));
|
|
|
|
}
|
2022-11-09 01:02:24 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface VariableUpdateInProgress {
|
|
|
|
variable: SceneVariable;
|
2022-12-12 06:01:27 -06:00
|
|
|
subscription?: Unsubscribable;
|
2022-11-09 01:02:24 -06:00
|
|
|
}
|