1/* 2 * Copyright (c) 2024 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 * 18 * This file includes only framework internal classes and functions 19 * non are part of SDK. Do not access from app. 20 * 21 */ 22 23/** 24 * ComputedV2 25 * one ComputedV2 object per @Computed variable 26 * computedId_ - similar to elmtId, identify one ComputedV2 in Observe.idToCmp Map 27 * observeObjectAccess = calculate the compute function and create dependencies to 28 * source variables 29 * fireChange - execute compute function and re-new dependencies with observeObjectAccess 30 */ 31class ComputedV2 { 32 33 // start with high number to avoid same id as elmtId for components. 34 public static readonly MIN_COMPUTED_ID = 0x1000000000; 35 private static nextCompId_ = ComputedV2.MIN_COMPUTED_ID; 36 37 // name of @Computed property 38 private prop_: string; 39 40 // owning object of @Computed property 41 private target_: object; 42 43 // computation function for property 44 private propertyComputeFunc_: () => any; 45 private computedId_: number; 46 47 public static readonly COMPUTED_PREFIX = '___comp_'; 48 public static readonly COMPUTED_CACHED_PREFIX = '___comp_cached_'; 49 50 constructor(target: object, prop: string, func: (...args: any[]) => any) { 51 this.target_ = target; 52 this.propertyComputeFunc_ = func; 53 this.computedId_ = ++ComputedV2.nextCompId_; 54 this.prop_ = prop; 55 } 56 57 public InitRun(): number { 58 let cachedProp = ComputedV2.COMPUTED_CACHED_PREFIX + this.prop_; 59 let propertyKey = this.prop_; 60 Reflect.defineProperty(this.target_, propertyKey, { 61 get() { 62 ObserveV2.getObserve().addRef(this, propertyKey); 63 return ObserveV2.autoProxyObject(this, cachedProp); 64 }, 65 set(_) { 66 const error = `@Computed ${propertyKey} is readonly, cannot set value for it`; 67 stateMgmtConsole.applicationError(error); 68 throw new Error(error); 69 }, 70 enumerable: true 71 }); 72 73 this.target_[cachedProp] = this.observeObjectAccess(); 74 return this.computedId_; 75 } 76 77 public fireChange(): void { 78 let newVal = this.observeObjectAccess(); 79 let cachedProp = ComputedV2.COMPUTED_CACHED_PREFIX + this.prop_; 80 if (this.target_[cachedProp] !== newVal) { 81 this.target_[cachedProp] = newVal; 82 ObserveV2.getObserve().fireChange(this.target_, this.prop_); 83 } 84 } 85 86 public getTarget() : object { 87 return this.target_; 88 } 89 90 public getProp() : string { 91 return this.prop_; 92 } 93 94 public getComputedFuncName(): string { 95 return this.propertyComputeFunc_.name; 96 } 97 98 // register current watchId while executing compute function 99 private observeObjectAccess(): Object | undefined { 100 ObserveV2.getObserve().startRecordDependencies(this, this.computedId_); 101 let ret; 102 103 try { 104 ret = this.propertyComputeFunc_.call(this.target_); 105 } catch (e) { 106 stateMgmtConsole.applicationError(`@Computed Exception caught for ${this.propertyComputeFunc_.name}`, e.toString()); 107 ret = undefined; 108 throw e; 109 } finally { 110 ObserveV2.getObserve().stopRecordDependencies(); 111 } 112 113 return ret; 114 } 115 116 public static clearComputedFromTarget(target: Object): void { 117 let meta: Object; 118 if (!target || typeof target !== 'object' || 119 !(meta = target[ObserveV2.COMPUTED_REFS]) || typeof meta !== 'object') { 120 return; 121 } 122 123 stateMgmtConsole.debug(`ComputedV2: clearComputedFromTarget: from target ${target.constructor?.name} computedIds to clear ${JSON.stringify(Array.from(Object.values(meta)))}`); 124 Array.from(Object.values(meta)).forEach((computed: ComputedV2) => ObserveV2.getObserve().clearWatch(computed.computedId_)); 125 } 126 127 /** 128 * @function resetComputed 129 * @description 130 * Recalculates the value of the specified computed property based on the current values 131 * of the input variables 132 * 133 * @param {string} computedName - The name of the computed property to be reset. 134 */ 135 public resetComputed(computedName: string) : void { 136 let newVal = this.observeObjectAccess(); 137 138 let cachedProp = ComputedV2.COMPUTED_CACHED_PREFIX + computedName; 139 if (this.target_[cachedProp] !== newVal) { 140 this.target_[cachedProp] = newVal; 141 ObserveV2.getObserve().fireChange(this.target_, computedName); 142 } 143 } 144 145} 146 147interface AsyncAddComputedJobEntryV2 { 148 target: Object; 149 name: string; 150} 151class AsyncAddComputedV2 { 152 static computedVars : Array<AsyncAddComputedJobEntryV2> = new Array<AsyncAddComputedJobEntryV2>(); 153 154 static addComputed(target: Object, name: string): void { 155 if (AsyncAddComputedV2.computedVars.length === 0) { 156 Promise.resolve(true) 157 .then(AsyncAddComputedV2.run) 158 .catch(error => { 159 stateMgmtConsole.applicationError(`Exception caught in @Computed ${name}`, error); 160 _arkUIUncaughtPromiseError(error); 161 }); 162 } 163 AsyncAddComputedV2.computedVars.push({target: target, name: name}); 164 } 165 166 static run(): void { 167 AsyncAddComputedV2.computedVars.forEach((computedVar : AsyncAddComputedJobEntryV2) => 168 ObserveV2.getObserve().constructComputed(computedVar.target, computedVar.name)); 169 // according to stackoverflow this is the fastest way to clear an Array 170 // ref https://stackoverflow.com/questions/1232040/how-do-i-empty-an-array-in-javascript 171 AsyncAddComputedV2.computedVars.length = 0; 172 } 173} 174