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