Skip to content

Commit 71ce68e

Browse files
committed
Added onUpdate support / DirtyState.
1 parent 85845cf commit 71ce68e

File tree

8 files changed

+85
-29
lines changed

8 files changed

+85
-29
lines changed

shared/src/main/scala/reactify/AbstractState.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import reactify.instance.{RecursionMode, StateInstanceManager}
55
class AbstractState[T](override val distinct: Boolean,
66
cache: Boolean,
77
recursion: RecursionMode,
8-
transactional: Boolean) extends State[T] {
9-
protected val manager = new StateInstanceManager[T](this, cache, recursion, transactional)
8+
transactional: Boolean,
9+
onUpdate: Boolean) extends State[T] {
10+
protected val manager = new StateInstanceManager[T](this, cache, recursion, transactional, onUpdate)
1011

1112
override def observing: Set[Observable[_]] = manager.observables
1213

1314
override protected def value(): T = manager.get
1415

15-
override def set(value: => T): Unit = {
16+
override protected def set(value: => T): Unit = {
1617
manager.replaceInstance(() => value)
1718
}
1819
}
Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,6 @@
11
package reactify
22

3-
import java.util.concurrent.atomic.AtomicReference
4-
5-
// TODO: apply in StateInstanceManager
63
trait DirtyState[T] extends AbstractState[T] {
7-
private lazy val dirty = new AtomicReference[Option[() => T]](None)
8-
9-
def isDirty: Boolean = dirty.get().nonEmpty
10-
11-
def update(): Boolean = dirty.getAndSet(None) match {
12-
case Some(value) => {
13-
manager.replaceInstance(value)
14-
true
15-
}
16-
case None => false
17-
}
18-
19-
override def set(value: => T): Unit = if (manager.isEmpty) {
20-
super.set(value)
21-
} else {
22-
dirty.set(Some(() => value))
23-
}
4+
def isDirty: Boolean = manager.isDirty
5+
def update(): Boolean = manager.update()
246
}

shared/src/main/scala/reactify/StateChannel.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import java.util.concurrent.atomic.AtomicBoolean
55
import reactify.bind._
66

77
trait StateChannel[T] extends State[T] with Channel[T] {
8-
override def set(value: => T): Unit
98
override def static(value: T): Unit = super.static(value)
109

1110
def bind[V](that: StateChannel[V], setNow: BindSet = BindSet.LeftToRight)

shared/src/main/scala/reactify/Transaction.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ object Transaction {
2121
f
2222
}
2323

24+
def withTransaction(f: => Unit, transaction: Transaction = new Transaction()): Transaction = {
25+
val previous = threadLocal.get()
26+
threadLocal.set(Some(transaction))
27+
try {
28+
f
29+
} finally {
30+
threadLocal.set(previous)
31+
}
32+
transaction
33+
}
34+
2435
def inTransaction: Boolean = threadLocal.get().nonEmpty
2536

2637
def update[T](manager: StateInstanceManager[T], f: Option[() => T]): Unit = threadLocal.get().get.update(manager, f)

shared/src/main/scala/reactify/Val.scala

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ class Val[T](function: () => T,
1414
distinct: Boolean = true,
1515
cache: Boolean = true,
1616
recursion: RecursionMode = RecursionMode.RetainPreviousValue,
17-
transactional: Boolean = true) extends AbstractState[T](distinct, cache, recursion, transactional) {
17+
transactional: Boolean = true,
18+
onUpdate: Boolean = false) extends AbstractState[T](distinct, cache, recursion, transactional, onUpdate) {
1819
set(function())
1920

2021
override def toString: String = s"Val($get)"
@@ -38,4 +39,22 @@ object Val {
3839
}
3940
new Val[T](f, distinct, cache, recursion, transactional)
4041
}
42+
43+
/**
44+
* Creates a new instance of `Var` mixing in `DirtyState`.
45+
*/
46+
def dirty[T](value: => T,
47+
static: Boolean = false,
48+
distinct: Boolean = true,
49+
cache: Boolean = true,
50+
recursion: RecursionMode = RecursionMode.RetainPreviousValue,
51+
transactional: Boolean = true): Val[T] with DirtyState[T] = {
52+
val f = if (static) {
53+
val v: T = value
54+
() => v
55+
} else {
56+
() => value
57+
}
58+
new Val[T](f, distinct, cache, recursion, transactional, onUpdate = true) with DirtyState[T]
59+
}
4160
}

shared/src/main/scala/reactify/Var.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ class Var[T](function: () => T,
1313
distinct: Boolean = true,
1414
cache: Boolean = true,
1515
recursion: RecursionMode = RecursionMode.RetainPreviousValue,
16-
transactional: Boolean = true
17-
) extends Val[T](function, distinct, cache, recursion, transactional) with StateChannel[T] {
16+
transactional: Boolean = true,
17+
onUpdate: Boolean = false
18+
) extends Val[T](function, distinct, cache, recursion, transactional, onUpdate) with StateChannel[T] {
19+
override def set(value: => T): Unit = super.set(value)
1820
def asVal: Val[T] = this
1921

2022
override def toString: String = s"Var($get)"
@@ -61,7 +63,7 @@ object Var {
6163
} else {
6264
() => value
6365
}
64-
new Var[T](f, distinct, cache, recursion, transactional) with DirtyState[T]
66+
new Var[T](f, distinct, cache, recursion, transactional, onUpdate = true) with DirtyState[T]
6567
}
6668

6769
def bound[T](get: => T,

shared/src/main/scala/reactify/instance/StateInstanceManager.scala

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import reactify.{Listener, Observable, State, Transaction}
55
class StateInstanceManager[T](state: State[T],
66
cache: Boolean,
77
recursion: RecursionMode,
8-
transactional: Boolean) {
8+
transactional: Boolean,
9+
onUpdate: Boolean) {
910
@volatile private var previousValue: T = _
1011
@volatile private var instance: StateInstance[T] = StateInstance.empty[T]
1112
@volatile private[reactify] var observables: Set[Observable[_]] = Set.empty
13+
@volatile private var updateTransaction: Option[Transaction] = None
1214
private val threadLocal = new ThreadLocal[StateInstance[T]] {
1315
override def initialValue(): StateInstance[T] = StateInstance.uninitialized[T]
1416
}
@@ -33,9 +35,29 @@ class StateInstanceManager[T](state: State[T],
3335
}
3436
}
3537

38+
def isDirty: Boolean = updateTransaction.nonEmpty
39+
40+
def update(): Boolean = synchronized {
41+
updateTransaction.exists { t =>
42+
t.commit()
43+
updateTransaction = None
44+
true
45+
}
46+
}
47+
3648
def updateInstance(force: Boolean = false): Unit = synchronized {
3749
if (!force && transactional && Transaction.inTransaction) {
3850
Transaction.update(this, None)
51+
} else if (!force && onUpdate && !instance.isEmpty) {
52+
val transaction = updateTransaction match {
53+
case Some(t) => t
54+
case None => {
55+
val t = new Transaction()
56+
updateTransaction = Some(t)
57+
t
58+
}
59+
}
60+
transaction.update(this, None)
3961
} else {
4062
// Reset cache
4163
instance.reset()
@@ -76,6 +98,16 @@ class StateInstanceManager[T](state: State[T],
7698
def replaceInstance(f: () => T, force: Boolean = false): Unit = synchronized {
7799
if (!force && transactional && Transaction.inTransaction) {
78100
Transaction.update(this, Some(f))
101+
} else if (!force && onUpdate && !instance.isEmpty) {
102+
val transaction = updateTransaction match {
103+
case Some(t) => t
104+
case None => {
105+
val t = new Transaction()
106+
updateTransaction = Some(t)
107+
t
108+
}
109+
}
110+
transaction.update(this, Some(f))
79111
} else {
80112
val previous = recursion match {
81113
case RecursionMode.Static => StateInstance.empty[T]

shared/src/test/scala/specs/BasicSpec.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,16 @@ class BasicSpec extends WordSpec with Matchers {
486486
v.update()
487487
v() should be("Two")
488488
}
489+
"modify the dependant value but not apply it" in {
490+
val v1 = Var[Int](1)
491+
val v2 = Val.dirty[String](s"v1 is ${v1()}")
492+
v2.update()
493+
v1 := 2
494+
v1() should be(2)
495+
v2() should be("v1 is 1")
496+
v2.update()
497+
v2() should be("v1 is 2")
498+
}
489499
"only fire events upon update" in {
490500
val v = Var.dirty[Int](0)
491501
var firedFor = ListBuffer.empty[Int]

0 commit comments

Comments
 (0)