@theia/core
Version:
Theia is a cloud & desktop IDE framework implemented in TypeScript.
258 lines • 12.8 kB
JavaScript
"use strict";
// *****************************************************************************
// Copyright (C) 2025 1C-Soft LLC and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// copied and modified from https://github.com/microsoft/vscode/blob/1.96.3/src/vs/base/common/observableInternal/derived.ts
Object.defineProperty(exports, "__esModule", { value: true });
exports.DerivedObservable = void 0;
const observable_base_1 = require("./observable-base");
/**
* An observable that is derived from other observables.
* Its value is only (re-)computed when absolutely needed.
*/
class DerivedObservable extends observable_base_1.BaseObservable {
constructor(compute, options) {
var _a, _b;
super();
this.compute = compute;
this.state = 0 /* DerivedObservable.State.Initial */;
this.updateCount = 0;
this.isComputing = false;
this.dependencies = new Set();
this.dependencyObserver = this.createDependencyObserver();
this.isEqual = (_a = options === null || options === void 0 ? void 0 : options.isEqual) !== null && _a !== void 0 ? _a : ((a, b) => a === b);
this.createChangeSummary = options === null || options === void 0 ? void 0 : options.createChangeSummary;
this.willHandleChange = options === null || options === void 0 ? void 0 : options.willHandleChange;
this.changeSummary = (_b = this.createChangeSummary) === null || _b === void 0 ? void 0 : _b.call(this);
}
onLastObserverRemoved() {
// We are not tracking changes anymore, thus we have to invalidate the cached value.
this.state = 0 /* DerivedObservable.State.Initial */;
this.value = undefined;
for (const dependency of this.dependencies) {
dependency.removeObserver(this.dependencyObserver);
}
this.dependencies.clear();
}
getValue() {
if (this.isComputing) {
throw new Error('Cyclic dependencies are not allowed');
}
if (this.observers.size === 0) {
// Without observers, we don't know when to clean up stuff.
// Thus, we don't cache anything to prevent memory leaks.
let result;
try {
this.isComputing = true;
result = observable_base_1.Observable.Accessor.runWithAccessor(() => { var _a; return this.compute({ changeSummary: (_a = this.createChangeSummary) === null || _a === void 0 ? void 0 : _a.call(this) }); }, dependency => this.watchDependency(dependency));
}
finally {
this.isComputing = false;
// Clear new dependencies.
this.onLastObserverRemoved();
}
return result;
}
else {
do {
if (this.state === 1 /* DerivedObservable.State.DependenciesMightHaveChanged */) {
// Need to ask our depedencies if at least one of them has actually changed.
for (const dependency of this.dependencies) {
dependency.update(); // might call handleChange indirectly, which could make us stale
if (this.state === 2 /* DerivedObservable.State.Stale */) {
// The other dependencies will refresh on demand, so early break
break;
}
}
}
// If we are still not stale, we can assume to be up to date again.
if (this.state === 1 /* DerivedObservable.State.DependenciesMightHaveChanged */) {
this.state = 3 /* DerivedObservable.State.UpToDate */;
}
if (this.state !== 3 /* DerivedObservable.State.UpToDate */) {
this.recompute();
}
// In case recomputation changed one of our dependencies, we need to recompute again.
} while (this.state !== 3 /* DerivedObservable.State.UpToDate */);
return this.value;
}
}
recompute() {
var _a;
this.dependenciesToBeRemoved = this.dependencies;
this.dependencies = new Set();
const hadValue = this.state !== 0 /* DerivedObservable.State.Initial */;
const oldValue = this.value;
this.state = 3 /* DerivedObservable.State.UpToDate */;
try {
const { changeSummary } = this;
this.changeSummary = (_a = this.createChangeSummary) === null || _a === void 0 ? void 0 : _a.call(this);
this.isComputing = true;
this.value = observable_base_1.Observable.Accessor.runWithAccessor(() => this.compute({ changeSummary }), dependency => this.watchDependency(dependency));
}
finally {
this.isComputing = false;
// We don't want our watched dependencies to think that they are no longer observed, even temporarily.
// Thus, we only unsubscribe from dependencies that are definitely not watched anymore.
for (const dependency of this.dependenciesToBeRemoved) {
dependency.removeObserver(this.dependencyObserver);
}
this.dependenciesToBeRemoved = undefined;
}
const didChange = hadValue && !this.isEqual(oldValue, this.value);
if (didChange) {
for (const observer of this.observers) {
observer.handleChange(this);
}
}
}
watchDependency(dependency) {
var _a;
if (!this.isComputing) {
throw new Error('The accessor may only be called while the compute function is running');
}
// Subscribe before getting the value to enable caching.
dependency.addObserver(this.dependencyObserver);
// This might call handleChange indirectly, which could invalidate us.
const value = dependency.getUntracked();
// Which is why we only add the observable to the dependencies now.
this.dependencies.add(dependency);
(_a = this.dependenciesToBeRemoved) === null || _a === void 0 ? void 0 : _a.delete(dependency);
return value;
}
createDependencyObserver() {
let inBeginUpdate = false;
return {
beginUpdate: () => {
if (inBeginUpdate) {
throw new Error('Cyclic dependencies are not allowed');
}
inBeginUpdate = true;
try {
this.updateCount++;
const propagateBeginUpdate = this.updateCount === 1;
if (this.state === 3 /* DerivedObservable.State.UpToDate */) {
this.state = 1 /* DerivedObservable.State.DependenciesMightHaveChanged */;
// If we propagate begin update, that will already signal a possible change.
if (!propagateBeginUpdate) {
for (const observer of this.observers) {
observer.handlePossibleChange(this);
}
}
}
if (propagateBeginUpdate) {
for (const observer of this.observers) {
observer.beginUpdate(this); // signals a possible change
}
}
}
finally {
inBeginUpdate = false;
}
},
endUpdate: () => {
this.updateCount--;
if (this.updateCount === 0) {
// Calls to endUpdate can potentially change the observers list.
let observers = [...this.observers];
for (const observer of observers) {
observer.endUpdate(this);
}
if (this.removedObserversToCallEndUpdateOn) {
observers = [...this.removedObserversToCallEndUpdateOn];
this.removedObserversToCallEndUpdateOn = undefined;
for (const observer of observers) {
observer.endUpdate(this);
}
}
}
if (this.updateCount < 0) {
throw new Error('Unexpected update count: ' + this.updateCount);
}
},
handlePossibleChange: (observable) => {
var _a;
// In all other states, observers already know that we might have changed.
if (this.state === 3 /* DerivedObservable.State.UpToDate */ && this.dependencies.has(observable) && !((_a = this.dependenciesToBeRemoved) === null || _a === void 0 ? void 0 : _a.has(observable))) {
this.state = 1 /* DerivedObservable.State.DependenciesMightHaveChanged */;
for (const observer of this.observers) {
observer.handlePossibleChange(this);
}
}
},
handleChange: (observable, change) => {
var _a;
if (this.dependencies.has(observable) && !((_a = this.dependenciesToBeRemoved) === null || _a === void 0 ? void 0 : _a.has(observable))) {
let shouldReact = true;
if (this.willHandleChange) {
try {
shouldReact = this.willHandleChange({
observable,
change,
isChangeOf: (o) => o === observable
}, this.changeSummary);
}
catch (e) {
console.error(e);
}
}
const wasUpToDate = this.state === 3 /* DerivedObservable.State.UpToDate */;
if (shouldReact && (this.state === 1 /* DerivedObservable.State.DependenciesMightHaveChanged */ || wasUpToDate)) {
this.state = 2 /* DerivedObservable.State.Stale */;
if (wasUpToDate) {
for (const observer of this.observers) {
observer.handlePossibleChange(this);
}
}
}
}
}
};
}
addObserver(observer) {
var _a;
const shouldCallBeginUpdate = !this.observers.has(observer) && this.updateCount > 0;
super.addObserver(observer);
if (shouldCallBeginUpdate) {
if ((_a = this.removedObserversToCallEndUpdateOn) === null || _a === void 0 ? void 0 : _a.has(observer)) {
this.removedObserversToCallEndUpdateOn.delete(observer);
}
else {
observer.beginUpdate(this);
}
}
}
removeObserver(observer) {
if (this.observers.has(observer) && this.updateCount > 0) {
if (!this.removedObserversToCallEndUpdateOn) {
this.removedObserversToCallEndUpdateOn = new Set();
}
this.removedObserversToCallEndUpdateOn.add(observer);
}
super.removeObserver(observer);
}
}
exports.DerivedObservable = DerivedObservable;
(function (DerivedObservable) {
function create(compute, options) {
return new DerivedObservable(compute, options);
}
DerivedObservable.create = create;
})(DerivedObservable || (exports.DerivedObservable = DerivedObservable = {}));
//# sourceMappingURL=derived-observable.js.map