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