1/* 2 * Copyright (c) 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// @Track class property decorator 17// indicates to framework to track individual object property value changes 18function Track(target: Object, property: string) { 19 ConfigureStateMgmt.instance.usingPUObservedTrack(`@Track`, property); 20 Reflect.set(target, `${TrackedObject.___TRACKED_PREFIX}${property}`, true); 21 Reflect.set(target, TrackedObject.___IS_TRACKED_OPTIMISED, true); 22 stateMgmtConsole.debug(`class ${target.constructor?.name}: property @Track ${property}`); 23} 24 25class TrackedObject { 26 public static readonly ___IS_TRACKED_OPTIMISED = `___IS_TRACKED_OPTIMISED`; 27 public static readonly ___TRACKED_OPTI_ASSIGNMENT_FAKE_PROP_PROPERTY = `___OPTI_TRACKED_ASSIGNMENT_FAKE_PROP_PROPERTY`; 28 public static readonly ___TRACKED_OPTI_ASSIGNMENT_FAKE_OBJLINK_PROPERTY = `___OPTI_TRACKED_ASSIGNMENT_FAKE_OBJLINK_PROPERTY`; 29 public static readonly ___TRACKED_PREFIX = `___TRACKED_`; 30 private static readonly ___TRACKED_PREFIX_LEN = TrackedObject.___TRACKED_PREFIX.length; 31 32 public static isCompatibilityMode(obj: Object): boolean { 33 return !obj || (typeof obj !== 'object') || !Reflect.has(obj, TrackedObject.___IS_TRACKED_OPTIMISED); 34 } 35 36 public static needsPropertyReadCb(obj: Object): boolean { 37 return obj && (typeof obj === 'object') && Reflect.has(obj, TrackedObject.___IS_TRACKED_OPTIMISED); 38 } 39 40 /** 41 * @Track new object assignment optimization 42 * can apply if old and new value are object, instance of same class, do not use compat mode. 43 * in this case function returns true and calls supplied notifyTrackedPropertyChanged cb function 44 * for each @Tracked'ed property whose value actually changed. 45 * if optimisation can not be applied calls notifyPropertyChanged and returns false 46 */ 47 public static notifyObjectValueAssignment(obj1: Object, obj2: Object, 48 notifyPropertyChanged: (isFromSource) => void, // notify as assignment (none-optimised) 49 notifyTrackedPropertyChange: (propName) => void, obSelf: ObservedPropertyAbstractPU<any>): boolean { 50 if (!obj1 || !obj2 || (typeof obj1 !== 'object') || (typeof obj2 !== 'object') || 51 (obj1.constructor !== obj2.constructor) || 52 TrackedObject.isCompatibilityMode(obj1)) { 53 stateMgmtConsole.debug(`TrackedObject.notifyObjectValueAssignment notifying change as assignment (non-optimised)`); 54 notifyPropertyChanged.call(obSelf); 55 return false; 56 } 57 58 stateMgmtConsole.debug(`TrackedObject.notifyObjectValueAssignment notifying actually changed properties (optimised)`); 59 const obj1Raw = ObservedObject.GetRawObject(obj1); 60 const obj2Raw = ObservedObject.GetRawObject(obj2); 61 let shouldFakePropPropertyBeNotified: boolean = false; 62 Object.keys(obj2Raw) 63 .forEach(propName => { 64 // Collect only @Track'ed changed properties 65 if (Reflect.has(obj1Raw, `${TrackedObject.___TRACKED_PREFIX}${propName}`) && 66 (Reflect.get(obj1Raw, propName) !== Reflect.get(obj2Raw, propName))) { 67 stateMgmtConsole.debug(` ... '@Track ${propName}' value changed - notifying`); 68 notifyTrackedPropertyChange.call(obSelf, propName); 69 shouldFakePropPropertyBeNotified = true; 70 } else { 71 stateMgmtConsole.debug(` ... '${propName}' value unchanged or not @Track'ed - not notifying`); 72 } 73 }); 74 // notify this non-existing object property has changed only if some of the tracked properties changed. 75 // SynchedPropertyOneWay reset() report a 'read' on this property, thereby creating a dependency 76 // reporting the property as changed causes @Prop sync from source 77 if (shouldFakePropPropertyBeNotified) { 78 stateMgmtConsole.debug(` ... TrackedObject.___TRACKED_OPTI_ASSIGNMENT_FAKE_PROP_PROPERTY - notifying`); 79 notifyTrackedPropertyChange.call(obSelf, TrackedObject.___TRACKED_OPTI_ASSIGNMENT_FAKE_PROP_PROPERTY); 80 } 81 82 // always notify this non-existing object property has changed for SynchedPropertyNestedObject as 83 // the object has changed in assigment. 84 // SynchedPropertyNestedObject set() reports a 'read' on this property, thereby creating a dependency 85 // reporting the property as changed causes @ObjectLink sync from source 86 stateMgmtConsole.debug(` ... TrackedObject.___TRACKED_OPTI_ASSIGNMENT_FAKE_OBJLINK_PROPERTY - notifying`); 87 notifyTrackedPropertyChange.call(obSelf, TrackedObject.___TRACKED_OPTI_ASSIGNMENT_FAKE_OBJLINK_PROPERTY); 88 return true; 89 } 90}