1/* 2 * Copyright (c) 2022-2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16/** 17 * ObservedPropertyObjectPU 18 * implementation of @State and @Provide decorated variables of type class object 19 * 20 * all definitions in this file are framework internal 21 * 22 * class that holds an actual property value of type T 23 * uses its base class to manage subscribers to this 24 * property. 25*/ 26 27class ObservedPropertyPU<T> extends ObservedPropertyAbstractPU<T> 28 implements PeerChangeEventReceiverPU<T>, ObservedObjectEventsPUReceiver<T> { 29 30 private wrappedValue_: T; 31 32 constructor(localInitValue: T, owningView: IPropertySubscriber, propertyName: PropertyInfo) { 33 super(owningView, propertyName); 34 35 this.setValueInternal(localInitValue); 36 } 37 38 aboutToBeDeleted(unsubscribeMe?: IPropertySubscriber) { 39 this.unsubscribeWrappedObject(); 40 this.removeSubscriber(unsubscribeMe); 41 super.aboutToBeDeleted(); 42 } 43 44 45 public debugInfoDecorator() : string { 46 return `@State/@Provide (class ObservedPropertyPU)`; 47 } 48 49 50 /** 51 * Called by a SynchedPropertyObjectTwoWayPU (@Link, @Consume) that uses this as sync peer when it has changed 52 * @param eventSource 53 */ 54 public syncPeerHasChanged(eventSource : ObservedPropertyAbstractPU<T>) : void { 55 stateMgmtConsole.debug(`${this.debugInfo()}: syncPeerHasChanged: from peer ${eventSource && eventSource.debugInfo && eventSource.debugInfo()}'.`); 56 this.notifyPropertyHasChangedPU(); 57 } 58 59 public syncPeerTrackedPropertyHasChanged(eventSource: ObservedPropertyAbstractPU<T>, changedTrackedObjectPropertyName: string): void { 60 stateMgmtConsole.debug(`${this.debugInfo()}: syncPeerTrackedPropertyHasChanged: from peer ${eventSource && eventSource.debugInfo && eventSource.debugInfo()}', changed property '${changedTrackedObjectPropertyName}'.`); 61 this.notifyTrackedObjectPropertyHasChanged(changedTrackedObjectPropertyName); 62 } 63 64 /** 65 * Wrapped ObservedObjectPU has changed 66 * @param souceObject 67 * @param changedPropertyName 68 */ 69 public objectPropertyHasChangedPU(souceObject: ObservedObject<T>, changedPropertyName : string) { 70 stateMgmtConsole.debug(`${this.debugInfo()}: objectPropertyHasChangedPU: contained ObservedObject property \ 71 '${changedPropertyName}' has changed.`) 72 this.notifyPropertyHasChangedPU(); 73 } 74 75 private unsubscribeWrappedObject() { 76 if (this.wrappedValue_) { 77 if (this.wrappedValue_ instanceof SubscribableAbstract) { 78 (this.wrappedValue_ as SubscribableAbstract).removeOwningProperty(this); 79 } else { 80 ObservedObject.removeOwningProperty(this.wrappedValue_, this); 81 82 // make sure the ObservedObject no longer has a read callback function 83 // assigned to it 84 ObservedObject.unregisterPropertyReadCb(this.wrappedValue_); 85 } 86 } 87 } 88 89 /* 90 actually update this.wrappedValue_ 91 called needs to do value change check 92 and also notify with this.aboutToChange(); 93 */ 94 private setValueInternal(newValue: T): boolean { 95 stateMgmtProfiler.begin("ObservedPropertyPU.setValueInternal"); 96 if (newValue === this.wrappedValue_) { 97 stateMgmtConsole.debug(`ObservedPropertyObjectPU[${this.id__()}, '${this.info() || "unknown"}'] newValue unchanged`); 98 stateMgmtProfiler.end(); 99 return false; 100 } 101 102 if (!this.checkIsSupportedValue(newValue)) { 103 stateMgmtProfiler.end(); 104 return false; 105 } 106 107 this.unsubscribeWrappedObject(); 108 if (!newValue || typeof newValue !== 'object') { 109 // undefined, null, simple type: 110 // nothing to subscribe to in case of new value undefined || null || simple type 111 this.wrappedValue_ = newValue; 112 } else if (newValue instanceof SubscribableAbstract) { 113 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: setValueInternal: new value is an SubscribableAbstract, subscribing to it.`); 114 this.wrappedValue_ = newValue; 115 (this.wrappedValue_ as unknown as SubscribableAbstract).addOwningProperty(this); 116 } else if (ObservedObject.IsObservedObject(newValue)) { 117 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: setValueInternal: new value is an ObservedObject already`); 118 ObservedObject.addOwningProperty(newValue, this); 119 this.shouldInstallTrackedObjectReadCb = TrackedObject.needsPropertyReadCb(newValue); 120 this.wrappedValue_ = newValue; 121 } else { 122 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: setValueInternal: new value is an Object, needs to be wrapped in an ObservedObject.`); 123 this.wrappedValue_ = ObservedObject.createNew(newValue, this); 124 this.shouldInstallTrackedObjectReadCb = TrackedObject.needsPropertyReadCb(this.wrappedValue_); 125 } 126 stateMgmtProfiler.end(); 127 return true; 128 } 129 130 public get(): T { 131 stateMgmtProfiler.begin("ObservedPropertyPU.get"); 132 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: get`); 133 this.recordPropertyDependentUpdate(); 134 if (this.shouldInstallTrackedObjectReadCb) { 135 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: get: @Track optimised mode. Will install read cb func if value is an object`); 136 ObservedObject.registerPropertyReadCb(this.wrappedValue_, this.onOptimisedObjectPropertyRead.bind(this)); 137 } else { 138 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: get: compatibility mode. `); 139 } 140 stateMgmtProfiler.end(); 141 return this.wrappedValue_; 142 } 143 144 public getUnmonitored(): T { 145 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: getUnmonitored.`); 146 // unmonitored get access , no call to notifyPropertyRead ! 147 return this.wrappedValue_; 148 } 149 150 public set(newValue: T): void { 151 if (this.wrappedValue_ === newValue) { 152 stateMgmtConsole.debug(`ObservedPropertyObjectPU[${this.id__()}, '${this.info() || "unknown"}']: set with unchanged value - ignoring.`); 153 return; 154 } 155 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: set: value about to changed.`); 156 const oldValue = this.wrappedValue_; 157 if (this.setValueInternal(newValue)) { 158 TrackedObject.notifyObjectValueAssignment(/* old value */ oldValue, /* new value */ this.wrappedValue_, 159 this.notifyPropertyHasChangedPU.bind(this), 160 this.notifyTrackedObjectPropertyHasChanged.bind(this)); 161 } 162 } 163 164 protected onOptimisedObjectPropertyRead(readObservedObject: T, readPropertyName: string, isTracked: boolean) : void { 165 stateMgmtProfiler.begin("ObservedProperty.onOptimisedObjectPropertyRead"); 166 const renderingElmtId = this.getRenderingElmtId(); 167 if (renderingElmtId >= 0) { 168 if (!isTracked) { 169 stateMgmtConsole.applicationError(`${this.debugInfo()}: onOptimisedObjectPropertyRead read NOT TRACKED property '${readPropertyName}' during rendering!`); 170 throw new Error(`Illegal usage of not @Track'ed property '${readPropertyName}' on UI!`); 171 } else { 172 stateMgmtConsole.debug(`${this.debugInfo()}: onOptimisedObjectPropertyRead: ObservedObject property '@Track ${readPropertyName}' read.`); 173 // only record dependency when 174 // 1 - currently rendering or re-rendering 175 // TODO room for further optimization: if not an expression in updateFunc, only first time render needs to record 176 // because there can be change to depended variables unless one of the bindings is a JS expression 177 // 2 - the changed ObservedObject is the wrapped object. The situation where it can be different is after a value assignment. 178 if (this.getUnmonitored() === readObservedObject) { 179 this.recordTrackObjectPropertyDependencyForElmtId(renderingElmtId, readPropertyName) 180 } 181 } 182 } 183 stateMgmtProfiler.end(); 184 } 185} 186 187// class definitions for backward compatibility 188class ObservedPropertyObjectPU<T> extends ObservedPropertyPU<T> { 189 190} 191 192class ObservedPropertySimplePU<T> extends ObservedPropertyPU<T> { 193 194} 195