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