• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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