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(): ViewPUInfo { 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 // FIXME enable the check when V1-V2 interoperability is forbidden 361 // && !ObserveV2.IsProxiedObservedV2(value)) 362 let res = ((typeof value === 'object' && typeof value !== 'function' && 363 !ObserveV2.IsObservedObjectV2(value) && 364 !ObserveV2.IsMakeObserved(value)) || 365 typeof value === 'number' || 366 typeof value === 'string' || 367 typeof value === 'boolean' || 368 value === undefined || 369 value === null); 370 371 if (!res) { 372 errorReport.varValueCheckFailed({ 373 customComponent: this.debugInfoOwningView(), 374 variableDeco: this.debugInfoDecorator(), 375 variableName: this.info(), 376 expectedType: `undefined, null, number, boolean, string, or Object but not function, not V2 @ObservedV2 / @Trace class, and makeObserved return value either`, 377 value: value 378 }); 379 } 380 return res; 381 } 382 383 /* 384 type checking for allowed Object type value 385 see 1st parameter for explanation what is allowed 386 387 FIXME this expects the Map, Set patch to go in 388 */ 389 protected checkIsObject(value: T): boolean { 390 let res = ((typeof value === 'object' && typeof value !== 'function' && !ObserveV2.IsObservedObjectV2(value)) || 391 value === undefined || value === null); 392 if (!res) { 393 errorReport.varValueCheckFailed({ 394 customComponent: this.debugInfoOwningView(), 395 variableDeco: this.debugInfoDecorator(), 396 variableName: this.info(), 397 expectedType: `undefined, null, Object including Array and instance of SubscribableAbstract and excluding function and V3 @observed/@track object`, 398 value: value 399 }); 400 } 401 return res; 402 } 403 404 /* 405 type checking for allowed simple types value 406 see 1st parameter for explanation what is allowed 407 */ 408 protected checkIsSimple(value: T): boolean { 409 let res = (value === undefined || typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean'); 410 if (!res) { 411 errorReport.varValueCheckFailed({ 412 customComponent: this.debugInfoOwningView(), 413 variableDeco: this.debugInfoDecorator(), 414 variableName: this.info(), 415 expectedType: `undefined, number, boolean, string`, 416 value: value 417 }); 418 } 419 return res; 420 } 421 422 protected checkNewValue(isAllowedComment : string, newValue: T, validator: (value: T) => boolean) : boolean { 423 if (validator(newValue)) { 424 return true; 425 } 426 427 // report error 428 // current implementation throws an Exception 429 errorReport.varValueCheckFailed({ 430 customComponent: this.debugInfoOwningView(), 431 variableDeco: this.debugInfoDecorator(), 432 variableName: this.info(), 433 expectedType: isAllowedComment, 434 value: newValue 435 }); 436 437 // never gets here if errorReport.varValueCheckFailed throws an exception 438 // but should not depend on its implementation 439 return false; 440 } 441 442 443 /** 444 * factory function for concrete 'object' or 'simple' ObservedProperty object 445 * depending if value is Class object 446 * or simple type (boolean | number | string) 447 * @param value 448 * @param owningView 449 * @param thisPropertyName 450 * @returns either 451 */ 452 static CreateObservedObject<C>(value: C, owningView: IPropertySubscriber, thisPropertyName: PropertyInfo) 453 : ObservedPropertyAbstract<C> { 454 return (typeof value === 'object') ? 455 new ObservedPropertyObject(value, owningView, thisPropertyName) 456 : new ObservedPropertySimple(value, owningView, thisPropertyName); 457 } 458 459 460 /** 461 * If owning viewPU is currently rendering or re-rendering a UINode, return its elmtId 462 * return notRecordingDependencies (-1) otherwise 463 * ViewPU caches the info, it does not request the info from C++ side (by calling 464 * ViewStackProcessor.GetElmtIdToAccountFor(); as done in earlier implementation 465 */ 466 protected getRenderingElmtId() : number { 467 return (this.owningView_) ? this.owningView_.getCurrentlyRenderedElmtId() : UINodeRegisterProxy.notRecordingDependencies; 468 } 469 470 471 /** 472 * during 'get' access recording take note of the created component and its elmtId 473 * and add this component to the list of components who are dependent on this property 474 */ 475 protected recordPropertyDependentUpdate() : void { 476 const elmtId = this.getRenderingElmtId(); 477 if (elmtId === UINodeRegisterProxy.notRecordingDependencies) { 478 // not access recording 479 return; 480 } 481 if (elmtId === UINodeRegisterProxy.monitorIllegalV2V3StateAccess) { 482 const error = `${this.debugInfo()}: recordPropertyDependentUpdate trying to use V2 state to init/update child V3 @Component. Application error`; 483 stateMgmtConsole.applicationError(error); 484 throw new TypeError(error); 485 } 486 487 stateMgmtConsole.debug(`${this.debugInfo()}: recordPropertyDependentUpdate: add (state) variable dependency for elmtId ${elmtId}.`); 488 this.dependentElmtIdsByProperty_.addPropertyDependency(elmtId); 489 } 490 491 /** record dependency ObservedObject + propertyName -> elmtId 492 * caller ensures renderingElmtId >= 0 493 */ 494 protected recordTrackObjectPropertyDependencyForElmtId(renderingElmtId : number, readTrackedPropertyName : string) : void { 495 stateMgmtConsole.debug(`${this.debugInfo()}: recordTrackObjectPropertyDependency on elmtId ${renderingElmtId}.`); 496 this.dependentElmtIdsByProperty_.addTrackedObjectPropertyDependency(readTrackedPropertyName, renderingElmtId); 497 } 498 499 public purgeDependencyOnElmtId(rmElmtId: number): void { 500 this.dependentElmtIdsByProperty_?.purgeDependenciesForElmtId(rmElmtId); 501 } 502 503 public SetPropertyUnchanged(): void { 504 // function to be removed 505 // keep it here until transpiler is updated. 506 } 507 508 // unified Appstorage, what classes to use, and the API 509 public createLink(subscribeOwner?: IPropertySubscriber, 510 linkPropName?: PropertyInfo): ObservedPropertyAbstractPU<T> { 511 throw new Error(`${this.debugInfo()}: createLink: Can not create a AppStorage 'Link' from this property.`); 512 } 513 514 public createProp(subscribeOwner?: IPropertySubscriber, 515 linkPropName?: PropertyInfo): ObservedPropertyAbstractPU<T> { 516 throw new Error(`${this.debugInfo()}: createProp: Can not create a AppStorage 'Prop' from a @State property. `); 517 } 518 519 /* 520 Below empty functions required to keep as long as this class derives from FU version 521 ObservedPropertyAbstract. Need to overwrite these functions to do nothing for PU 522 */ 523 protected notifyHasChanged(_: T) { 524 stateMgmtConsole.error(`${this.debugInfo()}: notifyHasChanged, DO NOT USE with PU. Use syncPeerHasChanged() \ 525 or onTrackedObjectProperty(CompatMode)HasChangedPU()`); 526 } 527 528 529 /** 530 * event emitted by wrapped ObservedObject, when one of its property values changes 531 * for class objects when in compatibility mode 532 * for Array, Date instances always 533 * @param souceObject 534 * @param changedPropertyName 535 */ 536 public onTrackedObjectPropertyHasChangedPU(sourceObject: ObservedObject<T>, changedPropertyName: string) { 537 stateMgmtConsole.debug(`${this.debugInfo()}: onTrackedObjectPropertyHasChangedPU: property '${changedPropertyName}' of \ 538 object value has changed.`); 539 540 this.notifyTrackedObjectPropertyHasChanged(changedPropertyName); 541 } 542 543 /** 544 * event emitted by wrapped ObservedObject, when one of its property values changes 545 * for class objects when in compatibility mode 546 * for Array, Date instances always 547 * @param souceObject 548 * @param changedPropertyName 549 */ 550 public onTrackedObjectPropertyCompatModeHasChangedPU(sourceObject: ObservedObject<T>, changedPropertyName: string) { 551 stateMgmtConsole.debug(`${this.debugInfo()}: onTrackedObjectPropertyCompatModeHasChangedPU: property '${changedPropertyName}' of \ 552 object value has changed.`); 553 554 this.notifyPropertyHasChangedPU(); 555 } 556 557 558 hasChanged(_: T): void { 559 // unused for PU 560 // need to overwrite impl of base class with empty function. 561 } 562 563 propertyHasChanged(_?: PropertyInfo): void { 564 // unused for PU 565 // need to overwrite impl of base class with empty function. 566 } 567 568 propertyRead(_?: PropertyInfo): void { 569 // unused for PU 570 // need to overwrite impl of base class with empty function. 571 } 572} 573 574class PropertyDependencies { 575 576 // dependencies for property -> elmtId 577 // variable read during render adds elmtId 578 // variable assignment causes elmtId to need re-render. 579 // UINode with elmtId deletion needs elmtId to be removed from all records, see purgeDependenciesForElmtId 580 private propertyDependencies_: Set<number> = new Set<number>(); 581 582 public getAllPropertyDependencies(): Set<number> { 583 stateMgmtConsole.debug(` ... variable value assignment: returning affected elmtIds ${JSON.stringify(Array.from(this.propertyDependencies_))}`); 584 return this.propertyDependencies_; 585 } 586 587 public addPropertyDependency(elmtId: number): void { 588 this.propertyDependencies_.add(elmtId); 589 stateMgmtConsole.debug(` ... variable value read: add dependent elmtId ${elmtId} - updated list of dependent elmtIds: ${JSON.stringify(Array.from(this.propertyDependencies_))}`); 590 } 591 592 public purgeDependenciesForElmtId(rmElmtId: number): void { 593 stateMgmtConsole.debug(` ...purge all dependencies for elmtId ${rmElmtId} `); 594 this.propertyDependencies_.delete(rmElmtId); 595 stateMgmtConsole.debug(` ... updated list of elmtIds dependent on variable assignment: ${JSON.stringify(Array.from(this.propertyDependencies_))}`); 596 this.trackedObjectPropertyDependencies_.forEach((propertyElmtId, propertyName) => { 597 propertyElmtId.delete(rmElmtId); 598 stateMgmtConsole.debug(` ... updated dependencies on objectProperty '${propertyName}' changes: ${JSON.stringify(Array.from(propertyElmtId))}`); 599 }); 600 } 601 602 // dependencies on individual object properties 603 private trackedObjectPropertyDependencies_: Map<string, Set<number>> = new Map<string, Set<number>>(); 604 605 public addTrackedObjectPropertyDependency(readProperty: string, elmtId: number): void { 606 let dependentElmtIds = this.trackedObjectPropertyDependencies_.get(readProperty); 607 if (!dependentElmtIds) { 608 dependentElmtIds = new Set<number>(); 609 this.trackedObjectPropertyDependencies_.set(readProperty, dependentElmtIds); 610 } 611 dependentElmtIds.add(elmtId); 612 stateMgmtConsole.debug(` ... object property '${readProperty}' read: add dependent elmtId ${elmtId} - updated list of dependent elmtIds: ${JSON.stringify(Array.from(dependentElmtIds))}`); 613 } 614 615 public getTrackedObjectPropertyDependencies(changedObjectProperty: string, debugInfo: string): Set<number> { 616 const dependentElmtIds = this.trackedObjectPropertyDependencies_.get(changedObjectProperty) || new Set<number>(); 617 stateMgmtConsole.debug(` ... property '@Track ${changedObjectProperty}': returning affected elmtIds ${JSON.stringify(Array.from(dependentElmtIds))}`); 618 return dependentElmtIds; 619 } 620 621 public dumpInfoDependencies(owningView: ViewPU | undefined = undefined, dumpDependantElements): string { 622 const formatElmtId = owningView ? (elmtId => owningView.debugInfoElmtId(elmtId)) : (elmtId => elmtId); 623 let result: string = ''; 624 const arr = Array.from(this.propertyDependencies_).map(formatElmtId); 625 if (dumpDependantElements) { 626 return (arr.length > 1 ? arr.join(', ') : arr[0]); 627 } 628 if (!this.trackedObjectPropertyDependencies_.size) { 629 result += `dependencies: variable assignment affects elmtIds: ${Array.from(this.propertyDependencies_).map(formatElmtId).join(', ')}`; 630 return result; 631 } 632 this.trackedObjectPropertyDependencies_.forEach((propertyElmtId, propertyName) => { 633 result += `dependencies: property '@Track ${propertyName}' change affects elmtIds: ${Array.from(propertyElmtId).map(formatElmtId).join(', ')}`; 634 }); 635 return result; 636 } 637 638 639 public dumpInfoDependenciesObj(owningView: ViewPU | undefined = undefined, isTrackedMode: boolean, isProfiler: boolean): PropertyDependenciesInfo { 640 641 const formatElmtId = owningView ? (elmtId => owningView.debugInfoElmtId(elmtId, isProfiler)) : (elmtId => elmtId); 642 643 let trackedObjectPropertyDependenciesDumpInfo: Map<string, Array<ElementType | number | string>> = new Map<string, Array<ElementType | number | string>>(); 644 645 this.trackedObjectPropertyDependencies_.forEach((propertyElmtId, propertyName) => { 646 trackedObjectPropertyDependenciesDumpInfo.set(propertyName, Array.from(propertyElmtId).map(formatElmtId)); 647 }); 648 649 let PropertyDependenciesInfo: PropertyDependenciesInfo = { 650 mode: isTrackedMode ? 'Track Mode' : 'Compatible Mode', 651 trackPropertiesDependencies: MapInfo.toObject(trackedObjectPropertyDependenciesDumpInfo).keyToValue, 652 propertyDependencies: Array.from(this.propertyDependencies_).map(formatElmtId), 653 } 654 return PropertyDependenciesInfo; 655 } 656 657 public hasDependencies() : boolean { 658 return this.propertyDependencies_.size > 0 || this.trackedObjectPropertyDependencies_.size > 0; 659 } 660} 661