Skip to content

Commit

Permalink
Refresh cache when re-enable a Binding or BindingSeq (fix #56)
Browse files Browse the repository at this point in the history
  • Loading branch information
Atry committed Nov 17, 2017
1 parent 6b71593 commit 8bc7845
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 40 deletions.
62 changes: 38 additions & 24 deletions Binding/src/main/scala/com/thoughtworks/binding/Binding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,11 @@ object Binding extends MonadicFactory.WithTypeClass[Monad, Binding] {

private val publisher = new Publisher[ChangedListener[B]]

private var cache: B = f(upstream.value)
private var cache: B = _

private def refreshCache() = {
cache = f(upstream.value)
}

@inline
override private[binding] def value: B = {
Expand All @@ -442,6 +446,7 @@ object Binding extends MonadicFactory.WithTypeClass[Monad, Binding] {
if (publisher.isEmpty) {
upstream.addChangedListener(this)
}
refreshCache()
publisher.subscribe(listener)
}

Expand Down Expand Up @@ -501,20 +506,25 @@ object Binding extends MonadicFactory.WithTypeClass[Monad, Binding] {
override private[binding] def addChangedListener(listener: ChangedListener[B]): Unit = {
if (publisher.isEmpty) {
upstream.addChangedListener(forwarder)
refreshCache()
cache.addChangedListener(this)
}
publisher.subscribe(listener)
}

private var cache: Binding[B] = f(upstream.value)
private var cache: Binding[B] = _

private def refreshCache() = {
cache = f(upstream.value)
}

override private[binding] def value: B = {
@tailrec
@inline
def tailrecGetValue(binding: Binding[B]): B = {
binding match {
case flatMap: FlatMap[_, B] => tailrecGetValue(flatMap.cache)
case _ => binding.value
case _ => binding.value
}
}
tailrecGetValue(cache)
Expand Down Expand Up @@ -712,8 +722,10 @@ object Binding extends MonadicFactory.WithTypeClass[Monad, Binding] {
extends BindingSeq[B]
with HasCache[Binding[B]] {

private[Binding] var cacheData: Cache = {
(for {
private[Binding] var cacheData: Cache = _

private def refreshCache() = {
cacheData = (for {
a <- upstream.value
} yield f(a))(collection.breakOut)
}
Expand All @@ -726,19 +738,19 @@ object Binding extends MonadicFactory.WithTypeClass[Monad, Binding] {
child <- upstreamEvent.that
} yield f(child))(collection.breakOut)
val oldChildren = spliceCache(upstreamEvent.from, mappedNewChildren, upstreamEvent.replaced)
for (newChild <- mappedNewChildren) {
newChild.addChangedListener(childListener)
}
for (oldChild <- oldChildren) {
oldChild.removeChangedListener(childListener)
}
val event = new PatchedEvent(MapBinding.this,
upstreamEvent.from,
new ValueProxy(mappedNewChildren),
upstreamEvent.replaced)
for (listener <- publisher) {
listener.patched(event)
}
for (oldChild <- oldChildren) {
oldChild.removeChangedListener(childListener)
}
for (newChild <- mappedNewChildren) {
newChild.addChangedListener(childListener)
}
}

}
Expand Down Expand Up @@ -768,6 +780,7 @@ object Binding extends MonadicFactory.WithTypeClass[Monad, Binding] {
override private[binding] def addPatchedListener(listener: PatchedListener[B]): Unit = {
if (publisher.isEmpty) {
upstream.addPatchedListener(upstreamListener)
refreshCache()
for (child <- cacheData) {
child.addChangedListener(childListener)
}
Expand Down Expand Up @@ -817,8 +830,10 @@ object Binding extends MonadicFactory.WithTypeClass[Monad, Binding] {
extends BindingSeq[B]
with HasCache[BindingSeq[B]] {

private[Binding] var cacheData: Cache = {
(for {
private[Binding] var cacheData: Cache = _

private def refreshCache() = {
cacheData = (for {
a <- upstream.value
} yield f(a))(collection.breakOut)
}
Expand All @@ -837,22 +852,20 @@ object Binding extends MonadicFactory.WithTypeClass[Monad, Binding] {
child <- upstreamEvent.that
} yield f(child))(collection.breakOut)
val flatNewChildren = new FlatProxy(mappedNewChildren)
val flattenFrom = flatIndex(cacheData, 0, upstreamEvent.from)
val flattenReplaced = flatIndex(cacheData, upstreamEvent.from, upstreamEvent.from + upstreamEvent.replaced)
val oldChilden = spliceCache(upstreamEvent.from, mappedNewChildren, upstreamEvent.replaced)
for (newChild <- mappedNewChildren) {
newChild.addPatchedListener(childListener)
}
for (oldChild <- oldChilden) {
oldChild.removePatchedListener(childListener)
}
if (upstreamEvent.replaced != 0 || flatNewChildren.nonEmpty) {
val flattenFrom = flatIndex(cacheData, 0, upstreamEvent.from)
val flattenReplaced = flatIndex(cacheData, upstreamEvent.from, upstreamEvent.from + upstreamEvent.replaced)
val oldChilden = spliceCache(upstreamEvent.from, mappedNewChildren, upstreamEvent.replaced)
val event = new PatchedEvent(FlatMapBinding.this, flattenFrom, flatNewChildren, flattenReplaced)
for (listener <- publisher) {
listener.patched(event)
}
for (oldChild <- oldChilden) {
oldChild.removePatchedListener(childListener)
}
for (newChild <- mappedNewChildren) {
newChild.addPatchedListener(childListener)
}
} else {
spliceCache(upstreamEvent.from, mappedNewChildren, upstreamEvent.replaced)
}
}

Expand Down Expand Up @@ -886,6 +899,7 @@ object Binding extends MonadicFactory.WithTypeClass[Monad, Binding] {
override private[binding] def addPatchedListener(listener: PatchedListener[B]): Unit = {
if (publisher.isEmpty) {
upstream.addPatchedListener(upstreamListener)
refreshCache()
for (child <- cacheData) {
child.addPatchedListener(childListener)
}
Expand Down
76 changes: 60 additions & 16 deletions Binding/src/test/scala/com/thoughtworks/binding/BindingTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
*/

package com.thoughtworks.binding

Expand All @@ -44,7 +44,6 @@ final class BindingTest extends FreeSpec with Matchers {
}
}


"hello world" in {
val target = Var("World")
val hello = Binding {
Expand Down Expand Up @@ -86,10 +85,9 @@ final class BindingTest extends FreeSpec with Matchers {
expr2.bind + 100
}


var resultChanged = 0

val expr1Value0 = expr1.value
assert(expr1.value == 0)

expr1.addChangedListener(new ChangedListener[Any] {
override def changed(event: ChangedEvent[Any]): Unit = {
Expand All @@ -98,14 +96,11 @@ final class BindingTest extends FreeSpec with Matchers {
})

assert(resultChanged == 0)
assert(expr1.value == expr1Value0)
assert(expr1.value == 32100)

expr3.value = 4000


assert(resultChanged == 1)
assert(expr1.value != expr1Value0)
assert(expr1.value == 34100)

}
Expand Down Expand Up @@ -182,7 +177,16 @@ final class BindingTest extends FreeSpec with Matchers {
assert(event.getSource == mapped)
assert(event.from == 0)
assert(event.replaced == 0)
assert(event.that == Seq("ForYield 0/2", "ForYield 1/2", "ForYield 0/3", "ForYield 1/3", "ForYield 2/3", "ForYield 0/4", "ForYield 1/4", "ForYield 2/4", "ForYield 3/4"))
assert(
event.that == Seq("ForYield 0/2",
"ForYield 1/2",
"ForYield 0/3",
"ForYield 1/3",
"ForYield 2/3",
"ForYield 0/4",
"ForYield 1/4",
"ForYield 2/4",
"ForYield 3/4"))
}
source.value += 0
assert(sourceEvents.length == 3)
Expand Down Expand Up @@ -211,7 +215,21 @@ final class BindingTest extends FreeSpec with Matchers {
assert(event.replaced == 0)
assert(event.that == Seq("ForYield 0/3", "ForYield 1/3", "ForYield 2/3"))
}
assert(mapped.value == Seq("ForYield 0/2", "ForYield 1/2", "ForYield 0/3", "ForYield 1/3", "ForYield 2/3", "ForYield 0/4", "ForYield 1/4", "ForYield 2/4", "ForYield 3/4", "ForYield 0/3", "ForYield 1/3", "ForYield 2/3"))
assert(
mapped.value == Seq(
"ForYield 0/2",
"ForYield 1/2",
"ForYield 0/3",
"ForYield 1/3",
"ForYield 2/3",
"ForYield 0/4",
"ForYield 1/4",
"ForYield 2/4",
"ForYield 3/4",
"ForYield 0/3",
"ForYield 1/3",
"ForYield 2/3"
))
prefix.value = "3"
assert(sourceEvents.length == 4)
assert(mapped.value == Seq("3 0/2", "3 1/2", "3 0/4", "3 1/4", "3 2/4", "3 3/4"))
Expand Down Expand Up @@ -271,7 +289,16 @@ final class BindingTest extends FreeSpec with Matchers {
assert(event.getSource == mapped)
assert(event.from == 0)
assert(event.replaced == 0)
assert(event.that == Seq("ForYield 0/2", "ForYield 1/2", "ForYield 0/3", "ForYield 1/3", "ForYield 2/3", "ForYield 0/4", "ForYield 1/4", "ForYield 2/4", "ForYield 3/4"))
assert(
event.that == Seq("ForYield 0/2",
"ForYield 1/2",
"ForYield 0/3",
"ForYield 1/3",
"ForYield 2/3",
"ForYield 0/4",
"ForYield 1/4",
"ForYield 2/4",
"ForYield 3/4"))
}
source.value += 0
assert(sourceEvents.length == 3)
Expand Down Expand Up @@ -303,7 +330,8 @@ final class BindingTest extends FreeSpec with Matchers {
prefix.value = "p"
assert(sourceEvents.length == 4)
assert(mappedEvents.length == 15)
val expected = Seq("p 0/2", "p 1/2", "p 0/3", "p 1/3", "p 2/3", "p 0/4", "p 1/4", "p 2/4", "p 3/4", "p 0/3", "p 1/3", "p 2/3")
val expected =
Seq("p 0/2", "p 1/2", "p 0/3", "p 1/3", "p 2/3", "p 0/4", "p 1/4", "p 2/4", "p 3/4", "p 0/3", "p 1/3", "p 2/3")
for (i <- 0 until 12) {
mappedEvents(i + 3) match {
case event: PatchedEvent[_] =>
Expand Down Expand Up @@ -561,7 +589,7 @@ final class BindingTest extends FreeSpec with Matchers {
Binding {
val myVars = Vars(1, 2, 100, 3)
val filtered = myVars.withFilter(_ < 10).map(x => x)

filtered.watch()
assert(filtered.value == Seq(1, 2, 3))
}
}
Expand All @@ -579,7 +607,9 @@ final class BindingTest extends FreeSpec with Matchers {
var count: Int = 0
val a: Var[Int] = Var(1)
val b: Var[Int] = Var(2)
def mkRx(i: Int) = (b: Binding[Int]).map { v => count += 1; i + v }
def mkRx(i: Int) = (b: Binding[Int]).map { v =>
count += 1; i + v
}

val c: Binding[Int] = (a: Binding[Int]).flatMap(mkRx)
c.watch()
Expand All @@ -593,7 +623,9 @@ final class BindingTest extends FreeSpec with Matchers {
b.value = 3
assert((7, 3) == ((c.value, count)))

(0 to 100).foreach { i => a.value = i }
(0 to 100).foreach { i =>
a.value = i
}
assert((103, 104) == ((c.value, count)))

b.value = 4
Expand Down Expand Up @@ -628,12 +660,24 @@ final class BindingTest extends FreeSpec with Matchers {
a.value = 500
aPlusOneTimesBPlusOn.value should be((500 + 1) * (200 + 1))
aFlushCount should be(2)
bFlushCount should be(1)
bFlushCount should be(2)
b.value = 600
aPlusOneTimesBPlusOn.value should be((500 + 1) * (600 + 1))
aFlushCount should be(2)
bFlushCount should be(2)
bFlushCount should be(3)

}

"for / yield / if" in {
def domMethod() = Binding {
val myVars = Vars(1, 2, 100, 3)
val filtered = for {
myVar <- myVars
if myVar < 10
} yield myVar
filtered.watch()
assert(filtered.value == Seq(1, 2, 3))
}
domMethod()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.thoughtworks.binding.regression

import com.thoughtworks.binding.Binding
import com.thoughtworks.binding.Binding.{Var, Vars}
import org.scalatest.{FreeSpec, Matchers}

/** Test for https://github.com/ThoughtWorksInc/Binding.scala/issues/56
* @author 杨博 (Yang Bo)
*/
final class Issue56 extends FreeSpec with Matchers {

"test" in {
var dataSource = Var[Int](100)
val isEnabled = Var[Boolean](false)

val mappedData = Binding {
dataSource.bind + 1
}

val result = Binding {
if (isEnabled.bind) {
mappedData.bind
} else {
0
}
}

result.watch()
dataSource.value = 300
isEnabled.value = true
result.value should be(301)
}

}
1 change: 1 addition & 0 deletions dom/src/test/scala/com/thoughtworks/binding/domTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ final class domTest extends FreeSpec with Matchers {
myVar <- myVars
if myVar < 10
} yield myVar
filtered.watch()
assert(filtered.value == Seq(1, 2, 3))
}
domMethod()
Expand Down

0 comments on commit 8bc7845

Please sign in to comment.