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 22abstract class ObservedPropertyAbstractPU<T> extends ObservedPropertyAbstract<T> 23implements ISinglePropertyChangeSubscriber<T>, IMultiPropertiesChangeSubscriber, IMultiPropertiesReadSubscriber 24// these interfaces implementations are all empty functions, overwrite FU base class implementations. 25{ 26 static readonly DelayedNotifyChangesEnum = class { 27 static readonly do_not_delay = 0; 28 static readonly delay_none_pending = 1; 29 static readonly delay_notification_pending = 2; 30 }; 31 32 private owningView_ : ViewPU; 33 public changeNotificationIsOngoing_: boolean = false; 34 35 // PU code stores object references to dependencies directly as class variable 36 // SubscriberManager is not used for lookup in PU code path to speedup updates 37 protected subscriberRefs_: Set<IPropertySubscriber>; 38 39 // when owning ViewPU is inActive, delay notifying changes 40 private delayedNotification_: number = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.do_not_delay; 41 42 // install when current value is ObservedObject and the value type is not using compatibility mode 43 // note value may change for union type variables when switching an object from one class to another. 44 protected shouldInstallTrackedObjectReadCb : boolean = false; 45 private dependentElmtIdsByProperty_ = new PropertyDependencies(); 46 47 constructor(subscriber: IPropertySubscriber, viewName: PropertyInfo) { 48 super(subscriber, viewName); 49 Object.defineProperty(this, 'owningView_', {writable: true, enumerable: false, value: undefined}); 50 Object.defineProperty(this, 'subscriberRefs_', 51 {writable: true, enumerable: false, value: new Set<IPropertySubscriber>()}); 52 if (subscriber) { 53 if (subscriber instanceof ViewPU) { 54 this.owningView_ = subscriber; 55 } else { 56 this.subscriberRefs_.add(subscriber); 57 } 58 } 59 } 60 61 aboutToBeDeleted() { 62 super.aboutToBeDeleted(); 63 this.subscriberRefs_.clear(); 64 this.owningView_ = undefined; 65 } 66 67 private decoratorInfo_?: string; 68 69 public setDecoratorInfo(decorate: string) { 70 this.decoratorInfo_ = decorate; 71 } 72 73 // dump info about variable decorator to string 74 // e.g. @State, @Link, etc. 75 public debugInfoDecorator() : string { 76 return this.decoratorInfo_; 77 } 78 79 // dump basic info about this variable to a string, non-recursive, no subscriber info 80 public debugInfo() : string { 81 const propSource : string | false = this.isPropSourceObservedPropertyFakeName(); 82 return (propSource) 83 ? `internal source (ObservedPropertyPU) of @Prop ${propSource} [${this.id__()}]` 84 : `${this.debugInfoDecorator()} '${this.info()}'[${this.id__()}] <${this.debugInfoOwningView()}>`; 85 } 86 87 public debugInfoOwningView() : string { 88 return `${this.owningView_ ? this.owningView_.debugInfo__() : 'owning @Component UNKNOWN'}`; 89 } 90 91 // dump info about owning view and subscribers (PU ones only) 92 // use function only for debug output and DFX. 93 public debugInfoSubscribers(): string { 94 return (this.owningView_) 95 ? `|--Owned by ${this.debugInfoOwningView()} ` 96 : `|--Owned by: owning view not known`; 97 } 98 99 public debugInfoSyncPeers(): string { 100 if (!this.subscriberRefs_.size) { 101 return '|--Sync peers: none'; 102 } 103 let result: string = `|--Sync peers: {`; 104 let sepa: string = ''; 105 this.subscriberRefs_.forEach((subscriber: IPropertySubscriber) => { 106 if ('debugInfo' in subscriber) { 107 result += `\n ${sepa}${(subscriber as ObservedPropertyAbstractPU<any>).debugInfo()}`; 108 sepa = ', '; 109 } 110 }); 111 result += '\n }'; 112 return result; 113 } 114 115 public debugInfoDependentElmtIds(dumpDependantElements: boolean = false): string { 116 return this.dependentElmtIdsByProperty_.dumpInfoDependencies(this.owningView_, dumpDependantElements); 117 } 118 119 public dumpDependentElmtIdsObj(isTrackedMode: boolean, isProfiler: boolean): PropertyDependenciesInfo { 120 return this.dependentElmtIdsByProperty_.dumpInfoDependenciesObj(this.owningView_, isTrackedMode, isProfiler); 121 } 122 123 public debugInfoElmtId(elmtId: number): string { 124 if (this.owningView_) { 125 return this.owningView_.debugInfoElmtId(elmtId) as string; 126 } 127 return '<unknown element id ' + elmtId + ', missing owning view>'; 128 } 129 130 public debugInfoDependentComponents(): string | Object { 131 let result: string = `|--Dependent elements: `; 132 let sepa: string = '; '; 133 let sepaDiff: string = ''; 134 const dumpDependantElements = true; 135 136 let queue: Array<ObservedPropertyAbstractPU<any>> = [this]; 137 let seen = new Set<ObservedPropertyAbstractPU<any>>(); 138 139 while (queue.length) { 140 let item = queue.shift(); 141 seen.add(item); 142 143 if (item !== this) { 144 result += `${sepa}${item.debugInfoOwningView()}`; 145 } 146 result += `${sepaDiff}${item.debugInfoDependentElmtIds(dumpDependantElements)}`; // new dependent elements 147 sepaDiff = ', '; 148 149 item.subscriberRefs_.forEach((subscriber: IPropertySubscriber) => { 150 if ((subscriber instanceof ObservedPropertyAbstractPU)) { 151 if (!seen.has(subscriber)) { 152 queue.push(subscriber); 153 } 154 } 155 }); 156 } 157 return result; 158 } 159 160 /**/ 161 public hasDependencies(): boolean { 162 return this.dependentElmtIdsByProperty_.hasDependencies(); 163 } 164 165 /* for @Prop value from source we need to generate a @State 166 that observes when this value changes. This ObservedPropertyPU 167 sits inside SynchedPropertyOneWayPU. 168 below methods invent a fake variable name for it 169 */ 170 protected getPropSourceObservedPropertyFakeName(): string { 171 return `${this.info()}_prop_fake_state_source___`; 172 } 173 174 protected isPropSourceObservedPropertyFakeName(): string | false { 175 return this.info() && this.info().endsWith('_prop_fake_state_source___') 176 ? this.info().substring(0, this.info().length - '_prop_fake_state_source___'.length) 177 : false; 178 } 179 180 public getOwningView(): TargetInfo { 181 return { componentName: this.owningView_?.constructor.name, id: this.owningView_?.id__() }; 182 } 183 184 public dumpSyncPeers(isProfiler: boolean, changedTrackPropertyName?: string): ObservedPropertyInfo<T>[] { 185 let res: ObservedPropertyInfo<T>[] = []; 186 this.subscriberRefs_.forEach((subscriber: IPropertySubscriber) => { 187 if ('debugInfo' in subscriber) { 188 const observedProp = subscriber as ObservedPropertyAbstractPU<any>; 189 res.push(stateMgmtDFX.getObservedPropertyInfo(observedProp, isProfiler, changedTrackPropertyName)); 190 } 191 }); 192 return res; 193 } 194 195 protected onDumpProfiler(changedTrackPropertyName?: string): void { 196 let res: DumpInfo = new DumpInfo(); 197 res.viewInfo = { componentName: this.owningView_?.constructor.name, id: this.owningView_?.id__() }; 198 res.observedPropertiesInfo.push(stateMgmtDFX.getObservedPropertyInfo(this, true, changedTrackPropertyName)); 199 if (this.owningView_) { 200 try { 201 this.owningView_.sendStateInfo(JSON.stringify(res)); 202 } catch (error) { 203 stateMgmtConsole.applicationError(`${this.debugInfo()} has error in sendStateInfo: ${(error as Error).message}`); 204 } 205 } 206 } 207 208 /* 209 Virtualized version of the subscription mechanism - add subscriber 210 Overrides implementation in ObservedPropertyAbstract<T> 211 */ 212 public addSubscriber(subscriber: ISinglePropertyChangeSubscriber<T>):void { 213 if (subscriber) { 214 // ObservedPropertyAbstract will also add subscriber to 215 // SubscriberManager map and to its own Set of subscribers as well 216 // Something to improve in the future for PU path. 217 // subscribeMe should accept IPropertySubscriber interface 218 super.subscribeMe(subscriber as ISinglePropertyChangeSubscriber<T>); 219 this.subscriberRefs_.add(subscriber); 220 } 221 } 222 223 /* 224 Virtualized version of the subscription mechanism - remove subscriber 225 Overrides implementation in ObservedPropertyAbstract<T> 226 */ 227 public removeSubscriber(subscriber: IPropertySubscriber, id?: number):void { 228 if (subscriber) { 229 this.subscriberRefs_.delete(subscriber); 230 if (!id) { 231 id = subscriber.id__(); 232 } 233 } 234 super.unlinkSuscriber(id); 235 } 236 237 /** 238 * put the property to delayed notification mode 239 * feature is only used for @StorageLink/Prop, @LocalStorageLink/Prop 240 */ 241 public enableDelayedNotification() : void { 242 if (this.delayedNotification_ !== ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending) { 243 stateMgmtConsole.debug(`${this.constructor.name}: enableDelayedNotification.`); 244 this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_none_pending; 245 } 246 } 247 248 /* 249 when moving from inActive to active state the owning ViewPU calls this function 250 This solution is faster than ViewPU polling each variable to send back a viewPropertyHasChanged event 251 with the elmtIds 252 253 returns undefined if variable has _not_ changed 254 returns dependentElementIds_ Set if changed. This Set is empty if variable is not used to construct the UI 255 */ 256 public moveElmtIdsForDelayedUpdate(isReused: boolean = false): Set<number> | undefined { 257 const result = (this.delayedNotification_ === ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending) ? 258 this.dependentElmtIdsByProperty_.getAllPropertyDependencies() : 259 undefined; 260 stateMgmtConsole.debug(`${this.debugInfo()}: moveElmtIdsForDelayedUpdate: elmtIds that need delayed update \ 261 ${result ? Array.from(result).toString() : 'no delayed notifications'} .`); 262 if (isReused && !this.owningView_.isViewActive()) { 263 this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_none_pending; 264 } else { 265 this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.do_not_delay; 266 } 267 return result; 268 } 269 270 protected notifyPropertyRead() { 271 stateMgmtConsole.error(`${this.debugInfo()}: notifyPropertyRead, DO NOT USE with PU. Use notifyReadCb mechanism.`); 272 273 } 274 275 // notify owning ViewPU and peers of a variable assignment 276 // also property/item changes to ObservedObjects of class object type, which use compat mode 277 // Date and Array are notified as if there had been an assignment. 278 protected notifyPropertyHasChangedPU() : void { 279 stateMgmtProfiler.begin('ObservedPropertyAbstractPU.notifyPropertyHasChangedPU'); 280 stateMgmtConsole.debug(`${this.debugInfo()}: notifyPropertyHasChangedPU.`); 281 if (this.owningView_) { 282 if (this.delayedNotification_ === ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.do_not_delay) { 283 // send viewPropertyHasChanged right away 284 this.owningView_.viewPropertyHasChanged(this.info_, this.dependentElmtIdsByProperty_.getAllPropertyDependencies()); 285 286 // send changed observed property to profiler 287 // only will be true when enable profiler 288 if (stateMgmtDFX.enableProfiler) { 289 stateMgmtConsole.debug(`notifyPropertyHasChangedPU in profiler mode`); 290 this.onDumpProfiler(); 291 } 292 } else { 293 // mark this @StorageLink/Prop or @LocalStorageLink/Prop variable has having changed and notification of viewPropertyHasChanged delivery pending 294 this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending; 295 } 296 } 297 this.subscriberRefs_.forEach((subscriber) => { 298 if (subscriber && typeof subscriber === 'object' && 'syncPeerHasChanged' in subscriber) { 299 (subscriber as unknown as PeerChangeEventReceiverPU<T>).syncPeerHasChanged(this); 300 } else { 301 stateMgmtConsole.warn(`${this.debugInfo()}: notifyPropertyHasChangedPU: unknown subscriber ID 'subscribedId' error!`); 302 } 303 }); 304 stateMgmtProfiler.end(); 305 } 306 307 308 // notify owning ViewPU and peers of a ObservedObject @Track property's assignment 309 protected notifyTrackedObjectPropertyHasChanged(changedPropertyName : string) : void { 310 stateMgmtProfiler.begin('ObservedPropertyAbstract.notifyTrackedObjectPropertyHasChanged'); 311 stateMgmtConsole.debug(`${this.debugInfo()}: notifyTrackedObjectPropertyHasChanged.`); 312 if (this.owningView_) { 313 if (this.delayedNotification_ == ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.do_not_delay) { 314 // send viewPropertyHasChanged right away 315 this.owningView_.viewPropertyHasChanged(this.info_, this.dependentElmtIdsByProperty_.getTrackedObjectPropertyDependencies(changedPropertyName, 'notifyTrackedObjectPropertyHasChanged')); 316 // send changed observed property to profiler 317 // only will be true when enable profiler 318 if (stateMgmtDFX.enableProfiler) { 319 stateMgmtConsole.debug(`notifyPropertyHasChangedPU in profiler mode`); 320 this.onDumpProfiler(changedPropertyName); 321 } 322 } else { 323 // mark this @StorageLink/Prop or @LocalStorageLink/Prop variable has having changed and notification of viewPropertyHasChanged delivery pending 324 this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending; 325 } 326 } 327 this.subscriberRefs_.forEach((subscriber) => { 328 if (subscriber) { 329 if ('syncPeerTrackedPropertyHasChanged' in subscriber) { 330 (subscriber as unknown as PeerChangeEventReceiverPU<T>).syncPeerTrackedPropertyHasChanged(this, changedPropertyName); 331 } else { 332 stateMgmtConsole.warn(`${this.debugInfo()}: notifyTrackedObjectPropertyHasChanged: unknown subscriber ID 'subscribedId' error!`); 333 } 334 } 335 }); 336 stateMgmtProfiler.end(); 337 } 338 339 protected abstract onOptimisedObjectPropertyRead(readObservedObject : T, readPropertyName : string, isTracked : boolean) : void; 340 341 public markDependentElementsDirty(view: ViewPU) { 342 // TODO ace-ets2bundle, framework, complicated apps need to update together 343 // this function will be removed after a short transition period. 344 stateMgmtConsole.warn(`${this.debugInfo()}: markDependentElementsDirty no longer supported. App will work ok, but 345 please update your ace-ets2bundle and recompile your application!`); 346 } 347 348 public numberOfSubscrbers(): number { 349 return this.subscriberRefs_.size + (this.owningView_ ? 1 : 0); 350 } 351 352 /* 353 type checking for any supported type, as required for union type support 354 see 1st parameter for explanation what is allowed 355 356 FIXME this expects the Map, Set patch to go in 357 */ 358 359 protected checkIsSupportedValue(value: T): boolean { 360 let res = ((typeof value === 'object' && typeof value !== 'function' && 361 !ObserveV2.IsObservedObjectV2(value) && 362 !ObserveV2.IsMakeObserved(value)) || 363 typeof value === 'number' || 364 typeof value === 'string' || 365 typeof value === 'boolean' || 366 value === undefined || 367 value === null); 368 369 if (!res) { 370 errorReport.varValueCheckFailed({ 371 customComponent: this.debugInfoOwningView(), 372 variableDeco: this.debugInfoDecorator(), 373 variableName: this.info(), 374 expectedType: `undefined, null, number, boolean, string, or Object but not function, not V2 @ObservedV2 / @Trace class, and makeObserved return value either`, 375 value: value 376 }); 377 } 378 return res; 379 } 380 381 /* 382 type checking for allowed Object type value 383 see 1st parameter for explanation what is allowed 384 385 FIXME this expects the Map, Set patch to go in 386 */ 387 protected checkIsObject(value: T): boolean { 388 let res = ((typeof value === 'object' && typeof value !== 'function' && !ObserveV2.IsObservedObjectV2(value)) || 389 value === undefined || value === null); 390 if (!res) { 391 errorReport.varValueCheckFailed({ 392 customComponent: this.debugInfoOwningView(), 393 variableDeco: this.debugInfoDecorator(), 394 variableName: this.info(), 395 expectedType: `undefined, null, Object including Array and instance of SubscribableAbstract, excluding function and V2 @Observed/@Trace object`, 396 value: value 397 }); 398 } 399 return res; 400 } 401 402 /* 403 type checking for allowed simple types value 404 see 1st parameter for explanation what is allowed 405 */ 406 protected checkIsSimple(value: T): boolean { 407 let res = (value === undefined || typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean'); 408 if (!res) { 409 errorReport.varValueCheckFailed({ 410 customComponent: this.debugInfoOwningView(), 411 variableDeco: this.debugInfoDecorator(), 412 variableName: this.info(), 413 expectedType: `undefined, number, boolean, string`, 414 value: value 415 }); 416 } 417 return res; 418 } 419 420 protected checkNewValue(isAllowedComment : string, newValue: T, validator: (value: T) => boolean) : boolean { 421 if (validator(newValue)) { 422 return true; 423 } 424 425 // report error 426 // current implementation throws an Exception 427 errorReport.varValueCheckFailed({ 428 customComponent: this.debugInfoOwningView(), 429 variableDeco: this.debugInfoDecorator(), 430 variableName: this.info(), 431 expectedType: isAllowedComment, 432 value: newValue 433 }); 434 435 // never gets here if errorReport.varValueCheckFailed throws an exception 436 // but should not depend on its implementation 437 return false; 438 } 439 440 441 /** 442 * factory function for concrete 'object' or 'simple' ObservedProperty object 443 * depending if value is Class object 444 * or simple type (boolean | number | string) 445 * @param value 446 * @param owningView 447 * @param thisPropertyName 448 * @returns either 449 */ 450 static CreateObservedObject<C>(value: C, owningView: IPropertySubscriber, thisPropertyName: PropertyInfo) 451 : ObservedPropertyAbstract<C> { 452 return (typeof value === 'object') ? 453 new ObservedPropertyObject(value, owningView, thisPropertyName) 454 : new ObservedPropertySimple(value, owningView, thisPropertyName); 455 } 456 457 458 /** 459 * If owning viewPU is currently rendering or re-rendering a UINode, return its elmtId 460 * return notRecordingDependencies (-1) otherwise 461 * ViewPU caches the info, it does not request the info from C++ side (by calling 462 * ViewStackProcessor.GetElmtIdToAccountFor(); as done in earlier implementation 463 */ 464 protected getRenderingElmtId() : number { 465 return (this.owningView_) ? this.owningView_.getCurrentlyRenderedElmtId() : UINodeRegisterProxy.notRecordingDependencies; 466 } 467 468 469 /** 470 * during 'get' access recording take note of the created component and its elmtId 471 * and add this component to the list of components who are dependent on this property 472 */ 473 protected recordPropertyDependentUpdate() : void { 474 const elmtId = this.getRenderingElmtId(); 475 if (elmtId === UINodeRegisterProxy.notRecordingDependencies) { 476 // not access recording 477 return; 478 } 479 if (elmtId === UINodeRegisterProxy.monitorIllegalV1V2StateAccess) { 480 const error = `${this.debugInfo()}: recordPropertyDependentUpdate trying to use V1 state to init/update child V2 @Component. Application error`; 481 stateMgmtConsole.applicationError(error); 482 throw new TypeError(error); 483 } 484 485 stateMgmtConsole.debug(`${this.debugInfo()}: recordPropertyDependentUpdate: add (state) variable dependency for elmtId ${elmtId}.`); 486 this.dependentElmtIdsByProperty_.addPropertyDependency(elmtId); 487 } 488 489 /** record dependency ObservedObject + propertyName -> elmtId 490 * caller ensures renderingElmtId >= 0 491 */ 492 protected recordTrackObjectPropertyDependencyForElmtId(renderingElmtId : number, readTrackedPropertyName : string) : void { 493 stateMgmtConsole.debug(`${this.debugInfo()}: recordTrackObjectPropertyDependency on elmtId ${renderingElmtId}.`); 494 this.dependentElmtIdsByProperty_.addTrackedObjectPropertyDependency(readTrackedPropertyName, renderingElmtId); 495 } 496 497 public purgeDependencyOnElmtId(rmElmtId: number): void { 498 this.dependentElmtIdsByProperty_?.purgeDependenciesForElmtId(rmElmtId); 499 } 500 501 public SetPropertyUnchanged(): void { 502 // function to be removed 503 // keep it here until transpiler is updated. 504 } 505 506 // unified Appstorage, what classes to use, and the API 507 public createLink(subscribeOwner?: IPropertySubscriber, 508 linkPropName?: PropertyInfo): ObservedPropertyAbstractPU<T> { 509 throw new Error(`${this.debugInfo()}: createLink: Can not create a AppStorage 'Link' from this property.`); 510 } 511 512 public createProp(subscribeOwner?: IPropertySubscriber, 513 linkPropName?: PropertyInfo): ObservedPropertyAbstractPU<T> { 514 throw new Error(`${this.debugInfo()}: createProp: Can not create a AppStorage 'Prop' from a @State property. `); 515 } 516 517 /* 518 Below empty functions required to keep as long as this class derives from FU version 519 ObservedPropertyAbstract. Need to overwrite these functions to do nothing for PU 520 */ 521 protected notifyHasChanged(_: T) { 522 stateMgmtConsole.error(`${this.debugInfo()}: notifyHasChanged, DO NOT USE with PU. Use syncPeerHasChanged() \ 523 or onTrackedObjectProperty(CompatMode)HasChangedPU()`); 524 } 525 526 527 /** 528 * event emitted by wrapped ObservedObject, when one of its property values changes 529 * for class objects when in compatibility mode 530 * for Array, Date instances always 531 * @param souceObject 532 * @param changedPropertyName 533 */ 534 public onTrackedObjectPropertyHasChangedPU(sourceObject: ObservedObject<T>, changedPropertyName: string) { 535 stateMgmtConsole.debug(`${this.debugInfo()}: onTrackedObjectPropertyHasChangedPU: property '${changedPropertyName}' of \ 536 object value has changed.`); 537 538 this.notifyTrackedObjectPropertyHasChanged(changedPropertyName); 539 } 540 541 /** 542 * event emitted by wrapped ObservedObject, when one of its property values changes 543 * for class objects when in compatibility mode 544 * for Array, Date instances always 545 * @param souceObject 546 * @param changedPropertyName 547 */ 548 public onTrackedObjectPropertyCompatModeHasChangedPU(sourceObject: ObservedObject<T>, changedPropertyName: string) { 549 stateMgmtConsole.debug(`${this.debugInfo()}: onTrackedObjectPropertyCompatModeHasChangedPU: property '${changedPropertyName}' of \ 550 object value has changed.`); 551 552 this.notifyPropertyHasChangedPU(); 553 } 554 555 556 hasChanged(_: T): void { 557 // unused for PU 558 // need to overwrite impl of base class with empty function. 559 } 560 561 propertyHasChanged(_?: PropertyInfo): void { 562 // unused for PU 563 // need to overwrite impl of base class with empty function. 564 } 565 566 propertyRead(_?: PropertyInfo): void { 567 // unused for PU 568 // need to overwrite impl of base class with empty function. 569 } 570} 571 572class PropertyDependencies { 573 574 // dependencies for property -> elmtId 575 // variable read during render adds elmtId 576 // variable assignment causes elmtId to need re-render. 577 // UINode with elmtId deletion needs elmtId to be removed from all records, see purgeDependenciesForElmtId 578 private propertyDependencies_: Set<number> = new Set<number>(); 579 580 public getAllPropertyDependencies(): Set<number> { 581 stateMgmtConsole.debug(` ... variable value assignment: returning affected elmtIds ${JSON.stringify(Array.from(this.propertyDependencies_))}`); 582 return this.propertyDependencies_; 583 } 584 585 public addPropertyDependency(elmtId: number): void { 586 this.propertyDependencies_.add(elmtId); 587 stateMgmtConsole.debug(` ... variable value read: add dependent elmtId ${elmtId} - updated list of dependent elmtIds: ${JSON.stringify(Array.from(this.propertyDependencies_))}`); 588 } 589 590 public purgeDependenciesForElmtId(rmElmtId: number): void { 591 stateMgmtConsole.debug(` ...purge all dependencies for elmtId ${rmElmtId} `); 592 this.propertyDependencies_.delete(rmElmtId); 593 stateMgmtConsole.debug(` ... updated list of elmtIds dependent on variable assignment: ${JSON.stringify(Array.from(this.propertyDependencies_))}`); 594 this.trackedObjectPropertyDependencies_.forEach((propertyElmtId, propertyName) => { 595 propertyElmtId.delete(rmElmtId); 596 stateMgmtConsole.debug(` ... updated dependencies on objectProperty '${propertyName}' changes: ${JSON.stringify(Array.from(propertyElmtId))}`); 597 }); 598 } 599 600 // dependencies on individual object properties 601 private trackedObjectPropertyDependencies_: Map<string, Set<number>> = new Map<string, Set<number>>(); 602 603 public addTrackedObjectPropertyDependency(readProperty: string, elmtId: number): void { 604 let dependentElmtIds = this.trackedObjectPropertyDependencies_.get(readProperty); 605 if (!dependentElmtIds) { 606 dependentElmtIds = new Set<number>(); 607 this.trackedObjectPropertyDependencies_.set(readProperty, dependentElmtIds); 608 } 609 dependentElmtIds.add(elmtId); 610 stateMgmtConsole.debug(` ... object property '${readProperty}' read: add dependent elmtId ${elmtId} - updated list of dependent elmtIds: ${JSON.stringify(Array.from(dependentElmtIds))}`); 611 } 612 613 public getTrackedObjectPropertyDependencies(changedObjectProperty: string, debugInfo: string): Set<number> { 614 const dependentElmtIds = this.trackedObjectPropertyDependencies_.get(changedObjectProperty) || new Set<number>(); 615 stateMgmtConsole.debug(` ... property '@Track ${changedObjectProperty}': returning affected elmtIds ${JSON.stringify(Array.from(dependentElmtIds))}`); 616 return dependentElmtIds; 617 } 618 619 public dumpInfoDependencies(owningView: ViewPU | undefined = undefined, dumpDependantElements): string { 620 const formatElmtId = owningView ? (elmtId => owningView.debugInfoElmtId(elmtId)) : (elmtId => elmtId); 621 let result: string = ''; 622 const arr = Array.from(this.propertyDependencies_).map(formatElmtId); 623 if (dumpDependantElements) { 624 return (arr.length > 1 ? arr.join(', ') : arr[0]); 625 } 626 if (!this.trackedObjectPropertyDependencies_.size) { 627 result += `dependencies: variable assignment affects elmtIds: ${Array.from(this.propertyDependencies_).map(formatElmtId).join(', ')}`; 628 return result; 629 } 630 this.trackedObjectPropertyDependencies_.forEach((propertyElmtId, propertyName) => { 631 result += `dependencies: property '@Track ${propertyName}' change affects elmtIds: ${Array.from(propertyElmtId).map(formatElmtId).join(', ')}`; 632 }); 633 return result; 634 } 635 636 637 public dumpInfoDependenciesObj(owningView: ViewPU | undefined = undefined, isTrackedMode: boolean, isProfiler: boolean): PropertyDependenciesInfo { 638 639 const formatElmtId = owningView ? (elmtId => owningView.debugInfoElmtId(elmtId, isProfiler)) : (elmtId => elmtId); 640 641 let trackedObjectPropertyDependenciesDumpInfo: Map<string, Array<ElementType | number | string>> = new Map<string, Array<ElementType | number | string>>(); 642 643 this.trackedObjectPropertyDependencies_.forEach((propertyElmtId, propertyName) => { 644 trackedObjectPropertyDependenciesDumpInfo.set(propertyName, Array.from(propertyElmtId).map(formatElmtId)); 645 }); 646 647 let PropertyDependenciesInfo: PropertyDependenciesInfo = { 648 mode: isTrackedMode ? 'Track Mode' : 'Compatible Mode', 649 trackPropertiesDependencies: MapInfo.toObject(trackedObjectPropertyDependenciesDumpInfo).keyToValue, 650 propertyDependencies: Array.from(this.propertyDependencies_).map(formatElmtId), 651 } 652 return PropertyDependenciesInfo; 653 } 654 655 public hasDependencies() : boolean { 656 return this.propertyDependencies_.size > 0 || this.trackedObjectPropertyDependencies_.size > 0; 657 } 658} 659