|
| 1 | +package com.raquo.airstream.state |
| 2 | + |
| 3 | +import com.raquo.airstream.core.AirstreamError.VarError |
| 4 | +import com.raquo.airstream.core.{AirstreamError, Transaction} |
| 5 | +import com.raquo.airstream.ownership.Owner |
| 6 | + |
| 7 | +import scala.util.{Failure, Success, Try} |
| 8 | + |
| 9 | +// #nc Update comments |
| 10 | + |
| 11 | +/** DerivedVar has the same Var contract as SourceVar, but instead of maintaining its own state |
| 12 | + * it is essentially a lens on the underlying SourceVar. |
| 13 | + * |
| 14 | + * This Var is active for as long as its signal has listeners. |
| 15 | + * Being a StrictSignal, it already starts out with a subscription owned by `owner`, |
| 16 | + * but even if owner kills its subscriptions, this Var's signal might have other listeners. |
| 17 | + */ |
| 18 | +class PureDerivedVar[A, B]( |
| 19 | + parent: Var[A], |
| 20 | + zoomIn: A => B, |
| 21 | + zoomOut: (A, B) => A, |
| 22 | + displayNameSuffix: String |
| 23 | +) extends Var[B] { |
| 24 | + |
| 25 | + override private[state] def underlyingVar: SourceVar[_] = parent.underlyingVar |
| 26 | + |
| 27 | + private[this] val _varSignal = new PureDerivedVarSignal(parent, zoomIn, displayName) |
| 28 | + |
| 29 | + // #Note this getCurrentValue implementation is different from SourceVar |
| 30 | + // - SourceVar's getCurrentValue looks at an internal currentValue variable |
| 31 | + // - That currentValue gets updated immediately before the signal (in an already existing transaction) |
| 32 | + // - I hope this doesn't introduce weird transaction related timing glitches |
| 33 | + // - But even if it does, I think keeping derived var's current value consistent with its signal value |
| 34 | + // is more important, otherwise it would be madness if the derived var was accessed after its owner |
| 35 | + // was killed |
| 36 | + override private[state] def getCurrentValue: Try[B] = signal.tryNow() |
| 37 | + |
| 38 | + override private[state] def setCurrentValue(value: Try[B], transaction: Transaction): Unit = { |
| 39 | + // #nc Unlike the old DerivedVar, we don't check `_varSignal.isStarted` before updating the parent. |
| 40 | + // - Is that "natural" because we don't have an explicit "owner" here, or is that a change in semantics? |
| 41 | + parent.tryNow() match { |
| 42 | + case Success(parentValue) => |
| 43 | + // This can update the parent without causing an infinite loop because |
| 44 | + // the parent updates this derived var's signal, it does not call |
| 45 | + // setCurrentValue on this var directly. |
| 46 | + val nextValue = value.map(zoomOut(parentValue, _)) |
| 47 | + // println(s">> parent.setCurrentValue($nextValue)") |
| 48 | + parent.setCurrentValue(nextValue, transaction) |
| 49 | + |
| 50 | + case Failure(err) => |
| 51 | + AirstreamError.sendUnhandledError(VarError(s"Unable to zoom out of derived var when the parent var is failed.", cause = Some(err))) |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + override val signal: StrictSignal[B] = _varSignal |
| 56 | + |
| 57 | + override protected def defaultDisplayName: String = parent.displayName + displayNameSuffix |
| 58 | +} |
0 commit comments