@@ -415,45 +415,59 @@ function runReflectEffect(inst, property, props, oldProps, info) {
415415function runComputedEffects ( inst , changedProps , oldProps , hasPaths ) {
416416 let computeEffects = inst [ TYPES . COMPUTE ] ;
417417 if ( computeEffects ) {
418- if ( orderedComputed == 2 ) {
418+ if ( orderedComputed ) {
419+ // Runs computed effects in efficient order by keeping a topologically-
420+ // sorted queue of compute effects to run, and inserting subsequently
421+ // invalidated effects as they are run
419422 dedupeId ++ ;
420- const order = orderedComputedDeps ( inst ) ;
423+ const order = getComputedOrder ( inst ) ;
421424 const queue = [ ] ;
422425 for ( let p in changedProps ) {
423- addEffectsFor ( p , computeEffects , queue , order , hasPaths ) ;
426+ enqueueEffectsFor ( p , computeEffects , queue , order , hasPaths ) ;
424427 }
425428 let info ;
426429 while ( ( info = queue . shift ( ) ) ) {
427430 if ( runComputedEffect ( inst , '' , changedProps , oldProps , info , hasPaths ) ) {
428- addEffectsFor ( info . methodInfo , computeEffects , queue , order , hasPaths ) ;
431+ enqueueEffectsFor ( info . methodInfo , computeEffects , queue , order , hasPaths ) ;
429432 }
430433 }
431434 Object . assign ( /** @type {!Object } */ ( oldProps ) , inst . __dataOld ) ;
432435 Object . assign ( /** @type {!Object } */ ( changedProps ) , inst . __dataPending ) ;
433436 inst . __dataPending = null ;
434437 } else {
438+ // Original Polymer 2.x computed effects order, which continues running
439+ // effects until no further computed properties have been invalidated
435440 let inputProps = changedProps ;
436441 while ( runEffects ( inst , computeEffects , inputProps , oldProps , hasPaths ) ) {
437442 Object . assign ( /** @type {!Object } */ ( oldProps ) , inst . __dataOld ) ;
438443 Object . assign ( /** @type {!Object } */ ( changedProps ) , inst . __dataPending ) ;
439444 inputProps = inst . __dataPending ;
440445 inst . __dataPending = null ;
441- if ( orderedComputed ) {
442- // Ensure all computed properties are de-duped against the same turn
443- dedupeId -- ;
444- }
445446 }
446447 }
447448 }
448449}
449450
450- const insertEffect = ( info , effects , order ) => {
451+ /**
452+ * Inserts a computed effect into a queue, given the specified order. Performs
453+ * the insert using a binary search.
454+ *
455+ * Used by `orderedComputed: true` computed property algorithm.
456+ *
457+ * @param {Object } info Property effects metadata
458+ * @param {Array<Object> } queue Ordered queue of effects
459+ * @param {Map<string,number> } order Map of computed property name->topological
460+ * sort order
461+ */
462+ const insertEffect = ( info , queue , order ) => {
451463 let start = 0 ;
452- let end = effects . length - 1 ;
464+ let end = queue . length - 1 ;
453465 let idx = - 1 ;
454466 while ( start <= end ) {
455467 const mid = ( start + end ) >> 1 ;
456- const cmp = order . get ( effects [ mid ] . methodInfo ) - order . get ( info . methodInfo ) ;
468+ // Note `methodInfo` is where the computed property name is stored in
469+ // the effect metadata
470+ const cmp = order . get ( queue [ mid ] . methodInfo ) - order . get ( info . methodInfo ) ;
457471 if ( cmp < 0 ) {
458472 start = mid + 1 ;
459473 } else if ( cmp > 0 ) {
@@ -466,10 +480,24 @@ const insertEffect = (info, effects, order) => {
466480 if ( idx < 0 ) {
467481 idx = end + 1 ;
468482 }
469- effects . splice ( idx , 0 , info ) ;
483+ queue . splice ( idx , 0 , info ) ;
470484} ;
471485
472- const addEffectsFor = ( prop , computeEffects , queue , order , hasPaths ) => {
486+ /**
487+ * Inserts all downstream computed effects invalidated by the specified property
488+ * into the topologically-sorted queue of effects to be run.
489+ *
490+ * Used by `orderedComputed: true` computed property algorithm.
491+ *
492+ * @param {string } prop Property name
493+ * @param {Object } computeEffects Computed effects for this element
494+ * @param {Array<Obbject> } queue Topologically-sorted queue of computed effects
495+ * to be run
496+ * @param {Map<string,numer> } order Map of computed property name->topological
497+ * sort order
498+ * @param {boolean } hasPaths True with `changedProps` contains one or more paths
499+ */
500+ const enqueueEffectsFor = ( prop , computeEffects , queue , order , hasPaths ) => {
473501 const rootProperty = hasPaths ? root ( prop ) : prop ;
474502 const fxs = computeEffects [ rootProperty ] ;
475503 if ( fxs ) {
@@ -484,21 +512,43 @@ const addEffectsFor = (prop, computeEffects, queue, order, hasPaths) => {
484512 }
485513} ;
486514
487- function orderedComputedDeps ( inst ) {
515+ /**
516+ * Generates and retrieves a memoized map of computed property name to its
517+ * topologically-sorted order.
518+ *
519+ * The map is generated by first assigning a "dependency count" to each property
520+ * (defined as number properties it depends on, including its method for
521+ * "dynamic functions"). Any properties that have no dependencies are added to
522+ * the `ready` queue, which are properties whose order can be added to the final
523+ * order map. Properties are popped off the `ready` one by one and a.) added as
524+ * the next property in the order map, and b.) each property that it is a
525+ * dependency for has its dep count decremented (and if that property's dep
526+ * count goes to zero, it is added to the `ready` queue), until all properties
527+ * have been visited and ordered.
528+ *
529+ * Used by `orderedComputed: true` computed property algorithm.
530+ *
531+ * @param {!Polymer_PropertyEffects } inst The instance to retrieve the computed
532+ * effect order for.
533+ * @return {Map<string,numbber> } Map of computed property name->topological sort
534+ * order
535+ */
536+ function getComputedOrder ( inst ) {
488537 let ordered = inst . constructor . __orderedComputedDeps ;
489538 if ( ! ordered ) {
490539 ordered = new Map ( ) ;
491540 const effects = inst [ TYPES . COMPUTE ] ;
492- const { counts, next } = edgeCounts ( inst ) ;
541+ const { counts, ready } = dependencyCounts ( inst ) ;
493542 let curr ;
494- while ( ( curr = next . pop ( ) ) ) {
543+ while ( ( curr = ready . shift ( ) ) ) {
495544 ordered . set ( curr , ordered . size ) ;
496545 const computedByCurr = effects [ curr ] ;
497546 if ( computedByCurr ) {
498547 computedByCurr . forEach ( fx => {
499- const p = fx . info . methodInfo ;
500- if ( -- counts [ p ] === 0 ) {
501- next . push ( p ) ;
548+ // Note `methodInfo` is where the computed property name is stored
549+ const computedProp = fx . info . methodInfo ;
550+ if ( -- counts [ computedProp ] === 0 ) {
551+ ready . push ( computedProp ) ;
502552 }
503553 } ) ;
504554 }
@@ -508,24 +558,37 @@ function orderedComputedDeps(inst) {
508558 return ordered ;
509559}
510560
511- function edgeCounts ( inst ) {
561+ /**
562+ * Generates a map of property-to-dependency count (`counts`, where "dependency
563+ * count" is the number of dependencies a given property has assuming it is a
564+ * computed property, otherwise 0). It also returns a pre-populated list of
565+ * `ready` properties that have no dependencies.
566+ *
567+ * Used by `orderedComputed: true` computed property algorithm.
568+ *
569+ * @param {!Polymer_PropertyEffects } inst The instance to generate dependency
570+ * counts for.
571+ * @return {Object } Object containing `counts` map (property-to-dependency
572+ * count) and pre-populated `ready` array of properties that had zero
573+ * dependencies.
574+ */
575+ function dependencyCounts ( inst ) {
512576 const props = inst . constructor . _properties ;
513- const depsForComputed = inst [ COMPUTE_INFO ] ;
577+ const infoForComputed = inst [ COMPUTE_INFO ] ;
514578 const counts = { } ;
515- const next = [ ] ;
579+ const ready = [ ] ;
516580 for ( let p in props ) {
517- const deps = depsForComputed [ p ] ;
518- if ( deps ) {
519- counts [ p ] = deps . args . length ;
581+ const info = infoForComputed [ p ] ;
582+ if ( info ) {
583+ // Be sure to add the method name itself in case of "dynamic functions"
584+ counts [ p ] = info . args . length + ( info . dynamicFn ? 1 : 0 ) ;
520585 } else {
521- next . push ( p ) ;
586+ ready . push ( p ) ;
522587 }
523588 }
524- return { counts, next } ;
589+ return { counts, ready } ;
525590}
526591
527- const TRANSITIVE_DEPENDENCY = '~transitive~dependency~' ;
528-
529592/**
530593 * Implements the "computed property" effect by running the method with the
531594 * values of the arguments specified in the `info` object and setting the
@@ -540,120 +603,19 @@ const TRANSITIVE_DEPENDENCY = '~transitive~dependency~';
540603 * @return {boolean } True when the property being computed changed
541604 * @private
542605 */
543- function runComputedEffect ( inst , property , changedProps , oldProps , info , hasPaths ) {
544- if ( orderedComputed == 1 ) {
545- // Compute any computed dependencies first; this recurses through the
546- // dependency graph to ensure computed properties are never computed with
547- // dependencies that may become invalidated later in this turn
548- computeDependencies ( inst , info , changedProps , oldProps , hasPaths ) ;
549- // If the dependency was not transitive, it's definitely dirty and needs to
550- // be computed; if it is transitive, check its arguments against the current
551- // changed props and only re-compute if it is dirty
552- if ( property === TRANSITIVE_DEPENDENCY &&
553- ! computedPropertyIsDirty ( inst , info , changedProps , hasPaths ) ) {
554- return ;
555- }
556- }
606+ function runComputedEffect ( inst , property , changedProps , oldProps , info ) {
557607 // Dirty check dependencies and run if any invalid
558608 let result = runMethodEffect ( inst , property , changedProps , oldProps , info ) ;
559609 // Abort if method returns a no-op result
560610 if ( result === NOOP ) {
561- return ;
611+ return false ;
562612 }
563613 let computedProp = info . methodInfo ;
564614 if ( inst . __dataHasAccessor && inst . __dataHasAccessor [ computedProp ] ) {
565615 return inst . _setPendingProperty ( computedProp , result , true ) ;
566616 } else {
567617 inst [ computedProp ] = result ;
568- }
569- }
570-
571- /**
572- * Returns any dependencies of a computed property that are themselves
573- * also computed.
574- *
575- * @param {!Polymer_PropertyEffects } inst The instance to test
576- * @param {? } info Computed effect metadata
577- * @return {Array<?> } Array of computed effect metadata for depenencies
578- * @private
579- */
580- function computedDependenciesFor ( inst , info ) {
581- let deps = info . computedDependencies ;
582- if ( ! deps ) {
583- deps = info . computedDependencies = [ ] ;
584- info . args . forEach ( arg => {
585- const dep = arg . rootProperty || arg . name ;
586- const depInfo = inst [ COMPUTE_INFO ] [ dep ] ;
587- if ( depInfo ) {
588- deps . push ( depInfo ) ;
589- }
590- } ) ;
591- }
592- return deps ;
593- }
594-
595- /**
596- * Runs computed effects for dependencies of the computed property effect
597- * described by `info`.
598- *
599- * @param {!Polymer_PropertyEffects } inst The instance to test
600- * @param {Object } info Effect metadata
601- * @param {Object } changedProps Bag of current property changes
602- * @param {Object } oldProps Bag of previous values for changed properties
603- * @param {boolean } hasPaths True with `changedProps` contains one or more paths
604- * @return {void }
605- * @private
606- */
607- function computeDependencies ( inst , info , changedProps , oldProps , hasPaths ) {
608- let deps = computedDependenciesFor ( inst , info ) ;
609- if ( deps . length ) {
610- deps . forEach ( depInfo => {
611- if ( depInfo . lastRun !== info . lastRun ) {
612- depInfo . lastRun = info . lastRun ;
613- runComputedEffect ( inst , TRANSITIVE_DEPENDENCY , changedProps , oldProps , depInfo , hasPaths ) ;
614- }
615- } ) ;
616- }
617- }
618-
619- /**
620- * Returns whether the computed effect has any changed dependencies.
621- *
622- * @param {!Polymer_PropertyEffects } inst The instance to test
623- * @param {? } info Effect metadata
624- * @param {Object } changedProps Bag of current property changes
625- * @param {boolean } hasPaths True with `changedProps` contains one or more paths
626- * @return {boolean } true when the computed effect should be re-calculated
627- * @private
628- */
629- function computedPropertyIsDirty ( inst , info , changedProps , hasPaths ) {
630- return info . dynamicFn && propertyIsDirty ( inst , { rootProperty : info . methodName } , changedProps , hasPaths ) ||
631- info . args . some ( arg => propertyIsDirty ( inst , arg , changedProps , hasPaths ) ) ;
632- }
633-
634- /**
635- * Returns whether any changed props match the effect trigger
636- *
637- * @param {!Polymer_PropertyEffects } inst The instance to test
638- * @param {DataTrigger } trigger Descriptor
639- * @param {Object } changedProps Bag of current property changes
640- * @param {boolean } hasPaths True with `changedProps` contains one or more paths
641- * @return {boolean } true when the property is dirty
642- * @private
643- */
644- function propertyIsDirty ( inst , trigger , changedProps , hasPaths ) {
645- if ( hasPaths ) {
646- [ changedProps , inst . __dataPending ] . some ( props => {
647- for ( let p in props ) {
648- if ( pathMatchesTrigger ( p , trigger ) ) {
649- return true ;
650- }
651- }
652- } ) ;
653618 return false ;
654- } else {
655- return Boolean ( changedProps && trigger . rootProperty in changedProps ||
656- inst . __dataPending && trigger . rootProperty in inst . __dataPending ) ;
657619 }
658620}
659621
0 commit comments