1/* 2 * Copyright (c) 2022 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 * SynchedPropertyObjectTwoWayPU 18 * implementation of @Link and @Consume decorated variables of type class object 19 * 20 * all definitions in this file are framework internal 21*/ 22 23class SynchedPropertyTwoWayPU<C> extends ObservedPropertyAbstractPU<C> 24 implements PeerChangeEventReceiverPU<C>, ObservedObjectEventsPUReceiver<C> { 25 26 private source_: ObservedPropertyObjectAbstract<C>; 27 28 29 constructor(source: ObservedPropertyObjectAbstract<C>, 30 owningChildView: IPropertySubscriber, 31 thisPropertyName: PropertyInfo) { 32 super(owningChildView, thisPropertyName); 33 this.source_ = source; 34 if (this.source_) { 35 // register to the parent property 36 this.source_.addSubscriber(this); 37 this.shouldInstallTrackedObjectReadCb = TrackedObject.needsPropertyReadCb(this.source_.getUnmonitored()); 38 } else { 39 throw new SyntaxError(`${this.debugInfo()}: constructor: source variable in parent/ancestor @Component must be defined. Application error!`); 40 } 41 this.setDecoratorInfo("@Link"); 42 } 43 44 /* 45 like a destructor, need to call this before deleting 46 the property. 47 */ 48 aboutToBeDeleted() { 49 // unregister from parent of this link 50 if (this.source_) { 51 this.source_.removeSubscriber(this); 52 53 // unregister from the ObservedObject 54 ObservedObject.removeOwningProperty(this.source_.getUnmonitored(), this); 55 } 56 super.aboutToBeDeleted(); 57 } 58 59 private isStorageLinkProp(): boolean { 60 return (this.source_ && this.source_ instanceof ObservedPropertyAbstract && (!(this.source_ instanceof ObservedPropertyAbstractPU))); 61 } 62 63 private setObject(newValue: C): void { 64 if (!this.source_) { 65 throw new SyntaxError(`${this.debugInfo()}: setObject (assign a new value), no source variable in parent/ancestor \ 66 @Component. Application error.`); 67 } 68 69 if (this.getUnmonitored() === newValue) { 70 stateMgmtConsole.debug(`SynchedPropertyObjectTwoWayPU[${this.id__()}IP, '${this.info() || 'unknown'}']: set with unchanged value - ignoring.`); 71 return; 72 } 73 74 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: set: value has changed.`); 75 76 if (this.checkIsSupportedValue(newValue)) { 77 // the source_ ObservedProperty will call: this.syncPeerHasChanged(newValue); 78 this.source_.set(newValue); 79 this.shouldInstallTrackedObjectReadCb = TrackedObject.needsPropertyReadCb(newValue); 80 } 81 } 82 83 84 /** 85 * Called when sync peer ObservedPropertyObject or SynchedPropertyObjectTwoWay has changed value 86 * that peer can be in either parent or child component if 'this' is used for a @Link 87 * that peer can be in either ancestor or descendant component if 'this' is used for a @Consume 88 * @param eventSource 89 */ 90 public syncPeerHasChanged(eventSource: ObservedPropertyAbstractPU<C>): void { 91 stateMgmtProfiler.begin('SynchedPropertyTwoWayPU.syncPeerHasChanged'); 92 if (!this.changeNotificationIsOngoing_) { 93 stateMgmtConsole.debug(`${this.debugInfo()}: syncPeerHasChanged: from peer '${eventSource && eventSource.debugInfo && eventSource.debugInfo()}' .`) 94 this.notifyPropertyHasChangedPU(); 95 } 96 stateMgmtProfiler.end(); 97 } 98 99 public syncPeerTrackedPropertyHasChanged(eventSource: ObservedPropertyAbstractPU<C>, changedTrackedObjectPropertyName: string): void { 100 stateMgmtProfiler.begin('SynchedPropertyTwoWayPU.syncPeerTrackedPropertyHasChanged'); 101 if (!this.changeNotificationIsOngoing_) { 102 stateMgmtConsole.debug(`${this.debugInfo()}: syncPeerTrackedPropertyHasChanged: from peer '${eventSource && eventSource.debugInfo && eventSource.debugInfo()}', changed property '${changedTrackedObjectPropertyName}'.`); 103 this.notifyTrackedObjectPropertyHasChanged(changedTrackedObjectPropertyName); 104 } 105 stateMgmtProfiler.end(); 106 } 107 108 public getUnmonitored(): C { 109 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: getUnmonitored.`); 110 return (this.source_ ? this.source_.getUnmonitored() : undefined); 111 } 112 113 // get 'read through` from the ObservedProperty 114 public get(): C { 115 stateMgmtProfiler.begin('SynchedPropertyTwoWayPU.get'); 116 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: get`) 117 this.recordPropertyDependentUpdate(); 118 const result = this.getUnmonitored(); 119 if (this.shouldInstallTrackedObjectReadCb) { 120 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: get: @Track optimised mode. Will install read cb func if value is an object`); 121 ObservedObject.registerPropertyReadCb(result, this.onOptimisedObjectPropertyRead, this); 122 } else { 123 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: get: compatibility mode. `); 124 } 125 126 stateMgmtProfiler.end(); 127 return result; 128 } 129 130 // set 'writes through` to the ObservedProperty 131 public set(newValue: C): void { 132 stateMgmtProfiler.begin('SynchedPropertyTwoWayPU.set'); 133 if (this.getUnmonitored() === newValue) { 134 stateMgmtConsole.debug(`SynchedPropertyObjectTwoWayPU[${this.id__()}IP, '${this.info() || 'unknown'}']: set with unchanged value - nothing to do.`); 135 stateMgmtProfiler.end(); 136 return; 137 } 138 139 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: set: value about to change.`); 140 141 // avoid circular notifications @Link -> source @State -> other but also back to same @Link 142 this.changeNotificationIsOngoing_ = true; 143 let oldValue = this.getUnmonitored(); 144 this.setObject(newValue); 145 TrackedObject.notifyObjectValueAssignment(/* old value */ oldValue, /* new value */ newValue, 146 this.notifyPropertyHasChangedPU, 147 this.notifyTrackedObjectPropertyHasChanged, this); 148 this.changeNotificationIsOngoing_ = false; 149 stateMgmtProfiler.end(); 150 } 151 152 153 protected onOptimisedObjectPropertyRead(readObservedObject: C, readPropertyName: string, isTracked: boolean): void { 154 stateMgmtProfiler.begin('SynchedPropertyTwoWayPU.onOptimisedObjectPropertyRead'); 155 const renderingElmtId = this.getRenderingElmtId(); 156 if (renderingElmtId >= 0) { 157 if (!isTracked) { 158 stateMgmtConsole.applicationError(`${this.debugInfo()}: onOptimisedObjectPropertyRead read NOT TRACKED property '${readPropertyName}' during rendering!`); 159 throw new Error(`Illegal usage of not @Track'ed property '${readPropertyName}' on UI!`); 160 } else { 161 stateMgmtConsole.debug(`${this.debugInfo()}: onOptimisedObjectPropertyRead: ObservedObject property '@Track ${readPropertyName}' read.`); 162 if (this.getUnmonitored() === readObservedObject) { 163 this.recordTrackObjectPropertyDependencyForElmtId(renderingElmtId, readPropertyName) 164 } 165 } 166 } 167 stateMgmtProfiler.end(); 168 } 169} 170 171// class definitions for backward compatibility 172class SynchedPropertyObjectTwoWayPU<C> extends SynchedPropertyTwoWayPU<C> { 173 174} 175 176class SynchedPropertySimpleTwoWayPU<T> extends SynchedPropertyTwoWayPU<T> { 177 178} 179 180