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