1/* 2 * Copyright (c) 2022 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 * ObservedPropertyAbstractPU aka ObservedPropertyAbstract for partial update 18 * 19 * all definitions in this file are framework internal 20 */ 21 22 23abstract class ObservedPropertyAbstractPU<T> extends ObservedPropertyAbstract<T> 24implements ISinglePropertyChangeSubscriber<T>, IMultiPropertiesChangeSubscriber, IMultiPropertiesReadSubscriber 25// these interfaces implementations are all empty functions, overwrite FU base class implementations. 26{ 27 static readonly DelayedNotifyChangesEnum=class { 28 static readonly do_not_delay = 0; 29 static readonly delay_none_pending = 1; 30 static readonly delay_notification_pending = 2; 31 }; 32 33 private owningView_ : ViewPU = undefined; 34 35 private dependentElementIds_: Set<number> = new Set<number>(); 36 37 // PU code stores object references to dependencies directly as class variable 38 // SubscriberManager is not used for lookup in PU code path to speedup updates 39 protected subscriberRefs_: Set<IPropertySubscriber>; 40 41 // when owning ViewPU is inActive, delay notifying changes 42 private delayedNotification_: number = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.do_not_delay; 43 44 constructor(subscriber: IPropertySubscriber, viewName: PropertyInfo) { 45 super(subscriber, viewName); 46 Object.defineProperty(this, 'owningView_', {writable: true, enumerable: false}); 47 Object.defineProperty(this, 'subscriberRefs_', 48 {writable: true, enumerable: false, value: new Set<IPropertySubscriber>()}); 49 if(subscriber) { 50 if (subscriber instanceof ViewPU) { 51 this.owningView_ = subscriber; 52 } else { 53 this.subscriberRefs_.add(subscriber); 54 } 55 } 56 } 57 58 aboutToBeDeleted() { 59 super.aboutToBeDeleted(); 60 this.subscriberRefs_.clear(); 61 this.owningView_ = undefined; 62 } 63 64 // dump info about variable decorator to string 65 // e.g. @State/Provide, @Link/Consume, etc. 66 public abstract debugInfoDecorator() : string; 67 68 // dump basic info about this variable to a string, non-recursive, no subscriber info 69 public debugInfo() : string { 70 const propSource : string | false = this.isPropSourceObservedPropertyFakeName(); 71 return (propSource) 72 ? `internal source (ObservedPropertyPU) of @Prop ${propSource} [${this.id__()}]` 73 : `${this.debugInfoDecorator()} '${this.info()}'[${this.id__()}] <${this.debugInfoOwningView()}>`; 74 } 75 76 public debugInfoOwningView() : string { 77 return `${this.owningView_ ? this.owningView_.debugInfo() : "owning @Component UNKNOWN"}`; 78 } 79 80 // dump info about owning view and subscribers (PU ones only) 81 // use function only for debug output and DFX. 82 public debugInfoSubscribers(): string { 83 return (this.owningView_) 84 ? `owned by ${this.debugInfoOwningView()} ` 85 : `owned by: owning view not known`; 86 } 87 88 public debugInfoSyncPeers(): string { 89 if (!this.subscriberRefs_.size) { 90 return "sync peers: none"; 91 } 92 let result: string = `sync peers:\n`; 93 let sepa: string = ""; 94 this.subscriberRefs_.forEach((subscriber: IPropertySubscriber) => { 95 if ("debugInfo" in subscriber) { 96 result += ` ${sepa}${(subscriber as ObservedPropertyAbstractPU<any>).debugInfo()}`; 97 sepa = ", "; 98 } 99 }); 100 return result; 101 } 102 103 public debugInfoDependentElmtIds(): string { 104 if (!this.dependentElementIds_.size) { 105 return `dependent components: no dependent elmtIds`; 106 } 107 let result: string = this.dependentElementIds_.size < 25 108 ? `dependent components: ${this.dependentElementIds_.size} elmtIds: ` 109 : `WARNING: high number of dependent components (consider app redesign): ${this.dependentElementIds_.size} elmtIds: `; 110 let sepa: string = ""; 111 this.dependentElementIds_.forEach((elmtId: number) => { 112 result+=`${sepa}${this.owningView_.debugInfoElmtId(elmtId)}`; 113 sepa = ", "; 114 }); 115 return result; 116 } 117 118 /* for @Prop value from source we need to generate a @State 119 that observes when this value changes. This ObservedPropertyPU 120 sits inside SynchedPropertyOneWayPU. 121 below methods invent a fake variable name for it 122 */ 123 protected getPropSourceObservedPropertyFakeName(): string { 124 return `${this.info()}_prop_fake_state_source___`; 125 } 126 127 protected isPropSourceObservedPropertyFakeName(): string | false { 128 return this.info().endsWith("_prop_fake_state_source___") 129 ? this.info().substring(0, this.info().length - "_prop_fake_state_source___".length) 130 : false; 131 } 132 133 /* 134 Virtualized version of the subscription mechanism - add subscriber 135 Overrides implementation in ObservedPropertyAbstract<T> 136 */ 137 public addSubscriber(subscriber: ISinglePropertyChangeSubscriber<T>):void { 138 if (subscriber) { 139 // ObservedPropertyAbstract will also add subscriber to 140 // SubscriberManager map and to its own Set of subscribers as well 141 // Something to improve in the future for PU path. 142 // subscribeMe should accept IPropertySubscriber interface 143 super.subscribeMe(subscriber as ISinglePropertyChangeSubscriber<T>); 144 this.subscriberRefs_.add(subscriber); 145 } 146 } 147 148 /* 149 Virtualized version of the subscription mechanism - remove subscriber 150 Overrides implementation in ObservedPropertyAbstract<T> 151 */ 152 public removeSubscriber(subscriber: IPropertySubscriber, id?: number):void { 153 if (subscriber) { 154 this.subscriberRefs_.delete(subscriber); 155 if (!id) { 156 id = subscriber.id__(); 157 } 158 } 159 super.unlinkSuscriber(id); 160 } 161 162 163 /** 164 * put the property to delayed notification mode 165 * feature is only used for @StorageLink/Prop, @LocalStorageLink/Prop 166 */ 167 public enableDelayedNotification() : void { 168 if (this.delayedNotification_ != ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending) { 169 stateMgmtConsole.debug(`${this.constructor.name}: enableDelayedNotification.`); 170 this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_none_pending; 171 } 172 } 173 174 /* 175 when moving from inActive to active state the owning ViewPU calls this function 176 This solution is faster than ViewPU polling each variable to send back a viewPropertyHasChanged event 177 with the elmtIds 178 179 returns undefined if variable has _not_ changed 180 returns dependentElementIds_ Set if changed. This Set is empty if variable is not used to construct the UI 181 */ 182 public moveElmtIdsForDelayedUpdate(): Set<number> | undefined { 183 const result = (this.delayedNotification_ == ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending) 184 ? this.dependentElementIds_ 185 : undefined; 186 stateMgmtConsole.debug(`${this.debugInfo()}: moveElmtIdsForDelayedUpdate: elmtIds that need delayed update \ 187 ${result ? Array.from(result).toString() : 'no delayed notifications'} .`); 188 this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.do_not_delay; 189 return result; 190 } 191 192 protected notifyPropertyRead() { 193 stateMgmtConsole.error(`${this.debugInfo()}: notifyPropertyRead, DO NOT USE with PU. Use \ 194 notifyPropertyHasBeenReadPU`); 195 } 196 197 protected notifyPropertyHasBeenReadPU() { 198 stateMgmtConsole.debug(`${this.debugInfo()}: notifyPropertyHasBeenReadPU.`) 199 this.subscriberRefs_.forEach((subscriber) => { 200 if (subscriber) { 201 // TODO 202 // propertyHasBeenReadPU is not use in the code 203 // defined by interface that is not used either: PropertyReadEventListener 204 // Maybe compiler generated code has it? 205 if ('propertyHasBeenReadPU' in subscriber) { 206 (subscriber as unknown as PropertyReadEventListener<T>).propertyHasBeenReadPU(this); 207 } 208 } 209 }); 210 this.recordDependentUpdate(); 211 } 212 213 protected notifyPropertyHasChangedPU() { 214 stateMgmtConsole.debug(`${this.debugInfo()}: notifyPropertyHasChangedPU.`) 215 if (this.owningView_) { 216 if (this.delayedNotification_ == ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.do_not_delay) { 217 // send viewPropertyHasChanged right away 218 this.owningView_.viewPropertyHasChanged(this.info_, this.dependentElementIds_); 219 } else { 220 // mark this @StorageLink/Prop or @LocalStorageLink/Prop variable has having changed and notification of viewPropertyHasChanged delivery pending 221 this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending; 222 } 223 } 224 this.subscriberRefs_.forEach((subscriber) => { 225 if (subscriber) { 226 if ('syncPeerHasChanged' in subscriber) { 227 (subscriber as unknown as PeerChangeEventReceiverPU<T>).syncPeerHasChanged(this); 228 } else { 229 stateMgmtConsole.warn(`${this.debugInfo()}: notifyPropertyHasChangedPU: unknown subscriber ID 'subscribedId' error!`); 230 } 231 } 232 }); 233 } 234 235 236 public markDependentElementsDirty(view: ViewPU) { 237 // TODO ace-ets2bundle, framework, complicated apps need to update together 238 // this function will be removed after a short transition period. 239 stateMgmtConsole.warn(`${this.debugInfo()}: markDependentElementsDirty no longer supported. App will work ok, but 240 please update your ace-ets2bundle and recompile your application!`); 241 } 242 243 public numberOfSubscrbers(): number { 244 return this.subscriberRefs_.size + (this.owningView_ ? 1 : 0); 245 } 246 247 /* 248 type checking for any supported type, as required for union type support 249 see 1st parameter for explanation what is allowed 250 251 FIXME this expects the Map, Set patch to go in 252 */ 253 254 protected checkIsSupportedValue(value: T): boolean { 255 return this.checkNewValue( 256 `undefined, null, number, boolean, string, or Object but not function`, 257 value, 258 () => ((typeof value == "object" && typeof value != "function") 259 || typeof value == "number" || typeof value == "string" || typeof value == "boolean") 260 || (value == undefined || value == null) 261 ); 262 } 263 264 /* 265 type checking for allowed Object type value 266 see 1st parameter for explanation what is allowed 267 268 FIXME this expects the Map, Set patch to go in 269 */ 270 protected checkIsObject(value: T): boolean { 271 return this.checkNewValue( 272 `undefined, null, Object including Array and instance of SubscribableAbstract and excluding function, Set, and Map`, 273 value, 274 () => (value == undefined || value == null || (typeof value == "object")) 275 ); 276 } 277 278 /* 279 type checking for allowed simple types value 280 see 1st parameter for explanation what is allowed 281 */ 282 protected checkIsSimple(value: T): boolean { 283 return this.checkNewValue( 284 `undefined, number, boolean, string`, 285 value, 286 () => (value == undefined || typeof value == "number" || typeof value == "string" || typeof value == "boolean") 287 ); 288 } 289 290 protected checkNewValue(isAllowedComment : string, newValue: T, validator: (value: T) => boolean) : boolean { 291 if (validator(newValue)) { 292 return true; 293 } 294 295 // report error 296 // current implementation throws an Exception 297 errorReport.varValueCheckFailed({ 298 customComponent: this.debugInfoOwningView(), 299 variableDeco: this.debugInfoDecorator(), 300 variableName: this.info(), 301 expectedType: isAllowedComment, 302 value: newValue 303 }); 304 305 // never gets here if errorReport.varValueCheckFailed throws an exception 306 // but should nto depend on its implementation 307 return false; 308 } 309 310 311 /** 312 * factory function for concrete 'object' or 'simple' ObservedProperty object 313 * depending if value is Class object 314 * or simple type (boolean | number | string) 315 * @param value 316 * @param owningView 317 * @param thisPropertyName 318 * @returns either 319 */ 320 static CreateObservedObject<C>(value: C, owningView: IPropertySubscriber, thisPropertyName: PropertyInfo) 321 : ObservedPropertyAbstract<C> { 322 return (typeof value === "object") ? 323 new ObservedPropertyObject(value, owningView, thisPropertyName) 324 : new ObservedPropertySimple(value, owningView, thisPropertyName); 325 } 326 327 328 /** 329 * during 'get' access recording take note of the created component and its elmtId 330 * and add this component to the list of components who are dependent on this property 331 */ 332 protected recordDependentUpdate() { 333 const elmtId = ViewStackProcessor.GetElmtIdToAccountFor(); 334 if (elmtId < 0) { 335 // not access recording 336 return; 337 } 338 stateMgmtConsole.debug(`${this.debugInfo()}: recordDependentUpdate on elmtId ${elmtId}.`) 339 this.dependentElementIds_.add(elmtId); 340 } 341 342 343 public purgeDependencyOnElmtId(rmElmtId: number): void { 344 stateMgmtConsole.debug(`${this.debugInfo()}: purgeDependencyOnElmtId ${rmElmtId}`); 345 this.dependentElementIds_.delete(rmElmtId); 346 } 347 348 public SetPropertyUnchanged(): void { 349 // function to be removed 350 // keep it here until transpiler is updated. 351 } 352 353 // FIXME check, is this used from AppStorage. 354 // unified Appstorage, what classes to use, and the API 355 public createLink(subscribeOwner?: IPropertySubscriber, 356 linkPropName?: PropertyInfo): ObservedPropertyAbstractPU<T> { 357 throw new Error(`${this.debugInfo()}: createLink: Can not create a AppStorage 'Link' from this property.`); 358 } 359 360 public createProp(subscribeOwner?: IPropertySubscriber, 361 linkPropName?: PropertyInfo): ObservedPropertyAbstractPU<T> { 362 throw new Error(`${this.debugInfo()}: createProp: Can not create a AppStorage 'Prop' from a @State property. `); 363 } 364 365 /* 366 Below empty functions required to keep as long as this class derives from FU version 367 ObservedPropertyAbstract. Need to overwrite these functions to do nothing for PU 368 */ 369 protected notifyHasChanged(_: T) { 370 stateMgmtConsole.error(`${this.debugInfo()}: notifyHasChanged, DO NOT USE with PU. Use syncPeerHasChanged() \ 371 or objectPropertyHasChangedPU()`); 372 } 373 374 hasChanged(_: T): void { 375 // unused for PU 376 // need to overwrite impl of base class with empty function. 377 } 378 379 propertyHasChanged(_?: PropertyInfo): void { 380 // unused for PU 381 // need to overwrite impl of base class with empty function. 382 } 383 384 propertyRead(_?: PropertyInfo): void { 385 // unused for PU 386 // need to overwrite impl of base class with empty function. 387 } 388}