| ... | @@ -453,7 +453,7 @@ function newNode<Result, EventMap extends Events.Map>( | ... | @@ -453,7 +453,7 @@ function newNode<Result, EventMap extends Events.Map>( |
| 453 | estimator = estimateCompletion; | 453 | estimator = estimateCompletion; |
| 454 | } else estimator = new Ema(); | 454 | } else estimator = new Ema(); |
| 455 | } | 455 | } |
| 456 | estimator?.sample(Date.now(), 0); | 456 | estimator?.sample(Date.now(), 0, 0); |
| 457 | | 457 | |
| 458 | function mutate(key: keyof ReadOnlyNode) { | 458 | function mutate(key: keyof ReadOnlyNode) { |
| 459 | if (state.detached) return; | 459 | if (state.detached) return; |
| ... | @@ -464,7 +464,7 @@ function newNode<Result, EventMap extends Events.Map>( | ... | @@ -464,7 +464,7 @@ function newNode<Result, EventMap extends Events.Map>( |
| 464 | // originally, auto-estimation would use the Root's timing primitives, | 464 | // originally, auto-estimation would use the Root's timing primitives, |
| 465 | // but this makes it so that `state.estimatedTime` is in terms of the | 465 | // but this makes it so that `state.estimatedTime` is in terms of the |
| 466 | // Root, which is unintuitive for everyone but the renderer. | 466 | // Root, which is unintuitive for everyone but the renderer. |
| 467 | const time = estimator.sample(Date.now(), state.value / state.total); | 467 | const time = estimator.sample(Date.now(), state.value, state.total); |
| 468 | if (time && (!state.estimatedTime || state.estimatedTime !== time)) { | 468 | if (time && (!state.estimatedTime || state.estimatedTime !== time)) { |
| 469 | state.estimatedTime = time; | 469 | state.estimatedTime = time; |
| 470 | owner.emit("node-change", state, "estimatedTime"); | 470 | owner.emit("node-change", state, "estimatedTime"); |
| ... | @@ -1671,9 +1671,10 @@ class KeyPool<T extends number> { | ... | @@ -1671,9 +1671,10 @@ class KeyPool<T extends number> { |
| 1671 | export interface EstimationAlgorithm { | 1671 | export interface EstimationAlgorithm { |
| 1672 | /** | 1672 | /** |
| 1673 | * given a timestamp `time` and a progress value `progress`, return the | 1673 | * given a timestamp `time` and a progress value `progress`, return the |
| 1674 | * timestamp that the action will be finished on. | 1674 | * timestamp that the action will be finished on. called with (t, 0, 0) |
| | 1675 | * to initialize the algorithm. |
| 1675 | */ | 1676 | */ |
| 1676 | sample(time: number, progress: number): number | null; | 1677 | sample(time: number, current: number, total: number): number | null; |
| 1677 | } | 1678 | } |
| 1678 | | 1679 | |
| 1679 | /** | 1680 | /** |
| ... | @@ -1685,33 +1686,29 @@ export class Ema implements EstimationAlgorithm { | ... | @@ -1685,33 +1686,29 @@ export class Ema implements EstimationAlgorithm { |
| 1685 | start: number | null = null; | 1686 | start: number | null = null; |
| 1686 | estimate: number | null = null; | 1687 | estimate: number | null = null; |
| 1687 | /** smoothing factor (0-1) */ | 1688 | /** smoothing factor (0-1) */ |
| 1688 | alpha: number = 0.25; | 1689 | alpha: number; |
| 1689 | | 1690 | |
| 1690 | constructor(alpha: number = 0.1) { | 1691 | constructor(alpha: number = 0.25) { |
| 1691 | this.alpha = alpha; | 1692 | this.alpha = alpha; |
| 1692 | } | 1693 | } |
| 1693 | | 1694 | |
| 1694 | /** | 1695 | sample(time: number, current: number, total: number): number | null { |
| 1695 | * given a timestamp `time` and a progress value `progress`, return the | 1696 | ASSERT(current >= 0 && current <= total); |
| 1696 | * timestamp that the action will be finished on. | 1697 | if (this.start == null || current === 0) { |
| 1697 | */ | | |
| 1698 | sample(time: number, progress: number): number | null { | | |
| 1699 | ASSERT( | | |
| 1700 | progress >= 0 && progress < 1, | | |
| 1701 | `progress must be a number between 0 and 1, got ${progress}`, | | |
| 1702 | ); | | |
| 1703 | if (this.start == null) { | | |
| 1704 | this.start = time; | 1698 | this.start = time; |
| | 1699 | this.estimate = null; |
| 1705 | return null; | 1700 | return null; |
| 1706 | } | 1701 | } |
| 1707 | | 1702 | |
| 1708 | const value = (time - this.start) / progress; | 1703 | const elapsed = time - this.start; |
| 1709 | this.estimate = this.estimate != null | 1704 | if (elapsed === 0) return null; // too quick |
| 1710 | ? ((1 - this.alpha) * this.estimate + | | |
| 1711 | this.alpha * value) | | |
| 1712 | : value; | | |
| 1713 | | 1705 | |
| 1714 | return time + (1 - progress) * this.estimate; | 1706 | const rate = current / elapsed; |
| | 1707 | this.estimate = this.estimate != null |
| | 1708 | ? (1 - this.alpha) * this.estimate + this.alpha * rate |
| | 1709 | : rate; |
| | 1710 | const remaining = total - current; |
| | 1711 | return time + remaining / this.estimate; |
| 1715 | } | 1712 | } |
| 1716 | | 1713 | |
| 1717 | reset(): void { | 1714 | reset(): void { |