Skip to content

Commit 8d881a1

Browse files
committed
WIP: Zooming into a Var should not require an Owner (#119)
1 parent 308231d commit 8d881a1

File tree

4 files changed

+780
-0
lines changed

4 files changed

+780
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.raquo.airstream.state
2+
3+
import com.raquo.airstream.core.{Protected, Signal}
4+
import com.raquo.airstream.misc.MapSignal
5+
6+
import scala.util.Try
7+
8+
class PureDerivedVarSignal[I, O](
9+
parent: Var[I],
10+
zoomIn: I => O,
11+
parentDisplayName: => String
12+
) extends MapSignal[I, O](parent.signal, project = zoomIn, recover = None) with StrictSignal[O] { self =>
13+
14+
// Note that even if owner kills subscription, this signal might remain due to other listeners
15+
// override protected[state] def isStarted: Boolean = super.isStarted
16+
17+
override protected def defaultDisplayName: String = parentDisplayName + ".signal"
18+
19+
override def tryNow(): Try[O] = {
20+
val newParentLastUpdateId = Protected.lastUpdateId(parent.signal)
21+
if (newParentLastUpdateId != _parentLastUpdateId) {
22+
// This branch can only run if !isStarted
23+
val nextValue = currentValueFromParent()
24+
updateCurrentValueFromParent(nextValue, newParentLastUpdateId)
25+
nextValue
26+
} else {
27+
super.tryNow()
28+
}
29+
}
30+
31+
override protected[state] def updateCurrentValueFromParent(nextValue: Try[O], nextParentLastUpdateId: Int): Unit =
32+
super.updateCurrentValueFromParent(nextValue, nextParentLastUpdateId)
33+
}

src/main/scala/com/raquo/airstream/state/Var.scala

+4
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ trait Var[A] extends SignalSource[A] with Sink[A] with Named {
8585
new DerivedVar[A, B](this, in, out, owner, displayNameSuffix = ".zoom")
8686
}
8787

88+
def zoomPure[B](in: A => B)(out: (A, B) => A): Var[B] = {
89+
new PureDerivedVar[A, B](this, in, out, displayNameSuffix = ".zoomPure")
90+
}
91+
8892
def setTry(tryValue: Try[A]): Unit = writer.onTry(tryValue)
8993

9094
final def set(value: A): Unit = setTry(Success(value))

0 commit comments

Comments
 (0)