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 // register current watchId while executing compute function 95 private observeObjectAccess(): Object | undefined { 96 ObserveV2.getObserve().startRecordDependencies(this, this.computedId_); 97 let ret; 98 99 try { 100 ret = this.propertyComputeFunc_.call(this.target_); 101 } catch (e) { 102 stateMgmtConsole.applicationError(`@Computed Exception caught for ${this.propertyComputeFunc_.name}`, e.toString()); 103 ret = undefined; 104 throw e; 105 } finally { 106 ObserveV2.getObserve().stopRecordDependencies(); 107 } 108 109 return ret; 110 } 111 112 public static clearComputedFromTarget(target: Object): void { 113 let meta: Object; 114 if (!target || typeof target !== 'object' || 115 !(meta = target[ObserveV2.COMPUTED_REFS]) || typeof meta !== 'object') { 116 return; 117 } 118 119 stateMgmtConsole.debug(`ComputedV2: clearComputedFromTarget: from target ${target.constructor?.name} computedIds to clear ${JSON.stringify(Array.from(Object.values(meta)))}`); 120 Array.from(Object.values(meta)).forEach((computed: ComputedV2) => ObserveV2.getObserve().clearWatch(computed.computedId_)); 121 } 122} 123 124interface AsyncAddComputedJobEntryV2 { 125 target: Object; 126 name: string; 127} 128class AsyncAddComputedV2 { 129 static computedVars : Array<AsyncAddComputedJobEntryV2> = new Array<AsyncAddComputedJobEntryV2>(); 130 131 static addComputed(target: Object, name: string): void { 132 if (AsyncAddComputedV2.computedVars.length === 0) { 133 Promise.resolve(true) 134 .then(AsyncAddComputedV2.run) 135 .catch(error => { 136 stateMgmtConsole.applicationError(`Exception caught in @Computed ${name}`, error); 137 _arkUIUncaughtPromiseError(error); 138 }); 139 } 140 AsyncAddComputedV2.computedVars.push({target: target, name: name}); 141 } 142 143 static run(): void { 144 AsyncAddComputedV2.computedVars.forEach((computedVar : AsyncAddComputedJobEntryV2) => 145 ObserveV2.getObserve().constructComputed(computedVar.target, computedVar.name)); 146 // according to stackoverflow this is the fastest way to clear an Array 147 // ref https://stackoverflow.com/questions/1232040/how-do-i-empty-an-array-in-javascript 148 AsyncAddComputedV2.computedVars.length = 0; 149 } 150} 151