Skip to content

Commit b8826fa

Browse files
authored
Merge pull request #13565 from RaananW/observableTrigger
[Suggestion] Notify added observer if observable was triggered.
2 parents d236991 + 41c71ba commit b8826fa

File tree

10 files changed

+125
-60
lines changed

10 files changed

+125
-60
lines changed

packages/dev/core/src/Engines/engine.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,7 +1279,7 @@ export class Engine extends ThinEngine {
12791279
public _renderLoop(): void {
12801280
if (!this._contextWasLost) {
12811281
let shouldRender = true;
1282-
if (!this.renderEvenInBackground && this._windowIsBackground) {
1282+
if (this.isDisposed || (!this.renderEvenInBackground && this._windowIsBackground)) {
12831283
shouldRender = false;
12841284
}
12851285

@@ -1874,7 +1874,7 @@ export class Engine extends ThinEngine {
18741874
}
18751875

18761876
// Release audio engine
1877-
if (Engine.Instances.length === 1 && Engine.audioEngine) {
1877+
if (EngineStore.Instances.length === 1 && Engine.audioEngine) {
18781878
Engine.audioEngine.dispose();
18791879
Engine.audioEngine = null;
18801880
}
@@ -1910,10 +1910,16 @@ export class Engine extends ThinEngine {
19101910
super.dispose();
19111911

19121912
// Remove from Instances
1913-
const index = Engine.Instances.indexOf(this);
1913+
const index = EngineStore.Instances.indexOf(this);
19141914

19151915
if (index >= 0) {
1916-
Engine.Instances.splice(index, 1);
1916+
EngineStore.Instances.splice(index, 1);
1917+
}
1918+
1919+
// no more engines left in the engine store? Notify!
1920+
if (!Engine.Instances.length) {
1921+
EngineStore.OnEnginesDisposedObservable.notifyObservers(this);
1922+
EngineStore.OnEnginesDisposedObservable.clear();
19171923
}
19181924

19191925
// Observables

packages/dev/core/src/Engines/engineStore.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Observable } from "../Misc/observable";
12
import type { Nullable } from "../types";
23

34
declare type Engine = import("./engine").Engine;
@@ -11,6 +12,12 @@ export class EngineStore {
1112
/** Gets the list of created engines */
1213
public static Instances = new Array<Engine>();
1314

15+
/**
16+
* Notifies when an engine was disposed.
17+
* Mainly used for static/cache cleanup
18+
*/
19+
public static OnEnginesDisposedObservable = new Observable<Engine>();
20+
1421
/** @internal */
1522
public static _LastCreatedScene: Nullable<Scene> = null;
1623

packages/dev/core/src/Engines/thinEngine.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,12 @@ export class ThinEngine {
266266
return this._webGLVersion;
267267
}
268268

269+
private _isDisposed = false;
270+
271+
public get isDisposed(): boolean {
272+
return this._isDisposed;
273+
}
274+
269275
// Updatable statics so stick with vars here
270276

271277
/**
@@ -1529,7 +1535,7 @@ export class ThinEngine {
15291535
*/
15301536
public stopRenderLoop(renderFunction?: () => void): void {
15311537
if (!renderFunction) {
1532-
this._activeRenderLoops = [];
1538+
this._activeRenderLoops.length = 0;
15331539
return;
15341540
}
15351541

@@ -1544,7 +1550,7 @@ export class ThinEngine {
15441550
public _renderLoop(): void {
15451551
if (!this._contextWasLost) {
15461552
let shouldRender = true;
1547-
if (!this.renderEvenInBackground && this._windowIsBackground) {
1553+
if (this._isDisposed || (!this.renderEvenInBackground && this._windowIsBackground)) {
15481554
shouldRender = false;
15491555
}
15501556

@@ -5346,6 +5352,7 @@ export class ThinEngine {
53465352
* Dispose and release all associated resources
53475353
*/
53485354
public dispose(): void {
5355+
this._isDisposed = true;
53495356
this.stopRenderLoop();
53505357

53515358
// Clear observables

packages/dev/core/src/Loading/Plugins/babylonFileLoader.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,7 @@ SceneLoader.RegisterPlugin({
905905
if (log !== null && SceneLoader.loggingLevel !== SceneLoader.NO_LOGGING) {
906906
Logger.Log(logOperation("importMesh", parsedData ? parsedData.producer : "Unknown") + (SceneLoader.loggingLevel !== SceneLoader.MINIMAL_LOGGING ? log : ""));
907907
}
908+
tempMaterialIndexContainer = {};
908909
}
909910

910911
return false;

packages/dev/core/src/Materials/Background/backgroundMaterial.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1057,7 +1057,7 @@ export class BackgroundMaterial extends PushMaterial {
10571057
* @param world The world matrix to bind.
10581058
*/
10591059
public bindOnlyWorldMatrix(world: Matrix): void {
1060-
this._activeEffect.setMatrix("world", world);
1060+
this._activeEffect!.setMatrix("world", world);
10611061
}
10621062

10631063
/**

packages/dev/core/src/Materials/material.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,12 @@ export class Material implements IAnimatable, IClipPlanesHolder {
202202
*/
203203
public static OnEventObservable = new Observable<Material>();
204204

205+
static {
206+
EngineStore.OnEnginesDisposedObservable.addOnce(() => {
207+
Material.OnEventObservable.clear();
208+
});
209+
}
210+
205211
/**
206212
* Custom callback helping to override the default shader used in the material.
207213
*/

packages/dev/core/src/Materials/materialPluginBase.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ export class MaterialPluginBase {
7070

7171
if (!material.pluginManager) {
7272
material.pluginManager = new MaterialPluginManager(material);
73+
material.onDisposeObservable.add(() => {
74+
material.pluginManager = undefined;
75+
});
7376
}
7477

7578
this._pluginDefineNames = defines;

packages/dev/core/src/Materials/pushMaterial.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type { SubMesh } from "../Meshes/subMesh";
1111
* @internal
1212
*/
1313
export class PushMaterial extends Material {
14-
protected _activeEffect: Effect;
14+
protected _activeEffect?: Effect;
1515

1616
protected _normalMatrix: Matrix = new Matrix();
1717

@@ -21,7 +21,7 @@ export class PushMaterial extends Material {
2121
}
2222

2323
public getEffect(): Effect {
24-
return this._storeEffectOnSubMeshes ? this._activeEffect : super.getEffect()!;
24+
return this._storeEffectOnSubMeshes ? this._activeEffect! : super.getEffect()!;
2525
}
2626

2727
public isReady(mesh?: AbstractMesh, useInstances?: boolean): boolean {
@@ -57,7 +57,7 @@ export class PushMaterial extends Material {
5757
* @param world the matrix to bind
5858
*/
5959
public bindOnlyWorldMatrix(world: Matrix): void {
60-
this._activeEffect.setMatrix("world", world);
60+
this._activeEffect!.setMatrix("world", world);
6161
}
6262

6363
/**
@@ -66,7 +66,7 @@ export class PushMaterial extends Material {
6666
* @param normalMatrix the matrix to bind
6767
*/
6868
public bindOnlyNormalMatrix(normalMatrix: Matrix): void {
69-
this._activeEffect.setMatrix("normalMatrix", normalMatrix);
69+
this._activeEffect!.setMatrix("normalMatrix", normalMatrix);
7070
}
7171

7272
public bind(world: Matrix, mesh?: Mesh): void {
@@ -88,4 +88,9 @@ export class PushMaterial extends Material {
8888
protected _mustRebind(scene: Scene, effect: Effect, visibility: number = 1) {
8989
return scene.isCachedMaterialInvalid(this, effect, visibility);
9090
}
91+
92+
public dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean, notBoundToMesh?: boolean) {
93+
this._activeEffect = undefined;
94+
super.dispose(forceDisposeEffect, forceDisposeTextures, notBoundToMesh);
95+
}
9196
}

packages/dev/core/src/Misc/observable.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ export class Observer<T> {
107107
export class Observable<T> {
108108
private _observers = new Array<Observer<T>>();
109109
private _numObserversMarkedAsDeleted = 0;
110+
private _hasNotified = false;
111+
private _lastNotifiedValue?: T;
110112

111113
/**
112114
* @internal
@@ -150,8 +152,16 @@ export class Observable<T> {
150152
/**
151153
* Creates a new observable
152154
* @param onObserverAdded defines a callback to call when a new observer is added
155+
* @param notifyIfTriggered If set to true the observable will notify when an observer was added if the observable was already triggered.
153156
*/
154-
constructor(onObserverAdded?: (observer: Observer<T>) => void) {
157+
constructor(
158+
onObserverAdded?: (observer: Observer<T>) => void,
159+
/**
160+
* If set to true the observable will notify when an observer was added if the observable was already triggered.
161+
* This is helpful to single-state observables like the scene onReady or the dispose observable.
162+
*/
163+
public notifyIfTriggered = false
164+
) {
155165
this._eventState = new EventState(0);
156166

157167
if (onObserverAdded) {
@@ -192,6 +202,13 @@ export class Observable<T> {
192202
this._onObserverAdded(observer);
193203
}
194204

205+
// If the observable was already triggered and the observable is set to notify if triggered, notify the new observer
206+
if (this._hasNotified && this.notifyIfTriggered) {
207+
if (this._lastNotifiedValue !== undefined) {
208+
this.notifyObserver(observer, this._lastNotifiedValue);
209+
}
210+
}
211+
195212
return observer;
196213
}
197214

@@ -306,6 +323,8 @@ export class Observable<T> {
306323
* @returns false if the complete observer chain was not processed (because one observer set the skipNextObservers to true)
307324
*/
308325
public notifyObservers(eventData: T, mask: number = -1, target?: any, currentTarget?: any, userInfo?: any): boolean {
326+
this._hasNotified = true;
327+
this._lastNotifiedValue = eventData;
309328
if (!this._observers.length) {
310329
return true;
311330
}
@@ -348,6 +367,8 @@ export class Observable<T> {
348367
* @param mask is used to filter observers defaults to -1
349368
*/
350369
public notifyObserver(observer: Observer<T>, eventData: T, mask: number = -1): void {
370+
this._hasNotified = true;
371+
this._lastNotifiedValue = eventData;
351372
if (observer._willBeUnregistered) {
352373
return;
353374
}
@@ -375,9 +396,18 @@ export class Observable<T> {
375396
* Clear the list of observers
376397
*/
377398
public clear(): void {
378-
this._observers = new Array<Observer<T>>();
399+
this._observers.length = 0;
379400
this._onObserverAdded = null;
380401
this._numObserversMarkedAsDeleted = 0;
402+
this.cleanLastNotifiedState();
403+
}
404+
405+
/**
406+
* Clean the last notified state - both the internal last value and the has-notified flag
407+
*/
408+
public cleanLastNotifiedState(): void {
409+
this._hasNotified = false;
410+
this._lastNotifiedValue = undefined;
381411
}
382412

383413
/**

packages/dev/core/src/scene.ts

Lines changed: 47 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2150,7 +2150,7 @@ export class Scene extends AbstractScene implements IAnimatable, IClipPlanesHold
21502150

21512151
/**
21522152
* Registers a function to be executed when the scene is ready
2153-
* @param {Function} func - the function to be executed
2153+
* @param func - the function to be executed
21542154
* @param checkRenderTargets true to also check that the meshes rendered as part of a render target are ready (default: false)
21552155
*/
21562156
public executeWhenReady(func: () => void, checkRenderTargets = false): void {
@@ -4694,52 +4694,6 @@ export class Scene extends AbstractScene implements IAnimatable, IClipPlanesHold
46944694
console.error("An error occurred while calling onDisposeObservable!", e);
46954695
}
46964696

4697-
this.onDisposeObservable.clear();
4698-
this.onBeforeRenderObservable.clear();
4699-
this.onAfterRenderObservable.clear();
4700-
this.onBeforeRenderTargetsRenderObservable.clear();
4701-
this.onAfterRenderTargetsRenderObservable.clear();
4702-
this.onAfterStepObservable.clear();
4703-
this.onBeforeStepObservable.clear();
4704-
this.onBeforeActiveMeshesEvaluationObservable.clear();
4705-
this.onAfterActiveMeshesEvaluationObservable.clear();
4706-
this.onBeforeParticlesRenderingObservable.clear();
4707-
this.onAfterParticlesRenderingObservable.clear();
4708-
this.onBeforeDrawPhaseObservable.clear();
4709-
this.onAfterDrawPhaseObservable.clear();
4710-
this.onBeforeAnimationsObservable.clear();
4711-
this.onAfterAnimationsObservable.clear();
4712-
this.onDataLoadedObservable.clear();
4713-
this.onBeforeRenderingGroupObservable.clear();
4714-
this.onAfterRenderingGroupObservable.clear();
4715-
this.onMeshImportedObservable.clear();
4716-
this.onBeforeCameraRenderObservable.clear();
4717-
this.onAfterCameraRenderObservable.clear();
4718-
this.onReadyObservable.clear();
4719-
this.onNewCameraAddedObservable.clear();
4720-
this.onCameraRemovedObservable.clear();
4721-
this.onNewLightAddedObservable.clear();
4722-
this.onLightRemovedObservable.clear();
4723-
this.onNewGeometryAddedObservable.clear();
4724-
this.onGeometryRemovedObservable.clear();
4725-
this.onNewTransformNodeAddedObservable.clear();
4726-
this.onTransformNodeRemovedObservable.clear();
4727-
this.onNewMeshAddedObservable.clear();
4728-
this.onMeshRemovedObservable.clear();
4729-
this.onNewSkeletonAddedObservable.clear();
4730-
this.onSkeletonRemovedObservable.clear();
4731-
this.onNewMaterialAddedObservable.clear();
4732-
this.onNewMultiMaterialAddedObservable.clear();
4733-
this.onMaterialRemovedObservable.clear();
4734-
this.onMultiMaterialRemovedObservable.clear();
4735-
this.onNewTextureAddedObservable.clear();
4736-
this.onTextureRemovedObservable.clear();
4737-
this.onPrePointerObservable.clear();
4738-
this.onPointerObservable.clear();
4739-
this.onPreKeyboardObservable.clear();
4740-
this.onKeyboardObservable.clear();
4741-
this.onActiveCameraChanged.clear();
4742-
47434697
this.detachControl();
47444698

47454699
// Detach cameras
@@ -4819,6 +4773,52 @@ export class Scene extends AbstractScene implements IAnimatable, IClipPlanesHold
48194773
}
48204774

48214775
this._engine.wipeCaches(true);
4776+
this.onDisposeObservable.clear();
4777+
this.onBeforeRenderObservable.clear();
4778+
this.onAfterRenderObservable.clear();
4779+
this.onBeforeRenderTargetsRenderObservable.clear();
4780+
this.onAfterRenderTargetsRenderObservable.clear();
4781+
this.onAfterStepObservable.clear();
4782+
this.onBeforeStepObservable.clear();
4783+
this.onBeforeActiveMeshesEvaluationObservable.clear();
4784+
this.onAfterActiveMeshesEvaluationObservable.clear();
4785+
this.onBeforeParticlesRenderingObservable.clear();
4786+
this.onAfterParticlesRenderingObservable.clear();
4787+
this.onBeforeDrawPhaseObservable.clear();
4788+
this.onAfterDrawPhaseObservable.clear();
4789+
this.onBeforeAnimationsObservable.clear();
4790+
this.onAfterAnimationsObservable.clear();
4791+
this.onDataLoadedObservable.clear();
4792+
this.onBeforeRenderingGroupObservable.clear();
4793+
this.onAfterRenderingGroupObservable.clear();
4794+
this.onMeshImportedObservable.clear();
4795+
this.onBeforeCameraRenderObservable.clear();
4796+
this.onAfterCameraRenderObservable.clear();
4797+
this.onAfterRenderCameraObservable.clear();
4798+
this.onReadyObservable.clear();
4799+
this.onNewCameraAddedObservable.clear();
4800+
this.onCameraRemovedObservable.clear();
4801+
this.onNewLightAddedObservable.clear();
4802+
this.onLightRemovedObservable.clear();
4803+
this.onNewGeometryAddedObservable.clear();
4804+
this.onGeometryRemovedObservable.clear();
4805+
this.onNewTransformNodeAddedObservable.clear();
4806+
this.onTransformNodeRemovedObservable.clear();
4807+
this.onNewMeshAddedObservable.clear();
4808+
this.onMeshRemovedObservable.clear();
4809+
this.onNewSkeletonAddedObservable.clear();
4810+
this.onSkeletonRemovedObservable.clear();
4811+
this.onNewMaterialAddedObservable.clear();
4812+
this.onNewMultiMaterialAddedObservable.clear();
4813+
this.onMaterialRemovedObservable.clear();
4814+
this.onMultiMaterialRemovedObservable.clear();
4815+
this.onNewTextureAddedObservable.clear();
4816+
this.onTextureRemovedObservable.clear();
4817+
this.onPrePointerObservable.clear();
4818+
this.onPointerObservable.clear();
4819+
this.onPreKeyboardObservable.clear();
4820+
this.onKeyboardObservable.clear();
4821+
this.onActiveCameraChanged.clear();
48224822
this._isDisposed = true;
48234823
}
48244824

0 commit comments

Comments
 (0)