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 this.setDecoratorInfo('@State'); 37 } 38 39 aboutToBeDeleted(unsubscribeMe?: IPropertySubscriber) { 40 this.unsubscribeWrappedObject(); 41 this.removeSubscriber(unsubscribeMe); 42 super.aboutToBeDeleted(); 43 } 44 45 /** 46 * Called by a SynchedPropertyObjectTwoWayPU (@Link, @Consume) that uses this as sync peer when it has changed 47 * @param eventSource 48 */ 49 public syncPeerHasChanged(eventSource : ObservedPropertyAbstractPU<T>) : void { 50 stateMgmtConsole.debug(`${this.debugInfo()}: syncPeerHasChanged: from peer ${eventSource && eventSource.debugInfo && eventSource.debugInfo()}'.`); 51 this.notifyPropertyHasChangedPU(); 52 } 53 54 public syncPeerTrackedPropertyHasChanged(eventSource: ObservedPropertyAbstractPU<T>, changedTrackedObjectPropertyName: string): void { 55 stateMgmtConsole.debug(`${this.debugInfo()}: syncPeerTrackedPropertyHasChanged: from peer ${eventSource && eventSource.debugInfo && eventSource.debugInfo()}', changed property '${changedTrackedObjectPropertyName}'.`); 56 this.notifyTrackedObjectPropertyHasChanged(changedTrackedObjectPropertyName); 57 } 58 59 /** 60 * Wrapped ObservedObjectPU has changed 61 * @param souceObject 62 * @param changedPropertyName 63 */ 64 public objectPropertyHasChangedPU(souceObject: ObservedObject<T>, changedPropertyName : string) { 65 stateMgmtConsole.debug(`${this.debugInfo()}: objectPropertyHasChangedPU: contained ObservedObject property \ 66 '${changedPropertyName}' has changed.`); 67 this.notifyPropertyHasChangedPU(); 68 } 69 70 private unsubscribeWrappedObject() { 71 if (this.wrappedValue_) { 72 if (this.wrappedValue_ instanceof SubscribableAbstract) { 73 (this.wrappedValue_ as SubscribableAbstract).removeOwningProperty(this); 74 } else { 75 ObservedObject.removeOwningProperty(this.wrappedValue_, this); 76 77 // make sure the ObservedObject no longer has a read callback function 78 // assigned to it 79 ObservedObject.unregisterPropertyReadCb(this.wrappedValue_); 80 } 81 } 82 } 83 84 /* 85 actually update this.wrappedValue_ 86 called needs to do value change check 87 and also notify with this.aboutToChange(); 88 */ 89 private setValueInternal(newValue: T): boolean { 90 stateMgmtProfiler.begin('ObservedPropertyPU.setValueInternal'); 91 if (newValue === this.wrappedValue_) { 92 stateMgmtConsole.debug(`ObservedPropertyObjectPU[${this.id__()}, '${this.info() || 'unknown'}'] newValue unchanged`); 93 stateMgmtProfiler.end(); 94 return false; 95 } 96 97 if (!this.checkIsSupportedValue(newValue)) { 98 stateMgmtProfiler.end(); 99 return false; 100 } 101 102 this.unsubscribeWrappedObject(); 103 if (!newValue || typeof newValue !== 'object') { 104 // undefined, null, simple type: 105 // nothing to subscribe to in case of new value undefined || null || simple type 106 this.wrappedValue_ = newValue; 107 } else if (newValue instanceof SubscribableAbstract) { 108 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: setValueInternal: new value is an SubscribableAbstract, subscribing to it.`); 109 this.wrappedValue_ = newValue; 110 (this.wrappedValue_ as unknown as SubscribableAbstract).addOwningProperty(this); 111 } else if (ObservedObject.IsObservedObject(newValue)) { 112 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: setValueInternal: new value is an ObservedObject already`); 113 ObservedObject.addOwningProperty(newValue, this); 114 this.shouldInstallTrackedObjectReadCb = TrackedObject.needsPropertyReadCb(newValue); 115 this.wrappedValue_ = newValue; 116 } else { 117 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: setValueInternal: new value is an Object, needs to be wrapped in an ObservedObject.`); 118 this.wrappedValue_ = ObservedObject.createNew(newValue, this); 119 this.shouldInstallTrackedObjectReadCb = TrackedObject.needsPropertyReadCb(this.wrappedValue_); 120 } 121 stateMgmtProfiler.end(); 122 return true; 123 } 124 125 public get(): T { 126 stateMgmtProfiler.begin('ObservedPropertyPU.get'); 127 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: get`); 128 this.recordPropertyDependentUpdate(); 129 if (this.shouldInstallTrackedObjectReadCb) { 130 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: get: @Track optimised mode. Will install read cb func if value is an object`); 131 ObservedObject.registerPropertyReadCb(this.wrappedValue_, this.onOptimisedObjectPropertyRead, this); 132 } else { 133 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: get: compatibility mode. `); 134 } 135 stateMgmtProfiler.end(); 136 return this.wrappedValue_; 137 } 138 139 public getUnmonitored(): T { 140 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: getUnmonitored.`); 141 // unmonitored get access , no call to notifyPropertyRead ! 142 return this.wrappedValue_; 143 } 144 145 public set(newValue: T): void { 146 if (this.wrappedValue_ === newValue) { 147 stateMgmtConsole.debug(`ObservedPropertyObjectPU[${this.id__()}, '${this.info() || 'unknown'}']: set with unchanged value - ignoring.`); 148 return; 149 } 150 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: set: value about to changed.`); 151 const oldValue = this.wrappedValue_; 152 if (this.setValueInternal(newValue)) { 153 TrackedObject.notifyObjectValueAssignment(/* old value */ oldValue, /* new value */ this.wrappedValue_, 154 this.notifyPropertyHasChangedPU, 155 this.notifyTrackedObjectPropertyHasChanged, this); 156 } 157 } 158 159 protected onOptimisedObjectPropertyRead(readObservedObject: T, readPropertyName: string, isTracked: boolean) : void { 160 stateMgmtProfiler.begin('ObservedProperty.onOptimisedObjectPropertyRead'); 161 const renderingElmtId = this.getRenderingElmtId(); 162 if (renderingElmtId >= 0) { 163 if (!isTracked) { 164 stateMgmtConsole.applicationError(`${this.debugInfo()}: onOptimisedObjectPropertyRead read NOT TRACKED property '${readPropertyName}' during rendering!`); 165 throw new Error(`Illegal usage of not @Track'ed property '${readPropertyName}' on UI!`); 166 } else { 167 stateMgmtConsole.debug(`${this.debugInfo()}: onOptimisedObjectPropertyRead: ObservedObject property '@Track ${readPropertyName}' read.`); 168 // only record dependency when 169 // 1 - currently rendering or re-rendering 170 // TODO room for further optimization: if not an expression in updateFunc, only first time render needs to record 171 // because there can be change to depended variables unless one of the bindings is a JS expression 172 // 2 - the changed ObservedObject is the wrapped object. The situation where it can be different is after a value assignment. 173 if (this.getUnmonitored() === readObservedObject) { 174 this.recordTrackObjectPropertyDependencyForElmtId(renderingElmtId, readPropertyName); 175 } 176 } 177 } 178 stateMgmtProfiler.end(); 179 } 180} 181 182// class definitions for backward compatibility 183class ObservedPropertyObjectPU<T> extends ObservedPropertyPU<T> { 184 185} 186 187class ObservedPropertySimplePU<T> extends ObservedPropertyPU<T> { 188 189} 190