├── .classpath
├── .project
├── LICENSE
├── NOTICE
├── README.md
├── lib
├── hamcrest.jar
├── joda-convert-1.2.jar
├── joda-time-2.0-sources.jar
├── joda-time-2.0.jar
├── jsr166y.jar
├── junit.jar
└── scalatest-1.2.1-SNAPSHOT.jar
├── src
└── scala
│ └── react
│ ├── Debug.scala
│ ├── Domain.scala
│ ├── EngineModule.scala
│ ├── EventModule.scala
│ ├── FlowModule.scala
│ ├── ReactiveModule.scala
│ ├── SchedulerModule.scala
│ ├── SignalModule.scala
│ ├── TopoQueue.scala
│ └── monitor
│ ├── IO.scala
│ ├── LogDebug.scala
│ ├── LogFormat.scala
│ └── PagedWriter.scala
└── test
└── scala
└── react
└── test
├── AllTests.java
├── CombinatorTests.scala
├── FlowTests.scala
├── ReactiveTests.scala
├── ReactorTests.scala
├── RouterTests.scala
├── TopoQueueTests.scala
└── utils
├── JTestUtils.java
├── TestDomain.scala
└── TestUtils.scala
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | scala-react-2
4 |
5 |
6 |
7 |
8 |
9 | org.scala-ide.sdt.core.scalabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.scala-ide.sdt.core.scalanature
16 | org.eclipse.jdt.core.javanature
17 |
18 |
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | scala.react
2 | Copyright 2012 Ingo Maier
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # scala.react
2 |
3 | Scala.react is a reactive programming library for Scala.
--------------------------------------------------------------------------------
/lib/hamcrest.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ingoem/scala-react/5713070438623307d12cc218617cfa50f4489df6/lib/hamcrest.jar
--------------------------------------------------------------------------------
/lib/joda-convert-1.2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ingoem/scala-react/5713070438623307d12cc218617cfa50f4489df6/lib/joda-convert-1.2.jar
--------------------------------------------------------------------------------
/lib/joda-time-2.0-sources.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ingoem/scala-react/5713070438623307d12cc218617cfa50f4489df6/lib/joda-time-2.0-sources.jar
--------------------------------------------------------------------------------
/lib/joda-time-2.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ingoem/scala-react/5713070438623307d12cc218617cfa50f4489df6/lib/joda-time-2.0.jar
--------------------------------------------------------------------------------
/lib/jsr166y.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ingoem/scala-react/5713070438623307d12cc218617cfa50f4489df6/lib/jsr166y.jar
--------------------------------------------------------------------------------
/lib/junit.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ingoem/scala-react/5713070438623307d12cc218617cfa50f4489df6/lib/junit.jar
--------------------------------------------------------------------------------
/lib/scalatest-1.2.1-SNAPSHOT.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ingoem/scala-react/5713070438623307d12cc218617cfa50f4489df6/lib/scalatest-1.2.1-SNAPSHOT.jar
--------------------------------------------------------------------------------
/src/scala/react/Debug.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 |
3 | import java.util.WeakHashMap
4 | import java.text.SimpleDateFormat
5 | import scala.annotation.elidable
6 | import scala.collection.mutable.ArrayBuffer
7 |
8 | abstract class Debug[D <: Domain](val domain: D) {
9 | import domain._
10 |
11 | def setName(node: Node, name: String)
12 | def getName(node: Node): String
13 |
14 |
15 | // The thread on which this engine is currently running, `null` if not in a turn.
16 | @volatile private var thread: Thread = null
17 |
18 | /**
19 | * Returns `true` if the current thread is the thread the engine is running on.
20 | */
21 | @elidable(800) protected def isInTurn = Thread.currentThread == thread
22 | @elidable(800) protected def isInTurn_=(b: Boolean) {
23 | thread = if (b) Thread.currentThread else null
24 | }
25 |
26 | @elidable(800) def enterTurn(id: Long) {
27 | assert(!isInTurn, "Tried to run a turn before the previous one was finished.")
28 | logEnterTurn(id)
29 | isInTurn = true
30 | }
31 | @elidable(800) def leaveTurn(id: Long) {
32 | assert(isInTurn)
33 | logLeaveTurn(id)
34 | isInTurn = false
35 | }
36 |
37 | @elidable(800) def assertInTurn() = assert(isInTurn, "This method must be run on its domain " + this)
38 |
39 | @elidable(800) def logStart()
40 | @elidable(800) def logEnterTurn(id: Long)
41 | @elidable(800) def logLeaveTurn(id: Long)
42 | @elidable(800) def logTock(n: Node)
43 | @elidable(800) def logLevelMismatch(accessor: Node, accessed: Node)
44 | @elidable(800) def logTodo(t: Any)
45 | }
46 |
47 | class NilDebug[D <: Domain](dom: D) extends Debug(dom) {
48 | import domain._
49 |
50 | def setName(node: Node, name: String) { }
51 | def getName(node: Node): String = ""
52 |
53 | def logStart() {}
54 | def logEnterTurn(id: Long) {}
55 | def logLeaveTurn(id: Long) {}
56 | def logTock(n: Node) {}
57 | def logLevelMismatch(accessor: Node, accessed: Node) {}
58 | def logTodo(t: Any) {}
59 | }
60 |
61 | abstract class AbstractDebug[D <: Domain](dom: D) extends Debug(dom) {
62 | import domain._
63 | private val names = new WeakHashMap[Node, String]
64 | def setName(node: Node, name: String) { names.put(node, name) }
65 | def getName(node: Node): String = names.get(node)
66 | }
67 |
68 | abstract class PrintDebug[D <: Domain](dom: D) extends AbstractDebug(dom) {
69 | import domain._
70 |
71 | private val dateFormat = new SimpleDateFormat("d MMM yyyy HH:mm:ss.SSS")
72 |
73 | def log(s: String)
74 |
75 | private def time() = System.currentTimeMillis()
76 | private def timePrefix = "+" + (time()-startTime) + "ms: "
77 | private var startTime = 0L
78 |
79 | def logStart() {
80 | startTime = time()
81 | log("Start domain " + domain + " at " + dateFormat.format(startTime))
82 | }
83 | def logEnterTurn(id: Long) = log(timePrefix + "enter turn " + id)
84 | def logLeaveTurn(id: Long) = log(timePrefix + "leave turn " + id)
85 | def logTock(n: Node) = log("Tock " + n)
86 | def logLevelMismatch(accessor: Node, accessed: Node) = log("Level mismatch when " + accessor + " accessed " + accessed)
87 | def logTodo(t: Any) = log("Todo " + t)
88 | }
89 |
90 |
91 | class ConsoleDebug[D <: Domain](dom: D) extends PrintDebug(dom) {
92 | def log(s: String) = println(s)
93 | }
--------------------------------------------------------------------------------
/src/scala/react/Domain.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 | import scala.annotation.elidable
3 |
4 | /**
5 | * Defines all reactive classes by mixing in all reactive modules.
6 | *
7 | * Clients usually create an 'object myDomain extends Domain' and import it.
8 | *
9 | * Except the scheduler interface, no method in neither class is guaranteed to be thread-safe.
10 | */
11 | abstract class Domain extends ReactiveModule
12 | with SignalModule
13 | with EventModule
14 | with FlowModule
15 | with SchedulerModule { domain =>
16 |
17 | protected val scheduler: Scheduler
18 | protected def engine: Engine
19 |
20 | val debug: Debug[this.type] =
21 | System.getProperty("scala.react.debug", "no").toLowerCase match {
22 | case "no" => new NilDebug[this.type](domain)
23 | case "print" => new ConsoleDebug[this.type](this)
24 | case "log" => new monitor.LogDebug[this.type](this)
25 | }
26 |
27 | /**
28 | * A reactive version of `scala.App`, running all initialization code on this domain.
29 | * Automatically starts the domain in the main method.
30 | *
31 | * @see scala.App
32 | * @see scala.DelayedInit
33 | */
34 | trait ReactiveApp extends App {
35 | def main() {}
36 |
37 | override def main(args: Array[String]) {
38 | domain schedule { super.main(args) }
39 | domain.start()
40 | main()
41 | }
42 | }
43 |
44 | /**
45 | * Starts processing events. Thread-safe.
46 | */
47 | def start() {
48 | debug.logStart()
49 | scheduler.start()
50 | }
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/src/scala/react/EngineModule.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 |
3 | import java.util.ArrayDeque
4 | import java.util.concurrent.ConcurrentLinkedQueue
5 | import scala.collection.mutable.ArrayStack
6 |
7 | abstract class EngineModule { self: Domain =>
8 | // Dependent stack stuff is inlined, since it needs to be as efficient as possible
9 | private var depStack = new Array[Node](4)
10 | private var depStackSize = 0
11 |
12 | protected def depStackPush(n: Node) {
13 | if (depStackSize == depStack.length) {
14 | val newarr = new Array[Node](depStack.length * 2)
15 | System.arraycopy(depStack, 0, newarr, 0, depStack.length)
16 | depStack = newarr
17 | }
18 | depStack(depStackSize) = n
19 | depStackSize += 1
20 | }
21 |
22 | protected def depStackPop() {
23 | if (depStackSize == 0) sys.error("Stack empty")
24 | depStackSize -= 1
25 | depStack(depStackSize) = null
26 | }
27 |
28 | protected def depStackTop: Node = depStack(depStackSize-1)
29 |
30 |
31 | // -----------------------------------------------------------------
32 |
33 |
34 | /**
35 | * Nodes that throw this exception must ensure that the dependent stack is in clean state, i.e.,
36 | * no leftovers on the stack. Otherwise dynamic dependency tracking goes havoc.
37 | */
38 | private[react] object LevelMismatch extends util.control.NoStackTrace {
39 | var accessed: Node = _
40 | }
41 |
42 | @inline def runTurn(op: => Unit) {
43 | op
44 | engine.runTurn
45 | }
46 |
47 | @inline def runReadTurn(leaf: LeafNode) {
48 | engine.runReadTurn(leaf)
49 | }
50 |
51 | abstract class Propagator {
52 | // the current turn index, first usable turn has index 2. Values 0 and 1 are reserved.
53 | private var turn = 1
54 |
55 | // the current propagation level at which this engine is or previously was
56 | protected var level = 0
57 |
58 | private var propQueue: PropQueue[StrictNode] = System.getProperty("scala.react.propqueue", "prio").toLowerCase match {
59 | case "prio" => new PriorityQueue[StrictNode] {
60 | def priority(n: StrictNode) = n.level
61 | }
62 | case "topo" => new TopoQueue[StrictNode] {
63 | def depth(n: StrictNode) = n.level
64 | }
65 | }
66 |
67 | def currentTurn: Long = turn
68 | def currentLevel: Int = level
69 |
70 | protected def applyTodos()
71 |
72 | def runTurn() {
73 | turn += 1
74 | debug.enterTurn(currentTurn)
75 |
76 | try {
77 | propagate()
78 | } catch {
79 | case e => uncaughtException(e)
80 | } finally {
81 | propQueue.clear
82 | level = 0
83 | debug.leaveTurn(currentTurn)
84 | }
85 | }
86 |
87 | def runReadTurn(leaf: LeafNode) {
88 | turn += 1
89 | debug.enterTurn(currentTurn)
90 |
91 | try {
92 | level = leaf.level
93 | leaf.invalidate()
94 | tryTock(leaf)
95 | } catch {
96 | case e => uncaughtException(e)
97 | } finally {
98 | propQueue.clear
99 | level = 0
100 | debug.leaveTurn(currentTurn)
101 | }
102 | }
103 |
104 | protected def propagate() {
105 | val queue = propQueue
106 | applyTodos()
107 | while (!queue.isEmpty) {
108 | val node = queue.dequeue
109 | assert(node.level >= level)
110 | if (node.level > level)
111 | level = node.level
112 | tryTock(node)
113 | assert(depStackSize == 0)
114 | }
115 | }
116 |
117 | def newNode(n: Node) {}
118 |
119 | def defer(dep: StrictNode) {
120 | debug.assertInTurn()
121 | val depLevel = dep.level
122 | if (depLevel == level) // fast path
123 | tryTock(dep)
124 | else if (depLevel < level) // can happen for signals that are created on the fly
125 | hoist(dep, level + 1)
126 | else propQueue += dep // default path
127 | }
128 |
129 | /**
130 | * Exceptions are caught in the main propagation loop and passed to this method.
131 | * Override to customize exception handling. The default behavior is to print a stack
132 | * trace and `sys.exit`.
133 | */
134 | protected def uncaughtException(e: Throwable) {
135 | Console.err.println("Uncaught exception in turn!")
136 | e.printStackTrace()
137 | sys.exit(-1)
138 | }
139 |
140 | /**
141 | * Try to tock a node. Hoists the node, if on wrong level.
142 | */
143 | protected def tryTock(dep: StrictNode) = try {
144 | debug.logTock(dep)
145 | dep.tock()
146 | } catch {
147 | case lm @ LevelMismatch =>
148 | val lvl = lm.accessed.level
149 | debug.logLevelMismatch(dep, lm.accessed)
150 | hoist(dep, lvl + 1)
151 | }
152 |
153 | def levelMismatch(accessed: Node) {
154 | LevelMismatch.accessed = accessed
155 | throw LevelMismatch
156 | }
157 |
158 | /**
159 | * Change the level of the given node and reschedule for later in this turn.
160 | */
161 | def hoist(dep: StrictNode, newLevel: Int) {
162 | dep.level = newLevel
163 | dep match {
164 | case dep: DependencyNode => dep.hoistDependents(newLevel + 1)
165 | case _ =>
166 | }
167 | propQueue reinsert dep
168 | }
169 |
170 | def hoist(dep: Node, newLevel: Int) {
171 | dep.level = newLevel
172 | dep match {
173 | case dep: DependencyNode => dep.hoistDependents(newLevel + 1)
174 | case _ =>
175 | }
176 | dep match {
177 | case dep: StrictNode => propQueue reinsert dep
178 | case _ =>
179 | }
180 | }
181 | }
182 |
183 | class Engine extends Propagator {
184 | // todos that can be scheduled externally, thread-safe
185 | private val asyncTodos = new ConcurrentLinkedQueue[AnyRef] // Runnable or ()=>Unit
186 | // todos that can be scheduled only inside a turn, no need for thead-safety
187 | private val localTodos = new ArrayDeque[Tickable]
188 |
189 | def schedule(r: Runnable) = {
190 | asyncTodos.add(r)
191 | scheduler.ensureTurnIsScheduled()
192 | }
193 | def schedule(op: => Unit) = {
194 | asyncTodos.add(() => op)
195 | scheduler.ensureTurnIsScheduled()
196 | }
197 |
198 | def tickNextTurn(t: Tickable) = {
199 | //assert(!localTodos.contains(t))
200 | // TODO: make localTodos a set?
201 | if (!localTodos.contains(t)) {
202 | localTodos add t
203 | scheduler.ensureTurnIsScheduled()
204 | }
205 | }
206 | protected def applyTodos() {
207 | var t = asyncTodos.poll()
208 | while (t ne null) {
209 | debug.logTodo(t)
210 | t match {
211 | case r: Runnable => r.run()
212 | case f: Function0[_] => f()
213 | }
214 |
215 | t = asyncTodos.poll()
216 | }
217 |
218 | while (!localTodos.isEmpty) {
219 | val t = localTodos.poll()
220 | debug.logTodo(t)
221 | t.tick()
222 | }
223 | }
224 |
225 | //def newLocalChannel[P](r: Reactive[P, Any], p: P): Channel[P] = new LocalDelayChannel(r, p)
226 | }
227 |
228 | class LocalDelayChannel[P](val node: Reactive[P, Any], p: P) extends Tickable with Channel[P] {
229 | private var pulse: P = p
230 | private var added = false
231 | def push(r: Reactive[P, Any], p: P) {
232 | pulse = p
233 | if (!added) {
234 | added = true
235 | engine.tickNextTurn(this)
236 | }
237 | }
238 | def get(r: Reactive[P, Any]) = pulse
239 |
240 | def tick() {
241 | added = false
242 | node.tick()
243 | }
244 | }
245 | }
--------------------------------------------------------------------------------
/src/scala/react/EventModule.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 |
3 | import scala.util.continuations._
4 |
5 | trait EventModule { module: Domain =>
6 | object Events {
7 | def once[A](a: A): Events[A] = {
8 | val es = EventSource[A]
9 | es << a
10 | es
11 | }
12 |
13 | /**
14 | * Creates a flow event that runs through the given `op` once.
15 | */
16 | def flow[A](op: EventsFlowOps[A] => Unit @suspendable): Events[A] = new FlowEvents[A] {
17 | def body = op(this)
18 | }
19 |
20 | /**
21 | * Creates a flow event that runs through the given `op` repeatedly until disposed, e.g., by
22 | * calling `halt`.
23 | */
24 | def loop[A](op: EventsFlowOps[A] => Unit @suspendable): Events[A] = new FlowEvents[A] {
25 | def body = while (!isDisposed) op(this)
26 | }
27 |
28 | /**
29 | * An event stream that never emits.
30 | */
31 | def never[A]: Events[A] = Never
32 | object Never extends Events[Nothing] with MuteNode
33 |
34 | class MergedAny(val inputs: Iterable[Reactive[Any, Any]]) extends Events[Unit]
35 | with DependentN
36 | with LazyNode {
37 | inputs foreach { _ subscribe this }
38 |
39 | def doValidatePulse() {
40 | if (inputs exists { _.isEmitting }) emit()
41 | else mute()
42 | }
43 | }
44 |
45 | def mergeAny(inputs: Reactive[Any, Any]*): Events[Unit] = new MergedAny(inputs)
46 | }
47 |
48 | /**
49 | * An event stream. Can emit in a propagation turn, but holds no value otherwise.
50 | */
51 | abstract class Events[+A] extends TotalReactive[A, Unit] with SimpleReactive[A, Unit] { outer =>
52 | def getValue: Unit = ()
53 |
54 | protected class Mapped[B](f: A => B) extends LazyEvents1[A, B](outer) {
55 | protected[this] def react(a: A) { emit(f(a)) }
56 | }
57 |
58 | protected class Filtered(pred: A => Boolean) extends StrictEvents1[A, A](outer) {
59 | protected[this] def react(a: A) { if (pred(a)) emit(a) }
60 | }
61 |
62 | protected class Collected[B](f: PartialFunction[A, B]) extends StrictEvents1[A, B](outer) {
63 | protected[this] def react(a: A) { if (f isDefinedAt a) emit(f(a)) }
64 | }
65 |
66 | protected class Taken(private var count: Int) extends StrictEvents1[A, A](outer) {
67 | protected[this] def react(a: A) {
68 | if (count > 0) {
69 | count -= 1
70 | emit(a)
71 | } else disconnect()
72 | }
73 | }
74 |
75 | protected class Dropped(private var count: Int) extends StrictEvents1[A, A](outer) {
76 | protected[this] def react(a: A) {
77 | if (count > 0) count -= 1
78 | else emit(a)
79 | }
80 | }
81 |
82 | protected class Delayed(delay: Int) extends StrictEvents1[A, A](outer) {
83 | if(delay <= 0) throw new IllegalArgumentException("Delay must be >= 1.")
84 |
85 | private val buffer = new Array[Any](delay) // ring buffer
86 | private var head = 0
87 | private var size = 0 // only used until we have seen delay number of events
88 |
89 | protected[this] def react(a: A) {
90 | if(size < delay) {
91 | buffer(size) = a
92 | size += 1
93 | } else {
94 | emit(buffer(head).asInstanceOf[A])
95 | buffer(head) = a
96 | head = (head + 1) % delay
97 | }
98 | }
99 | }
100 |
101 | protected class Scanned[B](init: B)(f: (B, A) => B) extends StrictEvents1[A, B](outer) {
102 | pulse = init
103 |
104 | protected[this] def react(a: A) { emit(f(this.pulse, a)) }
105 | }
106 |
107 | protected class ScannedMutable[B](init: B)(f: (B, A) => B) extends StrictEvents1[A, B](outer) {
108 | pulse = init
109 |
110 | protected[this] def react(a: A) {
111 | mutable {
112 | emit(f(this.pulse, a))
113 | }
114 | }
115 | }
116 |
117 | protected class Scanned1[B >: A](f: (B, B) => B) extends StrictEvents1[B, B](outer) {
118 | private var started = false
119 | protected[this] def react(a: B) = {
120 | if (started) emit(f(this.pulse, a))
121 | else {
122 | started = true
123 | emit(a)
124 | }
125 | }
126 | }
127 |
128 | protected class Merged[B >: A](val input1: Events[B], val input2: Events[B]) extends Events[B]
129 | with Dependent2[B, Unit]
130 | with LazyNode {
131 | input1 subscribe this
132 | input2 subscribe this
133 |
134 | def doValidatePulse() {
135 | input1.ifEmittingElse { emit _ } {
136 | input2.ifEmittingElse { emit _ } { mute() }
137 | }
138 | }
139 | }
140 |
141 | protected class Flattened[B](isEvents: A => Events[B]) extends LazyEvents[B] with ManagesDependencies {
142 | private var cur: Events[B] = null
143 |
144 | // level = (if(cur==null) outer.level else math.max(outer.level, cur.level)) + 1
145 |
146 | outer.subscribe(this)
147 | def doValidatePulse() {
148 | outer.ifEmitting { e =>
149 | if (cur != null) cur.unsubscribe(this)
150 | cur = isEvents(e)
151 | cur.subscribe(this)
152 | }
153 | if (cur != null) cur.ifEmitting { x =>
154 | emit(x)
155 | }
156 | }
157 |
158 | protected override def disconnect() {
159 | outer.unsubscribe(this)
160 | cur.unsubscribe(this)
161 | super.disconnect()
162 | }
163 | }
164 |
165 | /**
166 | * An event stream that emits every value from this stream mapped by the given function.
167 | */
168 | def map[B](f: A => B): Events[B] = new Mapped(f)
169 | /**
170 | * An event stream that emits all values from this stream for which the given predicate
171 | * evaluates to `true`.
172 | */
173 | def filter(p: A => Boolean): Events[A] = new Filtered(p)
174 | /**
175 | * An event stream that emits all values from this stream for which the given partial function
176 | * is defined. Additionally maps, i.e., `es collect { case e if p(e) => f(e) }` is equivalent to
177 | * `es filter p map f`.
178 | */
179 | def collect[B](f: PartialFunction[A, B]): Events[B] = new Collected(f)
180 |
181 | /**
182 | * An event stream that applies `init` and the value from this stream to the given
183 | * function and emits the result at the time of the second event. At the time of the
184 | * third event, it applies the last result and third value from this stream to the given
185 | * function and emits the result ... and so on.
186 | */
187 | def scan[B](init: B)(f: (B, A) => B): Events[B] = new Scanned(init)(f)
188 |
189 | /**
190 | * An event stream that applies the first and second value from this stream to the given
191 | * function and emits the result at the time of the second event. At the time of the third
192 | * event, it applies the last result and third value from this stream to the given function
193 | * and emits the result ... and so on.
194 | */
195 | def scan1[B >: A](f: (B, B) => B): Events[B] = new Scanned1(f)
196 |
197 | /**
198 | * A scan that can skip events from this event stream.
199 | *
200 | * Wraps the call to `f` in a `mutable` block, i.e., a call to `mute` inside `f` is safe and
201 | * indicates that the current event from this event stream should be skipped and the resulting
202 | * event stream should not emit in that turn.
203 | */
204 | def scanMutable[B](init: B)(f: (B, A) => B): Events[B] = new ScannedMutable(init)(f)
205 |
206 | /**
207 | * An event stream that emits the first `count` events from this stream.
208 | */
209 | def take(count: Int): Events[A] = new Taken(count)
210 |
211 | /**
212 | * An event stream that emits all event from this stream after the first `count` events.
213 | */
214 | def drop(count: Int): Events[A] = new Dropped(count)
215 |
216 | def delay(count: Int): Events[A] = new Delayed(count)
217 |
218 | /**
219 | * An event stream that emits the values from both this and the given stream. It is left-biased,
220 | * i.e., emits a value from this stream if both input stream emit at the same time.
221 | */
222 | def merge[B >: A](that: Events[B]): Events[B] = new Merged(this, that)
223 |
224 | /**
225 | * A signal that initially holds `init` and then always the latest emitted value from this
226 | * stream.
227 | */
228 | def hold[B >: A](init: B): Signal[B] = new HoldSignal(init)(this)
229 |
230 | def flatten[B](implicit isEvents: A => Events[B]): Events[B] = new Flattened[B](isEvents)
231 | }
232 |
233 | /**
234 | * An event stream with a single input.
235 | */
236 | abstract class Events1[+A, +B](protected val input: Reactive[A, Any])
237 | extends Events[B]
238 | with Dependent1[A, Any] {
239 |
240 | connect()
241 |
242 | def doValidatePulse() {
243 | input.ifEmitting(react _)
244 | setPulseValid()
245 | }
246 | protected[this] def react(a: A)
247 | }
248 |
249 | abstract class LazyEvents[+A]
250 | extends Events[A]
251 | with LazyNode
252 |
253 | abstract class StrictEvents[+A]
254 | extends Events[A]
255 | with LazyNode
256 |
257 | abstract class LazyEvents1[+A, +B](input: Reactive[A, Any])
258 | extends Events1[A, B](input)
259 | with LazyNode
260 |
261 | abstract class StrictEvents1[+A, +B](input: Reactive[A, Any])
262 | extends Events1[A, B](input)
263 | with StrictNode
264 |
265 | protected[this] class ChangeEvents[P](input: Reactive[P, Any]) extends LazyEvents1[P, P](input) {
266 | def react(p: P) { emit(p) }
267 | }
268 |
269 | object EventSource {
270 | def apply[A](implicit owner: Owner): EventSource[A] = new EventSource[A](owner)
271 | }
272 |
273 | /**
274 | * An externally mutable event stream.
275 | */
276 | class EventSource[A] protected (val owner: Owner) extends Events[A] with SimpleSource[A, Unit] with StrictNode {
277 | protected[react] val channel = owner.newChannel(this, null.asInstanceOf[A])
278 |
279 | /**
280 | * Lets this stream emit the given value, either in the next turn, if the owner is the domain or
281 | * in the current turn if the owner is a router.
282 | */
283 | def <<(a: A) {
284 | if (owner eq DomainOwner) channel.push(this, a)
285 | else {
286 | emit(a)
287 | engine.defer(this)
288 | }
289 | }
290 |
291 | override def reactiveDescriptor = "EventSource"
292 | }
293 | }
--------------------------------------------------------------------------------
/src/scala/react/FlowModule.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 |
3 | import scala.util.continuations._
4 |
5 | trait FlowModule { module: Domain =>
6 | private val idContinuation = ()=>()
7 |
8 | trait FlowNode extends StrictNode {
9 | protected[react] var _continue = initialContinuation
10 |
11 | protected def continue() = _continue()
12 | protected def initialContinuation: () => Unit = () => reset {
13 | body()
14 | //TODO: dispose()
15 | }
16 |
17 | override def dispose() {
18 | _continue = idContinuation
19 | super.dispose()
20 | }
21 |
22 | def body(): Unit @suspendable
23 |
24 | protected[react] def shiftAndContinue[A](body: (A => Unit) => Unit) =
25 | shift { (k: A => Unit) =>
26 | // we need to save the continuation before we run it, so we always have the very latest
27 | // continuation when a level mismatch exception is thrown.
28 | _continue = () => body(k)
29 | continue()
30 | }
31 |
32 | /**
33 | * Continues with the given continuation in the next cycle.
34 | */
35 | protected[react] def continueLater(k: => Unit) = {
36 | _continue = () => k
37 | engine.tickNextTurn(this)
38 | }
39 |
40 | /**
41 | * Continues with the given continuation and returns the remaining continuation.
42 | */
43 | protected[react] def continueWith(k: => Unit): (()=>Unit) = {
44 | _continue = () => k
45 | continue()
46 | _continue
47 | }
48 | }
49 |
50 |
51 | trait FlowOps { this: FlowNode =>
52 |
53 | /**
54 | * Suspends the reactive and continues execution in the next turn.
55 | */
56 | def pause: Unit @suspendable = shift { (k: Unit => Unit) =>
57 | continueLater { k() }
58 | }
59 |
60 | /**
61 | * Waits for a pulse from the given reactive and returns it. Returns immediately, if the
62 | * reactive is currently emitting.
63 | */
64 | def await[B](input: Reactive[B, Any]): B @suspendable = shiftAndContinue[B] { k =>
65 | input.ifEmittingElse { p =>
66 | /*if (!reactive.isDisposed)*/ k(p)
67 | } {
68 | input subscribe this
69 | }
70 | }
71 |
72 | /**
73 | * Waits for the next pulse from the given reactive and returns it. Never returns immediately,
74 | * even if the reactive is currently emitting. Equivalent to `pause; await input`
75 | */
76 | def awaitNext[B](input: Reactive[B, Any]): B @suspendable = {
77 | pause
78 | await(input)
79 | }
80 |
81 | /**
82 | * Halts the reactive and clears internal data structures.
83 | * Once halted, a reactive cannot resume.
84 | */
85 | def halt: Unit @suspendable = shift { (k: Unit => Unit) => dispose() }
86 |
87 | /**
88 | * Repeatedly executes `body` until the given reactive `r` emits.
89 | * Immediately returns if `r` is currently emitting. Otherwise, it finishes the iteration
90 | * in which `r` emits, i.e., the execution of the body is always completed before
91 | * returning. Equivalent to
92 | *
93 | * {{{
94 | * var done = false
95 | * var e: A
96 | * par {
97 | * e = next(es)
98 | * done = true
99 | * } {
100 | * while (!done) { body }
101 | * }
102 | * e
103 | * }}}
104 | */
105 | def loopEndUntil[A](r: Reactive[A, Any])(body: => Unit @suspendable): A @suspendable = {
106 | var done = false
107 | par {
108 | await(r)
109 | done = true
110 | } {
111 | while (!done) { body }
112 | }
113 | //assert(r.isEmitting)
114 | r.getPulse
115 | }
116 |
117 | /**
118 | * Same as `loopEndUntil` but aborts the execution of `body` in the turn the given reactive
119 | * emits. Equivalent to
120 | *
121 | * {{{
122 | * var e: A
123 | * abortOn(r) {
124 | * while (true) { body }
125 | * }
126 | * e
127 | * }}}
128 | */
129 | def loopUntil[A](r: Reactive[A, Any])(body: => Unit @suspendable): A @suspendable = {
130 | abortOn(r) {
131 | while (true) { body }
132 | }
133 | //assert(r.isEmitting)
134 | r.getPulse
135 | }
136 |
137 | /**
138 | * Runs the given `body` until the given reactive emits. Does not run `body` if the reactive
139 | * is currently emitting.
140 | */
141 | def abortOn[A](input: Reactive[A, Any])(body: => Unit @suspendable): Unit @suspendable = par {
142 | await(input)
143 | join
144 | } {
145 | body
146 | join
147 | }
148 |
149 | //private var _join = () => ()
150 | private var _join: scala.runtime.IntRef = null
151 |
152 | /**
153 | * Halts the execution of the current branch and, if present, joins the innermost
154 | * enclosing `par` expression. Only halts the current branch if there is no enclosing `par`.
155 | */
156 | def join: Unit @suspendable = shiftAndContinue[Unit] { k =>
157 | if(_join == null) throw new IllegalStateException("Join called in wrong context.")
158 | _join.elem = 0
159 | }
160 |
161 | /**
162 | * First runs the `left` branch until it pauses, finishes or calls `join`. Then does
163 | * the same with the `right` branch. If both branches finish, or at least one branch called
164 | * `join`, this method immediately returns. If no branch called `join` and at least one branch
165 | * is not finished yet, this method pauses and next turn, will continue evaluating each branch
166 | * where it stopped previously. It proceeds so until both branches are finsihed or at least one
167 | * calls `join`.
168 | */
169 | def par(left: => Unit @suspendable)(right: => Unit @suspendable): Unit @suspendable =
170 | shiftAndContinue[Unit] { exitK =>
171 | val latch = new scala.runtime.IntRef(2)
172 |
173 | def cont(left: => Unit)(right: => Unit): Unit = {
174 | val oldLatch = _join // stack joins for nested pars
175 | _join = latch
176 | val leftK = continueWith(left)
177 | if (latch.elem > 0) {
178 | val rightK = continueWith(right)
179 | _join = oldLatch
180 | if (latch.elem > 0) {
181 | _continue = () => cont { leftK() } { rightK() }
182 | } else exitK()
183 | } else exitK()
184 | }
185 |
186 | cont { reset { left; latch.elem -= 1 } } { reset { right; latch.elem -= 1 } }
187 | }
188 | }
189 |
190 | trait SimpleFlowOps[P, V, R <: SimpleFlowReactive[P, V, R]] extends FlowOps { this: R =>
191 |
192 | }
193 |
194 | trait SimpleFlowReactive[P, V, R <: SimpleFlowReactive[P, V, R]] extends OpaqueReactive[P, V]
195 | with FlowNode
196 | with SimpleReactive[P, V]
197 | with SimpleFlowOps[P, V, R] { this: R =>
198 | override protected[react] def emit(p: Any) = super.emit(p)
199 | def react() { continue() }
200 | }
201 |
202 | trait SignalFlowOps[A] extends SimpleFlowOps[A, A, FlowSignal[A]] { this: FlowSignal[A] =>
203 | def update(a: A): Unit @suspendable = emit(a)
204 | def previous = getValue
205 | }
206 |
207 | trait EventsFlowOps[A] extends SimpleFlowOps[A, Unit, FlowEvents[A]] { this: FlowEvents[A] =>
208 | def <<(a: A): Unit @suspendable = emit(a)
209 | }
210 |
211 | abstract class FlowSignal[A](init: A) extends Signal[A]
212 | with SimpleFlowReactive[A, A, FlowSignal[A]]
213 | with SignalFlowOps[A] {
214 | pulse = init
215 | tick()
216 |
217 | def toStrict: Signal[A] = this
218 |
219 | override def reactiveDescriptor = "FlowSignal"
220 | }
221 |
222 | abstract class FlowEvents[A] extends Events[A]
223 | with SimpleFlowReactive[A, Unit, FlowEvents[A]]
224 | with EventsFlowOps[A] {
225 | tick()
226 | override def reactiveDescriptor = "FlowEvents"
227 | }
228 |
229 | object Reactor {
230 | def flow[A](op: FlowOps => Unit @suspendable): Reactor = new Reactor {
231 | def body = op(this)
232 | }
233 |
234 | /**
235 | * Creates a flow event that runs through the given `op` repeatedly until disposed, e.g., by
236 | * calling `halt`.
237 | */
238 | def loop[A](op: FlowOps => Unit @suspendable): Reactor = new Reactor {
239 | def body = while (!isDisposed) op(this)
240 | }
241 | }
242 |
243 | abstract class Reactor extends FlowNode with LeafNode with FlowOps {
244 | tick()
245 | def react() { continue() }
246 | override def reactiveDescriptor = "Reactor"
247 | }
248 | }
--------------------------------------------------------------------------------
/src/scala/react/ReactiveModule.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 | import java.lang.ref.WeakReference
3 |
4 | /**
5 | * Defines the base classes for the reactive framework.
6 | *
7 | * Note: The main backbone of the hierarchy is implemented in classes instead of traits for
8 | * efficiency reasons.
9 | */
10 | abstract class ReactiveModule extends EngineModule { module: Domain =>
11 | object MuteControl extends scala.util.control.ControlThrowable
12 |
13 | /**
14 | * Control flow operator similar to `break`. May only be used inside a `mutable` block inside of
15 | * a `Reactive` implementation and indicates that the current reactive may not emit in
16 | * the current turn.
17 | */
18 | def mute: Nothing = throw MuteControl
19 |
20 | /**
21 | * Control flow operator similar to `breakable`. Retutrns `true` if `mute` was not called in `op`,
22 | * returns `false` otherwise. Some combinators use this internally, e.g., `Events.scanMutable`.
23 | */
24 | @inline final def mutable[B](op: => B): Boolean = try {
25 | op
26 | true
27 | } catch {
28 | case MuteControl => false
29 | }
30 |
31 | /**
32 | * Runs the given `op` on maximum level in this turn, i.e., `op` is safe to query any other
33 | * node.
34 | */
35 | def doLater(op: => Unit) {
36 | new DoLaterNode { def react() = op }
37 | }
38 |
39 | private abstract class DoLaterNode extends LeafNode {
40 | tick()
41 | }
42 |
43 | //type Dependents = PoolingWeakListSet[Node]
44 | //def newDependents(): Dependents = PoolingWeakListSet.empty
45 |
46 | abstract class Tickable {
47 | protected[react] def tick()
48 | }
49 |
50 | private object Node {
51 | final private val TickNumMask: Long = ~(TickInvalidMask | TickMuteMask)
52 | final private val TickInvalidMask: Long = 1L << 63
53 | final private val TickMuteMask: Long = 1L << 62
54 | }
55 |
56 | abstract class Node extends Tickable {
57 | /* lastTick indicates whether this node's pulse is valid (lastTick > 0) or invalid (< 0). Id 0
58 | * is reserved. This is used for multiple purposes.
59 | * When ticked, its sign bit is set, when validated the sign bit is cleared. Multiple ticks
60 | * are hence idempotent, and we can use this fact to make sure we don't put strict nodes on the
61 | * queue multiple times in the same turn. When emitting, lastTick is set to the current
62 | * turn id. Therefore, we can use this field to determine when this node has been emitting the
63 | * last time.
64 | */
65 | private var tickBits: Long = 1L
66 |
67 | private[react] val refs = new WeakReference(this)
68 |
69 | import Node._
70 | protected def isPulseValid = (tickBits & TickInvalidMask) == 0
71 | def lastTicked = tickBits & TickNumMask
72 |
73 | /**
74 | * Makes sure that the pulse of this node is consistent. Might be called multiple times in a
75 | * single turn. Calls to this method must happen from a level above this node. In other words,
76 | * if checkTopology() would throw an exception when this method is called, the behavior of
77 | * this method is undefined.
78 | */
79 | protected def validatePulse() {
80 | if (!isPulseValid) {
81 | doValidatePulse()
82 | setPulseValid()
83 | }
84 | }
85 |
86 | protected def doValidatePulse()
87 |
88 | protected def setPulseValid() {
89 | if (!isPulseValid) setNotEmitting()
90 | }
91 |
92 | /**
93 | * Mark as invalid and return 'true' if this node has not been invalidated in this turn yet,
94 | * otherwise do nothing but return `false`.
95 | */
96 | final protected[react] def invalidate(): Boolean = {
97 | val mask = engine.currentTurn | TickInvalidMask
98 | if (tickBits == mask) false
99 | else {
100 | tickBits = mask
101 | true
102 | }
103 | }
104 |
105 | def isEmitting: Boolean = tickBits == engine.currentTurn
106 |
107 | protected def setEmitting() {
108 | assert((engine.currentTurn & TickInvalidMask & TickMuteMask) == 0) // overflow
109 | tickBits = engine.currentTurn
110 | }
111 | protected def setNotEmitting() {
112 | tickBits = engine.currentTurn | TickMuteMask
113 | }
114 |
115 | engine.newNode(this)
116 |
117 | /**
118 | * The topological depth value of this node.
119 | *
120 | * Implementation note: this could be a def instead of a var for some nodes, which might reduce
121 | * object size. Benchmarking, however, revealed that this method then becomes a major bottleneck,
122 | * because of virtual calls and potential indirections because of traits and when accessing
123 | * the level of a dependency. Lesson learned: stick with a var for all nodes.
124 | */
125 | protected[react] var level = 0
126 |
127 | def name: String = debug.getName(this)
128 | def name_=(nme: String): this.type = { debug.setName(this, nme); this }
129 |
130 | def engine: Engine = module.engine
131 |
132 | /**
133 | * Called exactly once per turn for all nodes in the propagation set. Not called, if not in the
134 | * propagation set. As this method may be called out of topolical order, implementations should
135 | * not access this node's dependencies.
136 | */
137 | protected[react] def tick()
138 |
139 | /**
140 | * Ticks this nodes dependents. Does nothing, if or when this nodes does not have dependents.
141 | */
142 | protected def tickDependents()
143 |
144 | /**
145 | * Throws an exception if accessed from a level <= the level of this node. No-op otherwise.
146 | */
147 | def checkTopology() =
148 | if (level >= engine.currentLevel) engine.levelMismatch(this)
149 |
150 | /**
151 | * Makes sure that the value of this node is consistent. Might be called multiple times in a
152 | * single turn. Calls to this method must happen from a level above this node. In other words,
153 | * if checkTopology() would throw an exception when this method is called, the behavior of
154 | * this method is undefined.
155 | */
156 | protected def validateValue()
157 |
158 | def isDisposed: Boolean = tickBits == 0
159 |
160 | /**
161 | * Disconnects this node from the dependency graph.
162 | *
163 | * Subclasses that override this method should always call `super.disconnect()`
164 | * so effects of this method are chained.
165 | */
166 | protected def disconnect() { tickBits = 0 }
167 |
168 | def dispose() {
169 | disconnect()
170 | }
171 |
172 | def reactiveDescriptor: String = getClass.getName
173 |
174 | override def toString = {
175 | val nme = name
176 | val prefix = if (nme != null && nme != "") name + ":" else ""
177 | prefix + reactiveDescriptor + "@" + level
178 | }
179 | }
180 |
181 | /**
182 | * A node that does nothing.
183 | */
184 | trait MuteNode extends Node {
185 | protected[react] def tick() {}
186 | override def isEmitting = false
187 | override protected def tickDependents() {}
188 | override def checkTopology() {}
189 | override protected def validatePulse() {}
190 | override def doValidatePulse() {}
191 | override protected def validateValue() {}
192 | }
193 |
194 | object NilNode extends MuteNode
195 |
196 | /**
197 | * A dependent node that is strictly evaluated, i.e., in every turn in which it is ticked.
198 | *
199 | * When ticked, it lets the engine decide when it is safe to evaluate. It does not tick
200 | * its dependents until the engine tells this node to evaluate.
201 | */
202 | trait StrictNode extends Node {
203 | protected[react] def tick() {
204 | if (invalidate()) engine.defer(this)
205 | }
206 |
207 | /**
208 | * Called by the engine when this node defered itself in `tick`. Always called in topological
209 | * order, so implementations may safely access its dependencies.
210 | */
211 | protected[react] def tock() {
212 | validatePulse()
213 | if (isEmitting) tickDependents()
214 | }
215 | }
216 |
217 | /**
218 | * A dependent node that is evaluated not before queried, i.e., it might not be evaluated
219 | * in every turn in which it was ticked.
220 | *
221 | * A node that when ticked, always invalidates itself and ticks its dependents. It evaluates not
222 | * before it is queried.
223 | */
224 | trait LazyNode extends Node {
225 | invalidate()
226 |
227 | protected[react] def tick() {
228 | if (invalidate()) tickDependents()
229 | }
230 | }
231 |
232 | trait LeafNode extends StrictNode {
233 | level = Int.MaxValue
234 | protected def tickDependents() {}
235 | protected def isValueValid = true
236 | protected def doValidatePulse() { react(); setNotEmitting() }
237 | protected def validateValue() {}
238 |
239 | protected def react()
240 | }
241 |
242 | /**
243 | * A node that maintains a set of dependents, which are stored weakly. Methods that traverse
244 | * the dependent set clear it from stale weak refs.
245 | */
246 | abstract class DependencyNode extends Node {
247 | // Null if empty, a single weak ref or an array of weak refs if not empty.
248 | // This turns out to be the fastest and one of the most space efficient ways to handle weak
249 | // dependencies (few indirections, no garbage as long as array is not resized).
250 | private var dependents: AnyRef = null
251 |
252 | // Stuff in here is all inlined, since it is absolutely performance critical
253 |
254 | /**
255 | * Tick all dependents of this node.
256 | */
257 | protected def tickDependents() {
258 | dependents match {
259 | case null =>
260 | case wref: WeakReference[Node] =>
261 | val d = wref.get
262 | if ((d eq null) || !d.isInstanceOf[ManagesDependencies] /*|| d.isDisposed*/) dependents = null
263 | if (d ne null) d.tick()
264 | case arr: Array[WeakReference[Node]] =>
265 | var i = 0
266 | while (i < arr.length) {
267 | val wref = arr(i)
268 | if (wref ne null) {
269 | val d = wref.get
270 | if ((d eq null) || !d.isInstanceOf[ManagesDependencies] /*|| d.isDisposed*/) {
271 | arr(i) = null
272 | }
273 | if (d ne null) d.tick()
274 | }
275 | i += 1
276 | }
277 | }
278 | }
279 |
280 | /**
281 | * Transitively hoist all dependents to new level.
282 | */
283 | private[react] def hoistDependents(newLevel: Int) {
284 | // impl note: in contrast to tickDependents(), don't clear unmanaged dependencies
285 | dependents match {
286 | case null =>
287 | case wref: WeakReference[Node] =>
288 | val d = wref.get
289 | if (d eq null) dependents = null
290 | else engine.hoist(d, newLevel)
291 | case arr: Array[WeakReference[Node]] =>
292 | var i = 0
293 | while (i < arr.length) {
294 | val wref = arr(i)
295 | if (wref ne null) {
296 | val d = wref.get
297 | if (d eq null) arr(i) = null
298 | else engine.hoist(d, newLevel)
299 | }
300 | i += 1
301 | }
302 | }
303 | }
304 |
305 | /**
306 | * Add the given node to this node's dependents, if not already present.
307 | */
308 | protected[react] def subscribe(dep: Node) {
309 | if ((dep ne NilNode) && !isDisposed) {
310 | dependents match {
311 | case null => dependents = dep.refs
312 | case wref: WeakReference[Node] =>
313 | if (wref eq dep.refs) return
314 | val d = wref.get
315 | if (d eq null) dependents = dep.refs
316 | else if (d eq dep) return
317 | else {
318 | val newDeps = new Array[WeakReference[Node]](4)
319 | newDeps(0) = wref
320 | newDeps(1) = dep.refs.asInstanceOf[WeakReference[Node]]
321 | dependents = newDeps
322 | }
323 | case arr: Array[WeakReference[Node]] =>
324 | val oldSize = arr.length
325 | var i = 0
326 | while (i < oldSize) {
327 | val wref = arr(i)
328 | if (wref eq dep.refs) return
329 | if (wref eq null) {
330 | arr(i) = dep.refs.asInstanceOf[WeakReference[Node]]
331 | return
332 | } /*else {
333 | val d = wref.get
334 | if(d == null || d.isDisposed) {
335 | arr(i) = dep.refs.asInstanceOf[WeakReference[Node]]
336 | return
337 | }
338 | }*/
339 | i += 1
340 | }
341 |
342 |
343 |
344 | val newDeps = new Array[WeakReference[Node]](oldSize * 2)
345 | compat.Platform.arraycopy(arr, 0, newDeps, 0, oldSize)
346 | newDeps(oldSize) = dep.refs.asInstanceOf[WeakReference[Node]]
347 | dependents = newDeps
348 | }
349 | }
350 | }
351 |
352 | /**
353 | * Removes the given node from this node's dependent set if present.
354 | */
355 | protected[react] def unsubscribe(dep: Node) {
356 | if (dep ne NilNode) {
357 | dependents match {
358 | case null =>
359 | case wref: WeakReference[Node] =>
360 | val d = wref.get
361 | if ((d eq null) || (d eq dep)) dependents = null
362 | case arr: Array[WeakReference[Node]] =>
363 | var i = 0
364 | while (i < arr.length) {
365 | val wref = arr(i)
366 | if (wref ne null) {
367 | val d = wref.get
368 | if (d eq null) arr(i) = null
369 | else if (d eq dep) {
370 | arr(i) = null
371 | return
372 | }
373 | }
374 | i += 1
375 | }
376 | }
377 | }
378 | }
379 |
380 | protected override def disconnect() {
381 | dependents = null
382 | super.disconnect()
383 | }
384 | }
385 |
386 | /**
387 | * A marker trait indicating that this node takes care of subscribing and unsubscribing itself to its
388 | * dependencies.
389 | *
390 | * Implementation note: do not turn this trait into a boolean method. An instanceof check
391 | * is much faster than a virtual trait call.
392 | */
393 | trait ManagesDependencies extends Node
394 |
395 | /**
396 | * Convenience implementation for a dependent with a single input.
397 | */
398 | trait Dependent1[+P, +V] extends Node with ManagesDependencies {
399 | protected def input: Reactive[P, V]
400 | //level = input.level + 1
401 |
402 | protected def connect() {
403 | input.subscribe(this)
404 | }
405 |
406 | protected override def disconnect() {
407 | input.unsubscribe(this)
408 | super.disconnect()
409 | }
410 | }
411 |
412 | trait Dependent2[+P, +V] extends Node with ManagesDependencies {
413 | protected def input1: Reactive[P, V]
414 | protected def input2: Reactive[P, V]
415 | //level = math.max(input1.level, input2.level) + 1
416 |
417 | protected def connect() {
418 | input1.subscribe(this)
419 | input2.subscribe(this)
420 | }
421 |
422 | protected override def disconnect() {
423 | input1.unsubscribe(this)
424 | input2.unsubscribe(this)
425 | super.disconnect()
426 | }
427 | }
428 |
429 | trait DependentN extends Node with ManagesDependencies {
430 | protected def inputs: Iterable[Reactive[Any, Any]]
431 | // level = inputs.foldLeft(0) { (l, r) => math.max(l, r.level) } + 1
432 |
433 | protected def connect() {
434 | inputs foreach { _ subscribe this }
435 | }
436 |
437 | protected override def disconnect() {
438 | inputs foreach { _ unsubscribe this }
439 | super.disconnect()
440 | }
441 | }
442 |
443 | /**
444 | * A node that emits pulses and holds values. It has three properties that can change from turn
445 | * to turn and are updated either strictly or lazily:
446 | *
447 | * - whether this reactive is emitting
448 | * - if it is emitting, what pulse it is emitting
449 | * - what value it currently holds.
450 | *
451 | * A reactive must change its value only in a turn in which it is also emitting. It can,
452 | * however, choose not to change its value when it is emitting.
453 | */
454 | abstract class Reactive[+P, +V] extends DependencyNode with Emitter[P, V] {
455 | /**
456 | * Returns the value of this reactive if defined. The result is undefined, if this reactive
457 | * isn't currently emitting.
458 | */
459 | def getValue: V
460 |
461 | def valueNow: Option[V] = ifDefinedElse[Option[V]](Some(_))(None)
462 |
463 | def isDefined: Boolean
464 |
465 | protected def validateValue() {
466 | if (!isValueValid) doValidateValue()
467 | }
468 |
469 | protected def doValidateValue()
470 | protected def isValueValid: Boolean
471 |
472 | @inline final def ifDefined[U](f: V => U) {
473 | checkTopology()
474 | validateValue()
475 | if (isDefined) f(getValue)
476 | }
477 |
478 | @inline final def ifDefinedElse[B](f: V => B)(default: => B): B = {
479 | checkTopology()
480 | validateValue()
481 | if (isDefined) f(getValue) else default
482 | }
483 |
484 | /**
485 | * Returns a pulse value which is only valid if this reactive is currently emitting.
486 | * The result is undefined (it may throw an exception), if this reactive isn't currently
487 | * emitting.
488 | */
489 | def getPulse: P
490 |
491 | def pulseNow: Option[P] = ifEmittingElse[Option[P]](Some(_))(None)
492 |
493 | /**
494 | * Runs the given function with the current value of this pulse, if this pulse is currently
495 | * present.
496 | */
497 | @inline final def ifEmitting[U](f: P => U) {
498 | checkTopology()
499 | validatePulse()
500 | assert(isPulseValid)
501 | if (isEmitting) f(getPulse)
502 | }
503 |
504 | /**
505 | * Runs the given function with the current value of this pulse and return the result,
506 | * if this pulse is currently present. Otheriwse, returns the given default value.
507 | */
508 | @inline final def ifEmittingElse[B](f: P => B)(default: => B): B = {
509 | checkTopology()
510 | validatePulse()
511 | assert(isPulseValid)
512 | if (isEmitting) f(getPulse) else default
513 | }
514 |
515 | def changes: Events[P] = new ChangeEvents[P](this)
516 |
517 | override def toString = {
518 | val s = if (isPulseValid) "" else ""
519 | val p = if (isEmitting) "p=" + getPulse else ""
520 | val v = if (isDefined) "v=" + getValue else ""
521 | super.toString + "(" + s + "," + p + "," + v + ")"
522 | }
523 | }
524 |
525 | /**
526 | * A reactive that always has a value, i.e., for which `isDefined == true` always holds.
527 | */
528 | abstract class TotalReactive[+P, +V] extends Reactive[P, V] {
529 | def now: V = {
530 | checkTopology()
531 | validateValue()
532 | getValue
533 | }
534 |
535 | def apply(): V = {
536 | checkTopology()
537 | subscribe(depStackTop)
538 | validateValue()
539 | getValue
540 | }
541 |
542 | def isDefined: Boolean = true
543 | }
544 |
545 | /**
546 | * A standard interface for an implementation of a reactive that can emit pulses. Any reactive
547 | * that emits pulses must extend this trait or one of its subtraits.
548 | */
549 | trait Emitter[+P, +V] extends Node { this: Reactive[P, V] =>
550 | /**
551 | * Sets this reactive's state to 'not emitting'.
552 | */
553 | protected[this] def mute() {
554 | assert(!isEmitting)
555 | freePulse()
556 | setNotEmitting()
557 | }
558 |
559 | /**
560 | * Lets this reactive emit the given pulse, i.e., sets the pulse and the state to 'emitting'.
561 | *
562 | * Unfortunately, this needs to stay untyped until SI-3272 is fixed, if that'll ever happen.
563 | */
564 | protected[this] def emit(p: Any)
565 |
566 | /**
567 | * Frees garbage from pulse
568 | */
569 | protected[react] def freePulse()
570 | }
571 |
572 | /**
573 | * An emitter whose pulses represent the current value as opposed to a change delta. Therefore,
574 | * the current value depends on the most recent pulse only and not on a previous value or pulse.
575 | */
576 | trait SimpleEmitter[+P, +V] extends Emitter[P, V] { this: Reactive[P, V] =>
577 | // TODO: I'd like to move most of that stuff here to Emitter, but that triggers SI-3272 or
578 | // some variation.
579 | protected[this] var pulse: P = _
580 |
581 | def getPulse = pulse
582 |
583 | protected[this] def emit(p: Any) {
584 | //val oldPulse = getPulse
585 | //val oldValue = getValue
586 | pulse = p.asInstanceOf[P]
587 | val doEmit = true //!isDefined //|| shouldEmit(oldValue, getPulse)
588 | if (doEmit) setEmitting()
589 | //else {
590 | // pulse = oldPulse
591 | // mute()
592 | // }
593 | }
594 |
595 | protected[react] def freePulse() { pulse = null.asInstanceOf[P] }
596 | }
597 |
598 | /**
599 | * An emitter whose pulses represent a delta to its previous value. Therefore, the current value
600 | * depends on the most recent pulse and a previous value.
601 | */
602 | trait DeltaEmitter[+P, +V] extends Emitter[P, V] { this: Reactive[P, V] =>
603 |
604 | }
605 |
606 | trait SimpleReactive[+P, +V] extends Reactive[P, V] with SimpleEmitter[P, V] {
607 | protected def isValueValid = isPulseValid
608 |
609 | protected def doValidateValue() { doValidatePulse() }
610 | }
611 |
612 | trait DeltaReactive[+P, +V] extends Reactive[P, V] with DeltaEmitter[P, V]
613 |
614 | /**
615 | * A reactive that accesses dependencies in a manner unpredictable by the framework. Usually
616 | * this means it evaluates some closure when notified, such as a `FuncSignal`.
617 | */
618 | trait OpaqueReactive[P, V] extends Reactive[P, V] {
619 | level = math.max(1, level)
620 |
621 | protected def doValidatePulse() {
622 | depStackPush(this)
623 | try {
624 | // eval and let reactives consulted in `op` obtain our dependent
625 | react()
626 | } finally {
627 | // always leave the dependent stack in a clean state.
628 | depStackPop()
629 | }
630 | }
631 |
632 | protected def react()
633 | }
634 |
635 | /**
636 | * Objects that create observers must inherit this trait. Maintains an internal list of created
637 | * observers. Once an instance of this trait is disposed, all it observers it maintains are
638 | * disposed.
639 | *
640 | * It is safe to create instances of this trait off-turn. Observers must be created in a turn,
641 | * however.
642 | */
643 | trait Observing { outer =>
644 | private var _obRefs: AnyRef = null
645 |
646 | protected abstract class Observer extends LeafNode {
647 | ref()
648 |
649 | protected override def disconnect() {
650 | super.disconnect()
651 | unref()
652 | }
653 |
654 | protected def ref() {
655 | _obRefs = _obRefs match {
656 | case null => this
657 | case ob: Observer => collection.immutable.Set(ob, this)
658 | case obs: Set[_] => obs.asInstanceOf[Set[Observer]] + this
659 | case _ => sys.error("Impossible case")
660 | }
661 | }
662 |
663 | protected def unref() {
664 | _obRefs = _obRefs match {
665 | case null => null
666 | case x if x eq this => null
667 | case obs: Set[_] => obs.asInstanceOf[Set[Observer]] - this
668 | case _ => sys.error("Impossible case")
669 | }
670 | }
671 |
672 | override def reactiveDescriptor = "Observer@" + System.identityHashCode(this)
673 | }
674 |
675 | class Observer1[P, V](val input: Reactive[P, V], op: P => Unit) extends Observer with Dependent1[P, V] {
676 | input.subscribe(this)
677 | def react() {
678 | if (!isDisposed) input.ifEmitting(op)
679 | }
680 | }
681 |
682 | class ObserverOnce1[P, V](input: Reactive[P, V], op: P => Unit) extends Observer1[P, V](input, op) {
683 | override def react() {
684 | super.react()
685 | disconnect()
686 | }
687 | }
688 |
689 | class ObserverDynamic(op: => Unit) extends Observer {
690 | def react() {
691 | depStackPush(this)
692 | try {
693 | op
694 | } finally {
695 | depStackPop()
696 | }
697 | }
698 | }
699 |
700 | /**
701 | * Observes pulses from the given reactive, starting in the current turn.
702 | */
703 | protected def observe[P, V](r: Reactive[P, V])(op: P => Unit): Observer = {
704 | val ob = new Observer1(r, op)
705 | ob.tick()
706 | ob
707 | }
708 |
709 | /**
710 | * Observes exactly one pulse from the given reactive, starting in the current turn.
711 | */
712 | protected def observeOnce[P, V](r: Reactive[P, V])(op: P => Unit): Observer = {
713 | new ObserverOnce1(r, op)
714 | }
715 |
716 | /**
717 | * Runs `op` once, which might access signals through `Signal.apply`. Repeatedly runs `op`
718 | * when some of the signals accessed change. Might run `op` even if no signal has changed.
719 | */
720 | protected def observeDynamic(op: => Unit): Observer = {
721 | val ob = new ObserverDynamic(op)
722 | ob.tick()
723 | ob
724 | }
725 | }
726 |
727 | trait Source[P, V] extends Reactive[P, V] {
728 | protected[this] def owner: Owner
729 | level = owner.level
730 | protected[react] def channel: Channel[P]
731 | }
732 |
733 | trait SimpleSource[P, V] extends SimpleReactive[P, V] with Source[P, V] {
734 | protected def doValidatePulse() {
735 | val x = channel.get(this)
736 | emit(x)
737 | }
738 | }
739 |
740 | trait Channel[P] {
741 | def get(r: Reactive[P, Any]): P
742 | def push(r: Reactive[P, Any], p: P)
743 | }
744 |
745 | sealed trait Owner {
746 | protected[react] def level: Int
747 | def newChannel[P, V](r: Reactive[P, V], p: P): Channel[P]
748 | }
749 |
750 | object DomainOwner extends Owner {
751 | def level = 0
752 | def newChannel[P, V](r: Reactive[P, V], p: P): Channel[P] = new LocalDelayChannel(r, p)
753 | }
754 |
755 | abstract class Router(inputNodes: Reactive[Any, Any]*) extends DependentN with StrictNode with Owner {
756 | protected val inputs = inputNodes.toSeq
757 | connect()
758 | protected implicit def owner: Owner = this
759 |
760 | invalidate()
761 |
762 | def newChannel[P, V](r: Reactive[P, V], p: P): Channel[P] = null
763 |
764 | protected def tickDependents() {}
765 |
766 | protected def doValidatePulse() {
767 | react()
768 | }
769 |
770 | protected def validateValue() {}
771 |
772 | //def get[P](r: Reactive[P, Any]): P = r.getPulse
773 | //def push[P](r: Reactive[P, Any], p: P) = r emit p
774 |
775 | def react()
776 | }
777 |
778 | implicit def owner: Owner = DomainOwner
779 | }
--------------------------------------------------------------------------------
/src/scala/react/SchedulerModule.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 |
3 | import java.util.concurrent.atomic.AtomicBoolean
4 | import java.util.concurrent.{ Executors, ExecutorService }
5 | import javax.swing.SwingUtilities
6 |
7 | trait SchedulerModule { self: Domain =>
8 |
9 |
10 | def schedule(r: Runnable) { engine.schedule(r) }
11 | def schedule(op: => Unit) { engine.schedule(op) }
12 |
13 | /**
14 | * The scheduler is responsible for scheduling propagation turns. Turns are not necessarily run
15 | * immediately, but are scheduled with `ensureTurnIsScheduled()`.
16 | */
17 | abstract class Scheduler {
18 | /**
19 | * Ensure that a turn will be started at the discretion of this scheduler.
20 | * Repeated invocations of this method before the turn is started may not result
21 | * in additional turns being scheduled.
22 | */
23 | def ensureTurnIsScheduled()
24 |
25 | /**
26 | * Starts this scheduler. Called exactly once before any other method on this
27 | * scheduler is invoked.
28 | */
29 | def start() {
30 | ensureTurnIsScheduled()
31 | }
32 | }
33 |
34 | class ManualScheduler extends Scheduler {
35 | def ensureTurnIsScheduled() {}
36 | }
37 |
38 | /**
39 | * A scheduler with a thread-safe API.
40 | */
41 | abstract class ThreadSafeScheduler extends Scheduler {
42 | private val isScheduled = new AtomicBoolean(false)
43 |
44 | private val runnable = new Runnable {
45 | def run {
46 | // TODO: do we really need to CAS twice?
47 | if (isScheduled.compareAndSet(true, false)) engine.runTurn()
48 | }
49 | }
50 |
51 | def ensureTurnIsScheduled() {
52 | if (isScheduled.compareAndSet(false, true)) {
53 | schedule(runnable)
54 | }
55 | }
56 |
57 | /**
58 | * To be implemented by subclasses.
59 | *
60 | * This uses a Runnable and not a Scala closure to avoid wrapping them when interacting
61 | * with existing Java frameworks.
62 | */
63 | protected def schedule(r: Runnable)
64 | }
65 |
66 | /**
67 | * A scheduler running turns on a java.util.concurrent thread-pool.
68 | */
69 | class ThreadPoolScheduler(pool: ExecutorService) extends ThreadSafeScheduler {
70 | def this() = this(Executors.newCachedThreadPool())
71 | protected def schedule(r: Runnable) = pool.execute(r)
72 | }
73 |
74 | /**
75 | * A scheduler running turns on the Swing event dispatcher thread (EDT).
76 | */
77 | class SwingScheduler extends ThreadSafeScheduler {
78 | def schedule(r: Runnable) = SwingUtilities.invokeLater(r)
79 | }
80 | }
--------------------------------------------------------------------------------
/src/scala/react/SignalModule.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 |
3 | import scala.util.continuations._
4 |
5 | trait SignalModule { module: Domain =>
6 | def Strict[A](op: =>A): Signal[A] = new StrictOpSignal(op)
7 | def Lazy[A](op: =>A): Signal[A] = new LazyOpSignal(op)
8 |
9 | object Signal {
10 | /**
11 | * Creates a flow signal that runs through the given `op` once.
12 | */
13 | def flow[A](init: A)(op: SignalFlowOps[A]=>Unit @suspendable): Signal[A] = new FlowSignal(init) {
14 | def body = op(this)
15 | }
16 |
17 | /**
18 | * Creates a flow signal that runs through the given `op` repeatedly until disposed, e.g., by
19 | * calling `halt`.
20 | */
21 | def loop[A](init: A)(op: SignalFlowOps[A]=>Unit @suspendable): Signal[A] = new FlowSignal(init) {
22 | def body = while(!isDisposed) op(this)
23 | }
24 | }
25 |
26 | /**
27 | * A time-varying value.
28 | */
29 | abstract class Signal[+A] extends TotalReactive[A, A] with SimpleReactive[A, A] { outer =>
30 | /* A signal's pulse and value are really the same, except the flag that indicates whether
31 | * the signal is emitting or not.
32 | */
33 | def getValue: A = getPulse
34 |
35 | def toStrict: Signal[A]
36 |
37 | // Fix types, when/if SI-3272 gets fixed
38 | protected def shouldEmit(oldVal: Any, newVal: Any): Boolean = oldVal != newVal
39 |
40 | override protected[this] def emit(p: Any) {
41 | if(shouldEmit(getValue, p)) super.emit(p)
42 | }
43 |
44 | override protected[react] def freePulse() {
45 | // the pulse is the value for signals, so don't do anything here.
46 | }
47 | }
48 |
49 | abstract class FuncSignal[A] extends Signal[A] with OpaqueReactive[A, A] {
50 | protected def react() {
51 | val v = eval
52 | emit(v)
53 | }
54 |
55 | protected def eval: A
56 | }
57 |
58 | abstract class StrictFuncSignal[A] extends FuncSignal[A] with StrictNode {
59 | tick()
60 |
61 | def toStrict: Signal[A] = this
62 | }
63 |
64 | class StrictOpSignal[A](op: => A) extends StrictFuncSignal[A] {
65 | protected def eval = op
66 |
67 | override def reactiveDescriptor = "Strict Signal"
68 | }
69 |
70 | abstract class LazyFuncSignal[A] extends FuncSignal[A] with LazyNode
71 |
72 | class LazyOpSignal[A](op: => A) extends LazyFuncSignal[A] {
73 | protected def eval = op
74 |
75 | def toStrict: Signal[A] = new StrictOpSignal(op)
76 | }
77 |
78 | /**
79 | * A signal with a single input.
80 | */
81 | abstract class Signal1[+A, +B](protected val input: Reactive[A, Any])
82 | extends Signal[B]
83 | with Dependent1[A, Any] {
84 |
85 | input.subscribe(this)
86 |
87 | def doValidatePulse() {
88 | input.ifEmitting(pulsate _)
89 | setPulseValid()
90 | }
91 | protected[this] def pulsate(a: A)
92 | }
93 |
94 | abstract class StrictSignal1[+A, +B](input: Reactive[A, Any])
95 | extends Signal1[A, B](input)
96 | with StrictNode
97 |
98 | protected[this] class HoldSignal[P](init: P)(input: Reactive[P, Any]) extends StrictSignal1[P, P](input) {
99 | pulse = init
100 |
101 | def pulsate(p: P) { emit(p) }
102 |
103 | def toStrict = this
104 | }
105 |
106 | object Var {
107 | /**
108 | * Creates a mutable signal with the given owner.
109 | */
110 | def apply[A](init: A)(implicit owner: Owner): Var[A] = new Var(init, owner)
111 | }
112 |
113 | /**
114 | * An externally mutable signal.
115 | */
116 | class Var[A] protected(init: A, val owner: Owner) extends Signal[A] with SimpleSource[A, A] with StrictNode {
117 | pulse = init
118 |
119 | protected[react] val channel = owner.newChannel(this, init)
120 |
121 | def toStrict = this
122 |
123 | /**
124 | * Sets the value of this signal, either in the next turn, if the owner is the domain or
125 | * in the current turn if the owner is a router.
126 | */
127 | def update(a: A) {
128 | if(owner eq DomainOwner) channel.push(this, a)
129 | else {
130 | emit(a)
131 | if(isEmitting) engine.defer(this)
132 | }
133 | }
134 |
135 | override def reactiveDescriptor = "Var"
136 | }
137 |
138 | /**
139 | * A constant signal.
140 | */
141 | case class Val[A](a: A) extends Signal[A] with MuteNode {
142 | pulse = a
143 |
144 | def toStrict = this
145 | override def reactiveDescriptor = "Val"
146 | }
147 | }
--------------------------------------------------------------------------------
/src/scala/react/TopoQueue.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 | import java.util.Arrays
3 |
4 | abstract class PropQueue[A >: Null: Manifest] {
5 | def clear()
6 | def dequeue(): A
7 | def isEmpty: Boolean
8 | def +=(elem: A)
9 | def reinsert(elem: A)
10 | }
11 |
12 | /**
13 | * A slightly specialized priority queue.
14 | */
15 | abstract class PriorityQueue[A >: Null: Manifest] extends PropQueue[A] {
16 |
17 | private var array = new Array[A](16)
18 | private var size0 = 1 // array(0) unused
19 |
20 | protected def ensureSize(n: Int) {
21 | if (n > array.length) {
22 | var newsize = array.length * 2
23 | while (n > newsize)
24 | newsize = newsize * 2
25 |
26 | val newar = new Array[A](newsize)
27 | compat.Platform.arraycopy(array, 0, newar, 0, size0)
28 | array = newar
29 | }
30 | }
31 |
32 | protected def swap(a: Int, b: Int) {
33 | val h = array(a)
34 | array(a) = array(b)
35 | array(b) = h
36 | }
37 |
38 | protected def fixUp(m: Int): Unit = {
39 | val as = array
40 | var k = m
41 | while (k > 1 && priority(as(k / 2)) > priority(as(k))) {
42 | swap(k, k / 2)
43 | k = k / 2
44 | }
45 | }
46 |
47 | protected def fixDown(m: Int, n: Int): Unit = {
48 | val as = array
49 | var k = m
50 | while (n >= 2 * k) {
51 | var j = 2 * k
52 | if (j < n && priority(as(j)) > priority(as(j + 1)))
53 | j += 1
54 | if (priority(as(k)) <= priority(as(j)))
55 | return
56 | else {
57 | swap(k, j)
58 | k = j
59 | }
60 | }
61 | }
62 |
63 | def +=(elem: A) {
64 | ensureSize(size0 * 3/2 + 1)
65 | array(size0) = elem
66 | fixUp(size0)
67 | size0 += 1
68 | }
69 |
70 | def dequeue(): A = if (size0 > 1) {
71 | size0 -= 1
72 | val res = array(1)
73 | array(1) = array(size0)
74 | array(size0) = null // need to clear, don't want to keep nodes alive longer than necessary
75 | fixDown(1, size0 - 1)
76 | res
77 | } else
78 | throw new NoSuchElementException("no element to remove from heap")
79 |
80 | def priority(a: A): Int
81 |
82 | def isEmpty = size0 == 1
83 | def clear() { size0 = 1 }
84 |
85 | /*private def remove(elem: A) {
86 | var i = 1
87 | while(i < size0) {
88 | if (array(i) == elem) {
89 | size0 -= 1
90 | array(i) = null
91 | if(i == size0) {
92 | return
93 | } else {
94 | array(i) = null
95 | }
96 | compat.Platform.arraycopy(array, i+1, array, i, size0-i)
97 |
98 | }
99 | return
100 | }
101 | }*/
102 |
103 | private def indexOf(elem: A): Int = {
104 | var i = 1
105 | while(i < size0) {
106 | if (array(i) == elem) return i
107 | i += 1
108 | }
109 | -1
110 | }
111 |
112 | def reinsert(elem: A) {
113 | //remove(elem)
114 | val idx = indexOf(elem)
115 | if(idx == -1) this += elem
116 | else {
117 | fixDown(idx, size0-1)
118 | }
119 |
120 | }
121 |
122 | override def toString() =
123 | "PrioQueue" + array.mkString("[", ",", "]")
124 | }
125 |
126 | /**
127 | * A fast, special purpose priority queue. Priorities are topological depth values.
128 | * This queue assumes that between initialization and `clear` calls, elements are inserted with
129 | * monotonically increasing depths as it happens during topologically ordered graph traversal
130 | * (it might and probaly will crash otherwise).
131 | */
132 | abstract class TopoQueue[A >: Null: Manifest] extends PropQueue[A] {
133 | private var curDepth, curIndex, maxDepth = 0
134 | private var sizes: Array[Int] = _
135 | private var levels: Array[Array[A]] = _
136 | private var maxLevelSize = 0
137 | private var maxLevel: Array[A] = _
138 |
139 | compact()
140 |
141 | def compact() {
142 | sizes = Array.fill(initDepth)(0)
143 | levels = Array.fill(initDepth) { new Array[A](initWidth) }
144 | maxLevel = new Array[A](initWidth)
145 | }
146 |
147 | def initDepth = 2
148 | def initWidth = 2
149 |
150 | /**
151 | * The depth value of the given element. To be implemented by subclasses.
152 | */
153 | def depth(a: A): Int
154 |
155 | def clear() {
156 | assert(isEmpty)
157 | curDepth = 0
158 | curIndex = 0
159 | maxDepth = 0
160 | maxLevelSize = 0
161 | // it should be slightly faster to do this here than to adapt sizes during traversal:
162 | Arrays.fill(sizes, 0)
163 | }
164 |
165 | def +=(a: A) {
166 | val d = depth(a)
167 | assert(d >= curDepth)
168 | if (d < Int.MaxValue) {
169 | ensureDepthCapacity(d)
170 | val l = ensureLevelCapacity(d)
171 | l(sizes(d)) = a
172 | sizes(d) += 1
173 | if (maxDepth < d) maxDepth = d
174 | } else {
175 | val l = ensureLevelCapacity(d)
176 | l(maxLevelSize) = a
177 | maxLevelSize += 1
178 | }
179 | }
180 |
181 | /**
182 | * Removes the element with the least depth and makes sure that this queue
183 | * does not hold on to its reference.
184 | */
185 | def dequeue(): A = {
186 | assert(!isEmpty)
187 | while (curDepth < sizes.length && curIndex >= sizes(curDepth)) {
188 | curDepth += 1
189 | curIndex = 0
190 | }
191 | val level = if (curDepth < sizes.length) levels(curDepth) else { curDepth = Int.MaxValue; maxLevel }
192 | val a = level(curIndex)
193 | assert(a != null)
194 | level(curIndex) = null
195 | curIndex += 1
196 | a
197 | }
198 |
199 | /**
200 | * `true` if there are more elements after the current index.
201 | */
202 | def isEmpty: Boolean = if (maxLevelSize == 0)
203 | (curDepth == maxDepth) && (curIndex == sizes(curDepth))
204 | else (curDepth == Int.MaxValue) && (curIndex == maxLevelSize)
205 |
206 | // ensure that level d has capacity to store new elements
207 | private def ensureLevelCapacity(d: Int): Array[A] =
208 | if (d == Int.MaxValue) {
209 | val arr = maxLevel
210 | val oldSize = arr.length
211 | if (oldSize <= maxLevelSize) {
212 | var newsize = oldSize * 2
213 |
214 | val newArr = new Array[A](newsize)
215 | compat.Platform.arraycopy(arr, 0, newArr, 0, oldSize)
216 | maxLevel = newArr
217 | newArr
218 | } else arr
219 | } else {
220 | val arr = levels(d)
221 | val oldSize = arr.length
222 | if (oldSize <= sizes(d)) {
223 | var newsize = oldSize * 2
224 |
225 | val newArr = new Array[A](newsize)
226 | compat.Platform.arraycopy(arr, 0, newArr, 0, oldSize)
227 | levels(d) = newArr
228 | newArr
229 | } else arr
230 | }
231 |
232 | // ensure that we have levels allocated with indices at least up to d
233 | private def ensureDepthCapacity(d: Int) = {
234 | val arr = levels
235 | val oldSize = arr.length
236 | if (d >= oldSize) {
237 | var newSize = oldSize * 2
238 | while (d >= newSize) newSize *= 2
239 |
240 | val newArr = new Array[Array[A]](newSize)
241 | compat.Platform.arraycopy(arr, 0, newArr, 0, oldSize)
242 |
243 | val newSizeArr = new Array[Int](newSize)
244 | compat.Platform.arraycopy(sizes, 0, newSizeArr, 0, oldSize)
245 |
246 | // fill new levels and sizes with fresh arrays
247 | var i = oldSize
248 | while (i < newSize) {
249 | newArr(i) = new Array[A](initWidth)
250 | i += 1
251 | }
252 | levels = newArr
253 | sizes = newSizeArr
254 | }
255 | }
256 |
257 | def reinsert(elem: A) = sys.error("not implemented")
258 | }
--------------------------------------------------------------------------------
/src/scala/react/monitor/IO.scala:
--------------------------------------------------------------------------------
1 | package scala.react.monitor
2 |
3 | import java.io._
4 | import java.nio.channels.FileChannel
5 | import java.nio.{ByteBuffer, MappedByteBuffer}
6 |
7 | object IO {
8 | def readChannel(file: File): FileChannel = new RandomAccessFile(file, "r").getChannel
9 | def writeChannel(file: File): FileChannel = new RandomAccessFile(file, "rw").getChannel
10 |
11 | def mapForReading(ch: FileChannel, offset: Long, size: Int): MappedByteBuffer =
12 | ch.map(FileChannel.MapMode.READ_ONLY, offset, size)
13 |
14 | def mapForWriting(ch: FileChannel, offset: Long, size: Int): MappedByteBuffer =
15 | ch.map(FileChannel.MapMode.READ_WRITE, offset, size)
16 |
17 | def newLogFile: File = File.createTempFile("scalareact", "log")
18 |
19 | def unmap(buf: ByteBuffer) {
20 | buf match {
21 | case buf : sun.nio.ch.DirectBuffer =>
22 | buf.cleaner.clean()
23 | case _ =>
24 | throw new RuntimeException("Cannot unmap given buffer")
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/scala/react/monitor/LogDebug.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 | package monitor
3 |
4 | import java.io._
5 | import java.nio.MappedByteBuffer
6 | import java.nio.BufferOverflowException
7 | import java.nio.channels.FileChannel
8 | import java.util.WeakHashMap
9 | import scala.collection.mutable.ArrayBuffer
10 |
11 | class LogDebug[D <: Domain](dom: D) extends AbstractDebug(dom) {
12 | import domain._
13 | import LogFormat._
14 |
15 | private val log = new PagedWriter(IO.newLogFile)
16 | private val header = IO.mapForWriting(log.channel, 0, 8)
17 |
18 | private val ids = new WeakHashMap[Node, Long]
19 | private var idCounter = 0L // 0 is reserved
20 | private var idSize = 1.toByte // in bytes
21 | private def uniqueId(n: Node): Long = {
22 | var id = ids.get(n)
23 | if (id == 0) {
24 | idCounter += 1
25 | id = idCounter
26 | ids.put(n, id)
27 |
28 | if (id <= Byte.MaxValue) {
29 | writeTag(NewNodeTag)
30 | log putByte id.toByte
31 | } else if (id <= Short.MaxValue) {
32 | if (idSize == Byte.MaxValue + 1) {
33 | idSize = 2
34 | writeTag(NewNodeGenTag)
35 | log putByte idSize
36 | }
37 | writeTag(NewNodeTag)
38 | log putShort id.toShort
39 | } else if (id <= Int.MaxValue) {
40 | if (idSize == Short.MaxValue + 1) {
41 | idSize = 4
42 | writeTag(NewNodeGenTag)
43 | log putByte idSize
44 | }
45 | writeTag(NewNodeTag)
46 | log putInt id.toInt
47 | } else {
48 | if (idSize == Int.MaxValue + 1) {
49 | idSize = 8
50 | writeTag(NewNodeGenTag)
51 | log putByte idSize
52 | }
53 | writeTag(NewNodeTag)
54 | log putLong id
55 | }
56 | }
57 | id
58 | }
59 |
60 | private def writeTag(tag: Byte) {
61 | log putByte tag
62 | }
63 |
64 | private def writeNodeId(id: Long) {
65 | idSize match {
66 | case 1 => log putByte id.toByte
67 | case 2 => log putShort id.toShort
68 | case 4 => log putInt id.toInt
69 | case 8 => log putLong id
70 | }
71 | }
72 |
73 | private var t0 = 0L
74 | private var t = 0L
75 | private var lastSnapshot = 0L
76 |
77 | private def time() = System.nanoTime - t0
78 |
79 | def logStart() {
80 | val startTime = System.currentTimeMillis
81 | t0 = time()
82 | writeHeader(0)
83 | log putLong startTime
84 | }
85 |
86 | def writeHeader(l: Long) {
87 | header.rewind()
88 | header putLong l
89 | }
90 |
91 | def logEnterTurn(id: Long) = {
92 | t = time()
93 | log putLong id
94 | log putLong t
95 | }
96 | def logLeaveTurn(id: Long) = {
97 | t = time()
98 | if (t - lastSnapshot > 1000000000) {
99 | lastSnapshot = t
100 | writeHeader(t)
101 | }
102 | writeTag(TodoTag)
103 | log putInt todoCounter
104 | todoCounter = 0
105 | writeTag(EndTurnTag)
106 | log putLong t
107 | }
108 | def logTock(n: Node) = {
109 | val id = uniqueId(n)
110 | writeTag(TockTag)
111 | writeNodeId(id)
112 | }
113 | def logLevelMismatch(accessor: Node, accessed: Node) = {
114 | writeTag(MismatchTag)
115 | writeNodeId(uniqueId(accessor))
116 | writeNodeId(uniqueId(accessed))
117 | }
118 | private var todoCounter = 0
119 | def logTodo(t: Any) = {
120 | todoCounter += 1
121 | }
122 | }
123 |
124 |
--------------------------------------------------------------------------------
/src/scala/react/monitor/LogFormat.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 | package monitor
3 |
4 | object LogFormat {
5 | val TockTag: Byte = 1
6 | val TodoTag: Byte = 2
7 | val MismatchTag: Byte = 3
8 | val NewNodeTag: Byte = 4
9 | val NewNodeGenTag: Byte = 5
10 | val EndTurnTag: Byte = -1
11 | }
12 |
--------------------------------------------------------------------------------
/src/scala/react/monitor/PagedWriter.scala:
--------------------------------------------------------------------------------
1 | package scala.react.monitor
2 |
3 | import java.io.File
4 | import java.nio.channels.FileChannel
5 | import java.nio.BufferOverflowException
6 |
7 | /**
8 | * A writer with a MappedByteBuffer-like interface that can be used to write to memory mapped files
9 | * for which the size is unknown in advance. The file size is increased by `chunkSize` (plus/minus
10 | * alignment) everytime it runs out of space.
11 | */
12 | class PagedWriter(file: File, chunkSize: Int = 1000000) {
13 | private var offset = 0L
14 | val channel = IO.writeChannel(file)
15 | private var buf = IO.mapForWriting(channel, offset, chunkSize)
16 |
17 | private def remap() {
18 | offset += buf.position
19 | buf = channel.map(FileChannel.MapMode.READ_WRITE, offset, chunkSize)
20 | }
21 |
22 | def putLong(v: Long): Unit = try {
23 | buf putLong v
24 | } catch {
25 | case e: BufferOverflowException =>
26 | remap()
27 | putLong(v)
28 | }
29 |
30 | def putByte(v: Byte): Unit = try {
31 | buf put v
32 | } catch {
33 | case e: BufferOverflowException =>
34 | remap()
35 | putByte(v)
36 | }
37 |
38 | def putShort(v: Short): Unit = try {
39 | buf putShort v
40 | } catch {
41 | case e: BufferOverflowException =>
42 | remap()
43 | putShort(v)
44 | }
45 |
46 | def putInt(v: Int): Unit = try {
47 | buf putInt v
48 | } catch {
49 | case e: BufferOverflowException =>
50 | remap()
51 | putInt(v)
52 | }
53 | }
--------------------------------------------------------------------------------
/test/scala/react/test/AllTests.java:
--------------------------------------------------------------------------------
1 | package scala.react.test;
2 |
3 | import org.junit.runner.RunWith;
4 | import org.junit.runners.Suite;
5 |
6 | @RunWith(Suite.class)
7 | @Suite.SuiteClasses({
8 | TopoQueueTests.class,
9 | ReactiveTests.class,
10 | CombinatorTests.class,
11 | FlowTests.class,
12 | RouterTests.class,
13 | ReactorTests.class
14 | })
15 | public final class AllTests {
16 | }
17 |
--------------------------------------------------------------------------------
/test/scala/react/test/CombinatorTests.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 | package test
3 |
4 | import utils._
5 | import dom._
6 | import org.junit.Test
7 | import org.junit.Assert._
8 |
9 | class CombinatorTests extends TestUtils with Observing {
10 | @Test def eventsMap() = testTurns(6) {
11 | val x = EventSource[Int]
12 | val y = x map (_ * 2)
13 | observe(y)(log(_))
14 |
15 | turn {
16 | log.assert()
17 | x << 1
18 | log.assert()
19 | }
20 | turn {
21 | log.assert(2)
22 | x << 1
23 | log.assert()
24 | }
25 | turn {
26 | log.assert(2)
27 | }
28 | turn {
29 | log.assert()
30 | x << 3
31 | log.assert()
32 | }
33 | turn {
34 | log.assert(6)
35 | }
36 | }
37 |
38 |
39 | @Test def eventsMapLazy() = testTurns(5) {
40 | val x = EventSource[Int]
41 | val y = x map (_ * 2)
42 |
43 | turn {
44 | x << 1
45 | doLater {
46 | assertEquals(Some(2), y.pulseNow)
47 | }
48 | }
49 | turn {
50 | x << 2
51 | }
52 | turn {
53 | x << 3
54 | observe(y) { log(_) }
55 | }
56 | turn {
57 | log.assert(6)
58 | }
59 | }
60 |
61 | @Test def eventsCollect() = testTurns(7) {
62 | val x = EventSource[Int]
63 | val y = x collect {
64 | case x if x % 2 == 0 => x
65 | }
66 | observe(y)(log(_))
67 |
68 | turn {
69 | log.assert()
70 | x << 2
71 | log.assert()
72 | }
73 | turn {
74 | log.assert(2)
75 | x << 1
76 | log.assert()
77 | }
78 | turn {
79 | log.assert()
80 | }
81 | turn {
82 | log.assert()
83 | x << 3
84 | log.assert()
85 | }
86 | turn {
87 | log.assert()
88 | x << 22
89 | log.assert()
90 | }
91 | turn {
92 | log.assert(22)
93 | }
94 | }
95 |
96 | @Test def eventsFilter() = testTurns(7) {
97 | val x = EventSource[Int]
98 | val y = x filter (_ % 2 == 0)
99 | observe(y)(log(_))
100 |
101 | turn {
102 | log.assert()
103 | x << 2
104 | log.assert()
105 | }
106 | turn {
107 | log.assert(2)
108 | x << 1
109 | log.assert()
110 | }
111 | turn {
112 | log.assert()
113 | }
114 | turn {
115 | log.assert()
116 | x << 3
117 | log.assert()
118 | }
119 | turn {
120 | log.assert()
121 | x << 22
122 | log.assert()
123 | }
124 | turn {
125 | log.assert(22)
126 | }
127 | }
128 |
129 | @Test def eventsTake() = testTurns(6) {
130 | val x = EventSource[Int]
131 | val y = x take 2
132 | observe(y)(log(_))
133 |
134 | turn {
135 | log.assert()
136 | x << 2
137 | log.assert()
138 | }
139 | turn {
140 | log.assert(2)
141 | x << 11
142 | log.assert()
143 | }
144 | turn {
145 | log.assert(11)
146 | x << 3
147 | log.assert()
148 | }
149 | turn {
150 | log.assert()
151 | x << 4
152 | log.assert()
153 | }
154 | turn {
155 | log.assert()
156 | }
157 | }
158 |
159 | @Test def eventsDelay2() = testTurns(10) {
160 | val x = EventSource[Int]
161 | val y = x delay 2
162 | observe(y)(log(_))
163 |
164 | turn {
165 | log.assert()
166 | x << 1
167 | log.assert()
168 | }
169 | turn {
170 | log.assert()
171 | x << 2
172 | log.assert()
173 | }
174 | turn {
175 | log.assert()
176 | x << 3
177 | log.assert()
178 | }
179 | turn {
180 | log.assert(1)
181 | x << 4
182 | log.assert()
183 | }
184 | turn {
185 | log.assert(2)
186 | }
187 | turn {
188 | log.assert()
189 | }
190 | turn {
191 | log.assert()
192 | x << 5
193 | log.assert()
194 | }
195 | turn {
196 | log.assert(3)
197 | x << 6
198 | log.assert()
199 | }
200 | turn {
201 | log.assert(4)
202 | }
203 | }
204 |
205 | @Test def eventsScan() = testTurns(6) {
206 | val x = EventSource[Int]
207 | val y = x.scan(1){ _ + _ }
208 | observe(y)(log(_))
209 |
210 | turn {
211 | log.assert()
212 | x << 2
213 | log.assert()
214 | }
215 | turn {
216 | log.assert(3)
217 | x << 5
218 | log.assert()
219 | }
220 | turn {
221 | log.assert(8)
222 | }
223 | turn {
224 | log.assert()
225 | x << 5
226 | log.assert()
227 | }
228 | turn {
229 | log.assert(13)
230 | }
231 | }
232 |
233 | @Test def eventsScan1() = testTurns(6) {
234 | val x = EventSource[Int]
235 | val y = x.scan1 { _ + _ }
236 | observe(y)(log(_))
237 |
238 | turn {
239 | log.assert()
240 | x << 2
241 | log.assert()
242 | }
243 | turn {
244 | log.assert(2)
245 | x << 5
246 | log.assert()
247 | }
248 | turn {
249 | log.assert(7)
250 | }
251 | turn {
252 | log.assert()
253 | x << 5
254 | log.assert()
255 | }
256 | turn {
257 | log.assert(12)
258 | }
259 | }
260 |
261 | @Test def eventsScanMutable() = testTurns(8) {
262 | val x = EventSource[Int]
263 | val y = x.scanMutable("0") { (acc, e) =>
264 | if(e == 2) mute else acc + "," + e
265 | }
266 | observe(y)(log(_))
267 |
268 | turn {
269 | log.assert()
270 | x << 1
271 | log.assert()
272 | }
273 | turn {
274 | log.assert("0,1")
275 | x << 3
276 | log.assert()
277 | }
278 | turn {
279 | log.assert("0,1,3")
280 | }
281 | turn {
282 | log.assert()
283 | x << 2
284 | log.assert()
285 | }
286 | turn {
287 | log.assert()
288 | x << 4
289 | log.assert()
290 | }
291 | turn {
292 | log.assert("0,1,3,4")
293 | }
294 | turn {
295 | log.assert()
296 | }
297 | }
298 |
299 | @Test def signalChanges() = testTurns(6) {
300 | val x = Var(0)
301 | val y = x.changes
302 | observe(y)(log(_))
303 |
304 | turn {
305 | log.assert()
306 | x()= 1
307 | log.assert()
308 | }
309 | turn {
310 | log.assert(1)
311 | x()= 1
312 | log.assert()
313 | }
314 | turn {
315 | log.assert()
316 | }
317 | turn {
318 | log.assert()
319 | x()= 2
320 | log.assert()
321 | }
322 | turn {
323 | log.assert(2)
324 | }
325 | }
326 |
327 | @Test def eventsHold() = testTurns(6) {
328 | val x = EventSource[Int]
329 | val y = x.hold(1)
330 | observe(y)(log(_))
331 |
332 | turn {
333 | log.assert()
334 | x << 1
335 | log.assert()
336 | }
337 | turn {
338 | log.assert()
339 | x << 2
340 | log.assert()
341 | }
342 | turn {
343 | log.assert(2)
344 | }
345 | turn {
346 | log.assert()
347 | x << 3
348 | log.assert()
349 | }
350 | turn {
351 | log.assert(3)
352 | }
353 | }
354 |
355 | @Test def strictLazyReorder() = testTurns(6) {
356 | val switch = Var(false)
357 | val a0 = Var(0) // level 0
358 | val a1 = Strict { a0() + 1 } // level 1
359 | //val s2 = Strict { if(s0() % 2 == 0) s0() + 1 else s1() + 2 } // level 1 then 2
360 | val a2 = Strict { a1() + 1 } // level 2
361 | val a3 = Strict { a2() + 1 } // level 3
362 | val a14 = Strict { if(switch()) a3() + 1 else a0() + 1 } // level 1 or 4
363 | val l25 = Lazy { a14() + 1 } // level 2 or 5
364 | // this should always observe the correct state in l25, which needs to be lifted
365 | // transitively when a14 switches branches
366 | val res = Strict {
367 | val x = a0() + l25() + 1
368 | log("eval" + x) // log to make sure this signal isn't evaluated redundantly
369 | x
370 | }
371 | observe(res)(log(_))
372 |
373 | turn {
374 | log.assert("eval3", 3)
375 | assertEquals(0, a0.level)
376 | assertEquals(1, a1.level)
377 | assertEquals(2, a2.level)
378 | assertEquals(3, a3.level)
379 | assertEquals(1, a14.level)
380 | assertEquals(1, l25.level) // not evaluated, should be something > 0
381 | assertEquals(2, res.level)
382 |
383 | a0()= 1
384 | log.assert()
385 | }
386 | turn {
387 | log.assert("eval5", 5)
388 | assertEquals(0, a0.level)
389 | assertEquals(1, a1.level)
390 | assertEquals(2, a2.level)
391 | assertEquals(3, a3.level)
392 | assertEquals(1, a14.level)
393 | assertEquals(1, l25.level)
394 | assertEquals(2, res.level)
395 |
396 | a0()= 2
397 | switch()= true
398 |
399 | log.assert()
400 | }
401 | turn {
402 | log.assert("eval10", 10)
403 | assertEquals(0, a0.level)
404 | assertEquals(1, a1.level)
405 | assertEquals(2, a2.level)
406 | assertEquals(3, a3.level)
407 | assertEquals(4, a14.level)
408 | assertEquals(5, l25.level)
409 | assertEquals(6, res.level)
410 | }
411 | turn {
412 | log.assert()
413 | a0()= 3
414 | log.assert()
415 | }
416 | turn {
417 | log.assert("eval12", 12)
418 | }
419 | }
420 | }
--------------------------------------------------------------------------------
/test/scala/react/test/FlowTests.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 | package test
3 |
4 | import org.junit.Test
5 | import org.junit.Assert._
6 | import org.scalatest.junit.ShouldMatchersForJUnit._
7 | import test.utils._
8 | import dom._
9 |
10 | class FlowTests extends TestUtils {
11 |
12 | @Test def simpleSignal() = testTurns(2) {
13 | val x = Signal.flow(1) { self =>
14 | log("a")
15 | }
16 | logReactive(x)
17 |
18 | postTurn {
19 | log.assert("a")
20 | assertEquals(1, x.now)
21 | }
22 |
23 | turn {
24 | postTurn {
25 | log.assert()
26 | }
27 | }
28 | }
29 |
30 | @Test def simpleSeqSignal() = testTurns(4) {
31 | val x = Signal.flow(1) { self =>
32 | log("a")
33 | self()= 2
34 | self.pause
35 | log("b")
36 | self()= 3
37 | self.pause
38 | log("d")
39 | }
40 | logReactive(x)
41 |
42 | postTurn {
43 | log.assert("a", 2)
44 | assertEquals(2, x.now)
45 | }
46 |
47 | turn {
48 | postTurn {
49 | log.assert("b", 3)
50 | assertEquals(3, x.now)
51 | }
52 | }
53 | turn {
54 | postTurn {
55 | log.assert("d")
56 | }
57 | }
58 | turn {
59 | postTurn {
60 | log.assert()
61 | }
62 | }
63 | }
64 |
65 | @Test def simpleSeqSignalWithPause() = testTurns(5) {
66 | val x = Signal.flow(0) { self =>
67 | log("a")
68 | self()= 1
69 | self.pause
70 | log("b")
71 | self.pause
72 | log("c")
73 | self()= 2
74 | self.pause
75 | log("d")
76 | }
77 | logReactive(x)
78 |
79 | postTurn {
80 | log.assert("a", 1)
81 | }
82 |
83 | turn {
84 | postTurn {
85 | log.assert("b")
86 | }
87 | }
88 | turn {
89 | postTurn {
90 | log.assert("c", 2)
91 | }
92 | }
93 | turn {
94 | postTurn {
95 | log.assert("d")
96 | }
97 | }
98 | turn {
99 | postTurn {
100 | log.assert()
101 | }
102 | }
103 | }
104 |
105 | @Test def simpleNext() = testTurns(6) {
106 | val es1 = EventSource[Int]
107 | val es2 = EventSource[Int]
108 | val x = Signal.loop(1) { self =>
109 | log("a")
110 | self()= (self await es1)
111 | log("b")
112 | self.pause
113 | log("c")
114 | self() = (self await es1)
115 | log("d")
116 | self.pause
117 | log("e")
118 | self() = (self await es2)
119 | log("f")
120 | self.pause
121 | }
122 | logReactive(x)
123 |
124 | postTurn {
125 | log.assert("a")
126 | assertEquals(1, x.now)
127 | }
128 |
129 | turn {
130 | es1 << 2
131 | postTurn {
132 | log.assert("b", 2)
133 | assertEquals(2, x.now)
134 | }
135 | }
136 | turn {
137 | }
138 | turn {
139 | es1 << 3
140 | postTurn {
141 | log.assert("c", "d", 3)
142 | assertEquals(3, x.now)
143 | }
144 | }
145 | turn {
146 | es1 << 4
147 | postTurn {
148 | log.assert("e")
149 | assertEquals(3, x.now)
150 | }
151 | }
152 | turn {
153 | es2 << 5
154 | postTurn {
155 | log.assert("f", 5)
156 | assertEquals(5, x.now)
157 | }
158 | }
159 | }
160 |
161 | @Test def finishedFlowIsNotMute() = testTurns(2) {
162 | val x = Signal.flow(0) { self =>
163 | log("a")
164 | self()= 1
165 | log("b")
166 | }
167 | logReactive(x)
168 |
169 | postTurn {
170 | log.assert("a", "b", 1)
171 | }
172 |
173 | turn {
174 | postTurn {
175 | log.assert()
176 | }
177 | }
178 | }
179 |
180 | @Test def repeatedEmit() = testTurns(2) {
181 | val x = Signal.flow(0) { self =>
182 | log("a")
183 | self()= 1
184 | log("b")
185 | self()= 2
186 | log("c")
187 | }
188 | logReactive(x)
189 |
190 | postTurn {
191 | log.assert("a", "b", "c", 2)
192 | }
193 |
194 | turn {
195 | postTurn {
196 | log.assert()
197 | }
198 | }
199 | }
200 |
201 | @Test def parJoin() = testTurns(3) {
202 | val x = Signal.flow(1) { self =>
203 | log("1")
204 | self.par {
205 | log("a1")
206 | self.pause
207 | log("a2")
208 | self.join
209 | log("a3")
210 | } {
211 | log("b1")
212 | self.pause
213 | log("b2")
214 | self.pause
215 | log("b3")
216 | }
217 | log("2")
218 | }
219 | logReactive(x)
220 |
221 | postTurn {
222 | log.assert("1", "a1", "b1")
223 | }
224 |
225 | turn {
226 | postTurn {
227 | log.assert("a2", "2")
228 | }
229 | }
230 | turn {}
231 | }
232 |
233 | @Test def nestedParsJoin() = testTurns(3) {
234 | val x = Signal.flow(1) { self =>
235 | log("1")
236 | self.par {
237 | log("a1")
238 | self.par {
239 | log("c1")
240 | self.pause
241 | log("c2")
242 | self.join
243 | log("c3") // should never be reached
244 | } {
245 | log("d1")
246 | self.pause
247 | log("d2") // should never be reached
248 | }
249 | log("a2")
250 | } {
251 | log("b1")
252 | self.pause
253 | log("b2")
254 | }
255 | log("2")
256 | }
257 | logReactive(x)
258 |
259 | postTurn {
260 | log.assert("1", "a1", "c1", "d1", "b1")
261 | }
262 |
263 | turn {
264 | postTurn {
265 | log.assert("c2", "a2", "b2", "2")
266 | }
267 | }
268 | turn {}
269 | }
270 |
271 | @Test def loopUntilSignal() = testTurns(6) {
272 | val es1 = EventSource[Int]
273 | val es2 = EventSource[Int]
274 | val x = Signal.flow(1) { self =>
275 | log("a")
276 | var i = 2
277 | val e1 = self.loopUntil(es1) {
278 | log("b")
279 | self()= i
280 | self.pause
281 | i += 1
282 | log("c")
283 | }
284 | log("d")
285 | self()= e1
286 | self.pause
287 | log("e")
288 | val e2 = self.loopUntil(es2) {
289 | log("f")
290 | self()= i
291 | self.pause
292 | i += 1
293 | log("g")
294 | }
295 | log("h")
296 | }.name = "subject"
297 | logReactive(x)
298 |
299 | postTurn {
300 | log.assert("a", "b", 2)
301 | }
302 |
303 | turn {
304 | postTurn {
305 | log.assert("c", "b", 3)
306 | }
307 | }
308 | turn {
309 | es1 << 1
310 | postTurn {
311 | log.assert("d", 1)
312 | }
313 | }
314 | turn {
315 | postTurn {
316 | log.assert("e", "f", 3) // last i+=1 should have been skipped
317 | }
318 | }
319 | turn {
320 | es1 << 2
321 | postTurn {
322 | log.assert("g", "f", 4)
323 | }
324 | }
325 | turn {
326 | es2 << 2
327 | postTurn {
328 | log.assert("h")
329 | }
330 | }
331 | }
332 |
333 | @Test def loopEndUntilSignal() = testTurns(6) {
334 | val es1 = EventSource[Int]
335 | val es2 = EventSource[Int]
336 | val x = Signal.flow(1) { self =>
337 | log("a")
338 | var i = 2
339 | val e1 = self.loopEndUntil(es1) {
340 | log("b")
341 | self()= i
342 | self.pause
343 | i += 1
344 | log("c")
345 | }
346 | log("d")
347 | self()= e1
348 | self.pause
349 | log("e")
350 | val e2 = self.loopEndUntil(es2) {
351 | log("f")
352 | self()= i
353 | self.pause
354 | i += 1
355 | log("g")
356 | }
357 | log("h")
358 | }.name = "subject"
359 | logReactive(x)
360 |
361 | postTurn {
362 | log.assert("a", "b", 2)
363 | }
364 |
365 | turn {
366 | postTurn {
367 | log.assert("c", "b", 3)
368 | }
369 | }
370 | turn {
371 | es1 << 1
372 | postTurn {
373 | log.assert("c", "d", 1)
374 | }
375 | }
376 | turn {
377 | postTurn {
378 | log.assert("e", "f", 4) // last i+=1 should have been executed
379 | }
380 | }
381 | turn {
382 | es1 << 2
383 | postTurn {
384 | log.assert("g", "f", 5)
385 | }
386 | }
387 | turn {
388 | es2 << 2
389 | postTurn {
390 | log.assert("g", "h")
391 | }
392 | }
393 | }
394 |
395 | }
--------------------------------------------------------------------------------
/test/scala/react/test/ReactiveTests.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 | package test
3 |
4 | import org.junit.Test
5 | import org.junit.Assert._
6 | import org.scalatest.junit.ShouldMatchersForJUnit._
7 | import test.utils._
8 | import dom._
9 |
10 | class ReactiveTests extends TestUtils {
11 |
12 | @Test def testTurnCount() = testTurns(1) {
13 | }
14 |
15 | @Test def testTurnOrder() = testTurns(4) {
16 | log(1)
17 |
18 | turn {
19 | log.assert(1)
20 | log.assert()
21 | log(2)
22 | }
23 | turn {
24 | log.assert(2)
25 | log.assert()
26 | log(3)
27 | }
28 | turn {
29 | log.assert(3)
30 | log.assert()
31 | }
32 | }
33 |
34 | @Test(expected=classOf[AssertionError])
35 | def testTurnOrderFailCount() = testTurns(3) {
36 | log(1)
37 |
38 | turn {
39 | log.assert(1)
40 | log.assert()
41 | log(2)
42 | }
43 | turn {
44 | log.assert(2)
45 | log.assert()
46 | log(3)
47 | }
48 | turn {
49 | log.assert(3)
50 | log.assert()
51 | }
52 | }
53 |
54 | @Test(expected=classOf[AssertionError])
55 | def testTurnOrderFailLog() = testTurns(2) {
56 | turn {
57 | log.assert(1)
58 | }
59 | }
60 |
61 | @Test def testDoLater() = testTurns(5) {
62 | doLater { log(1) }
63 | log.assert()
64 | postTurn {
65 | log.assert(1)
66 | }
67 |
68 | turn {
69 | log.assert()
70 | }
71 | turn {
72 | log.assert()
73 | doLater{ log(2) }
74 | postTurn {
75 | log.assert(2)
76 | }
77 | }
78 | turn {
79 | log.assert()
80 | doLater{ log(10) }
81 | doLater{ log(11) }
82 | postTurn {
83 | log.assert(10, 11)
84 | }
85 | }
86 | turn {
87 | log.assert()
88 | }
89 | }
90 |
91 | @Test def simpleInvalidate() = testTurns(1) {
92 | new Observer {
93 | assertTrue(invalidate())
94 | assertFalse(invalidate())
95 |
96 | def react() {}
97 | }
98 | }
99 |
100 | @Test def simpleObserveVar() = testTurns(7) {
101 | val x = Var(1)
102 | logReactive(x)
103 |
104 | doLater { assertEquals(1, x.now) }
105 |
106 | turn {
107 | log.assert()
108 | doLater {
109 | x() = 2
110 | }
111 | }
112 | turn {
113 | log.assert()
114 | postTurn { log.assert(2) }
115 | }
116 | turn {
117 | log.assert()
118 | }
119 | turn {
120 | log.assert()
121 | x() = 3
122 | doLater { log.assert() }
123 | }
124 | turn {
125 | log.assert(3)
126 | x() = 4
127 | x() = 5
128 | doLater { log.assert() }
129 | }
130 | turn {
131 | log.assert(5)
132 | }
133 | }
134 |
135 | @Test def simpleObserveVarImmediate() = testTurns(2) {
136 | val x = Var(1)
137 | x() = 2
138 | observe(x)(log(_))
139 | log.assert()
140 | doLater { log.assert(2) }
141 |
142 | turn {
143 | log.assert()
144 | }
145 | }
146 |
147 | @Test def simpleObserveEventSource() = testTurns(6) {
148 | val x = EventSource[Int]
149 | observe(x)(log(_))
150 |
151 | turn {
152 | log.assert()
153 | x << 1
154 | log.assert()
155 | }
156 | turn {
157 | log.assert(1)
158 | x << 1
159 | log.assert()
160 | }
161 | turn {
162 | log.assert(1)
163 | }
164 | turn {
165 | log.assert()
166 | x << 3
167 | log.assert()
168 | }
169 | turn {
170 | log.assert(3)
171 | }
172 | }
173 |
174 | @Test def eventsOnce() = testTurns(4) {
175 | val x = Events.once(1)
176 | observe(x)(log(_))
177 | log.assert()
178 |
179 | turn {
180 | log.assert(1)
181 | }
182 | turn {
183 | log.assert()
184 | }
185 | turn {
186 | log.assert()
187 | }
188 | }
189 |
190 | @Test def eventsNever() = testTurns(3) {
191 | val x = Events.never[Int]
192 | observe(x)(log(_))
193 | log.assert()
194 |
195 | turn {
196 | log.assert()
197 | }
198 | turn {
199 | log.assert()
200 | }
201 | }
202 |
203 | @Test def simpleObserveOnce() = testTurns(9) {
204 | val x = Var(1)
205 | observeOnce(x)(log(_))
206 |
207 | turn {
208 | log.assert()
209 | x() = 2
210 | log.assert()
211 | }
212 | turn {
213 | log.assert(2)
214 | x() = 2
215 | log.assert()
216 | }
217 | turn {
218 |
219 | }
220 | turn {
221 | log.assert()
222 | x() = 3
223 | }
224 | turn {
225 | log.assert()
226 | }
227 |
228 | turn {
229 | log.assert()
230 | x() = 4
231 | observeOnce(x)(log(_))
232 | postTurn { log.assert(4) }
233 | }
234 | turn {
235 | log.assert()
236 | x() = 5
237 | }
238 | turn {
239 | log.assert()
240 | }
241 | }
242 |
243 | @Test def strictSignalInit() = testTurns(3) {
244 | val x = Var(1)
245 | val y = Strict {
246 | log("y")
247 | log(x.now)
248 | log(x.getPulse)
249 | log(x())
250 | }
251 |
252 | turn {
253 | log.assert("y", 1, 1, 1)
254 | x() = 2
255 | log.assert()
256 | }
257 | turn {
258 | log.assert("y", 2, 2, 2)
259 | }
260 | }
261 |
262 | @Test def strictSignalHigherOrder() = testTurns(3) {
263 | // Testing that:
264 | // - we don't have assertions in the engine that break when nodes are created on the fly and/or
265 | // below the current engine level
266 | // - the dependent stack stays in a reasonable state in the presence of HO nodes and level
267 | // mismatches
268 | val x = Var(1) // level 0
269 | val y = Strict { x() + 1 } // level 1
270 | val z: Signal[Signal[Int]] = Strict {
271 | y() // lift this sig to level 2
272 | Strict {
273 | x()+1 // let this one be below the outer signal => below current engine level on first creation
274 | }.name = "inner"
275 | }.name = "outer"
276 |
277 | turn {
278 | x() = 2
279 | println(x)
280 | }
281 | turn {
282 | x() = 3
283 | }
284 | }
285 |
286 | @Test def strictSignalAdaptsLevel() = testTurns(3) {
287 | val x = Var(1)
288 | val y = Strict { x() + 1 }
289 | val z = Strict {
290 | log("z")
291 | log(y.now)
292 | log(y.getPulse)
293 | log(y())
294 | }
295 |
296 | turn {
297 | log.assert("z", "z", 2, 2, 2) // redundant "z", because of hoisting
298 | x() = 2
299 | log.assert()
300 | }
301 | turn {
302 | log.assert("z", 3, 3, 3)
303 | }
304 | }
305 |
306 | @Test def strictSignalAdaptsLevel2() = testTurns(5) {
307 | val x = Var(1)
308 | val y = Strict { x() + 1 }
309 | val which = Var(true)
310 | val z = Strict {
311 | if(which()) { log("then"); x() }
312 | else { log("else"); y() }
313 | }
314 | logReactive(z)
315 |
316 | postTurn {
317 | log.assert("then", 1)
318 | }
319 |
320 | turn {
321 | log.assert()
322 | x() = 2
323 | log.assert()
324 | postTurn {
325 | log.assert("then", 2)
326 | }
327 | }
328 | turn {
329 | log.assert()
330 | which() = false
331 | log.assert()
332 | postTurn {
333 | log.assert("else", "else", 3)
334 | }
335 | }
336 |
337 | turn {
338 | log.assert()
339 | x() = 3
340 | log.assert()
341 | postTurn {
342 | log.assert("else", 4)
343 | }
344 | }
345 |
346 | turn {
347 | log.assert()
348 | which() = true
349 | log.assert()
350 | postTurn {
351 | log.assert("then", 3)
352 | }
353 | }
354 | }
355 |
356 |
357 |
358 | @Test def strictSignalRedundantEval() = testTurns(3) {
359 | val x = Var(1)
360 | val y = Strict { x() }
361 | val z = Strict { x() }
362 | val sum = Strict{ y() + z() }
363 | observe(sum){ log(_) }
364 |
365 | turn {
366 | log.clear()
367 | x() = 2
368 | log.assert()
369 | }
370 | turn {
371 | log.assert(4)
372 | }
373 | }
374 |
375 | @Test def lazySignal() = testTurns(10) {
376 | val x = Var(1)
377 | val y = Lazy {
378 | log("y")
379 | val v = x.now
380 | log(v) // should adapt level
381 | log(x.getPulse)
382 | log(x())
383 | v
384 | }
385 | var doEval = Var(false)
386 | val z = Strict {
387 | if (doEval()) log("z " + y()) else log("z mute")
388 | }
389 | log.assert()
390 |
391 | turn {
392 | log.assert("z mute")
393 | x() = 2
394 | postTurn { log.assert() }
395 | }
396 | turn {
397 | // lazy signal is not connected
398 | log.assert()
399 | doLater {
400 | log("ob " + y.now)
401 | }
402 | }
403 | turn {
404 | log.assert("y", 2, 2, 2, "ob 2")
405 | // this should not cause y to validate, but invalidate it
406 | x() = 3
407 | }
408 | turn {
409 | // y should not have been evaluated
410 | log.assert()
411 | // should cause z to eval y
412 | doEval() = true
413 | }
414 | turn {
415 | log.assert("y", 3, 3, 3, "z 3")
416 | x() = 4
417 | log.assert()
418 | }
419 | turn {
420 | log.assert("y", 4, 4, 4, "z 4")
421 | doEval() = false
422 | }
423 | turn {
424 | log.assert("z mute")
425 | x() = 5
426 | log.assert()
427 | }
428 | turn {
429 | log.clear() // z might have been notified, because of a stale dependency
430 | x() = 6
431 | log.assert()
432 | }
433 | turn {
434 | // stale dependency should have been cleared
435 | log.assert()
436 | }
437 | }
438 |
439 | }
--------------------------------------------------------------------------------
/test/scala/react/test/ReactorTests.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 | package test
3 |
4 | import utils._
5 | import dom._
6 | import org.junit.Test
7 | import org.junit.Assert._
8 |
9 | class ReactorTests extends TestUtils with Observing {
10 | @Test def simple() = testTurns(3) {
11 | val x = EventSource[Int]
12 | val r = Reactor.flow { self =>
13 | log(1)
14 | self await x
15 | log(2)
16 | }
17 |
18 | turn {
19 | log.assert(1)
20 | x << 1
21 | log.assert()
22 | }
23 | turn {
24 | log.assert(2)
25 | x << 2
26 | log.assert()
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/test/scala/react/test/RouterTests.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 | package test
3 |
4 | import utils._
5 | import dom._
6 | import org.junit.Test
7 | import org.junit.Assert._
8 |
9 | class RouterTests extends TestUtils with Observing {
10 | @Test def holdRouter() = testTurns(6) {
11 | val x = EventSource[Int]
12 | val r = new Router(x) {
13 | private val v = Var(1)
14 | def out: Signal[Int] = v
15 | def react() {
16 | x.ifEmitting { e =>
17 | v() = e
18 | }
19 | }
20 | }
21 | observe(r.out)(log(_))
22 |
23 | turn {
24 | log.assert()
25 | x << 1
26 | log.assert()
27 | }
28 | turn {
29 | log.assert()
30 | x << 2
31 | log.assert()
32 | }
33 | turn {
34 | log.assert(2)
35 | }
36 | turn {
37 | log.assert()
38 | x << 3
39 | log.assert()
40 | }
41 | turn {
42 | log.assert(3)
43 | }
44 | }
45 |
46 | /*@Test def bidirectionalRouter() = testTurns(6) {
47 | val x = Var(1)
48 | val y = Var(2)
49 | val r = new Router(x) {
50 | private val v1 = Var(1)
51 | private val v2 = Var(2)
52 | def out1: Signal[Int] = v1
53 | def out2: Signal[Int] = v2
54 |
55 | def react() {
56 | x.ifEmitting { e =>
57 | v1() = e * v2.now
58 | }
59 | y.ifEmitting { e =>
60 | v2() = e * v1.now
61 | }
62 | }
63 | }
64 | observe(r.out1)(log(_))
65 | observe(r.out2)(log(_))
66 |
67 | turn {
68 | log.assert()
69 | x()= 1
70 | log.assert()
71 | }
72 | turn {
73 | log.assert()
74 | x()= 2
75 | log.assert()
76 | }
77 | turn {
78 | log.assert(2)
79 | }
80 | turn {
81 | log.assert()
82 | x()= 3
83 | log.assert()
84 | }
85 | turn {
86 | log.assert(3)
87 | }
88 | }*/
89 | }
--------------------------------------------------------------------------------
/test/scala/react/test/TopoQueueTests.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 | package test
3 |
4 | import org.junit.Test
5 | import org.junit.Assert._
6 |
7 | class TopoQueueTests {
8 | case class Node(i: Int)
9 |
10 | class Q extends TopoQueue[Node] {
11 | def depth(n: Node) = n.i
12 | }
13 |
14 | @Test def simple() {
15 | val q = new Q
16 | assertTrue(q.isEmpty)
17 | q.clear()
18 | assertTrue(q.isEmpty)
19 | q += Node(0)
20 | assertFalse(q.isEmpty)
21 | q += Node(0)
22 | q += Node(1)
23 | assertFalse(q.isEmpty)
24 | q += Node(5)
25 |
26 | assertEquals(Node(0), q.dequeue)
27 | assertFalse(q.isEmpty)
28 | assertEquals(Node(0), q.dequeue)
29 | assertEquals(Node(1), q.dequeue)
30 | assertFalse(q.isEmpty)
31 | assertEquals(Node(5), q.dequeue)
32 | assertTrue(q.isEmpty)
33 | q.clear()
34 | assertTrue(q.isEmpty)
35 | }
36 |
37 | @Test def withMaxLevel() {
38 | val q = new Q
39 | assertTrue(q.isEmpty)
40 | q += Node(Int.MaxValue)
41 | assertFalse(q.isEmpty)
42 | assertEquals(Node(Int.MaxValue), q.dequeue)
43 | assertTrue(q.isEmpty)
44 | q.clear()
45 | assertTrue(q.isEmpty)
46 | }
47 |
48 | }
--------------------------------------------------------------------------------
/test/scala/react/test/utils/JTestUtils.java:
--------------------------------------------------------------------------------
1 | package scala.react.test.utils;
2 |
3 | import org.junit.Rule;
4 | import org.junit.rules.TestName;
5 |
6 | public class JTestUtils {
7 | // This needs to be a public member, so needs to be in a Java class.
8 | @Rule public TestName testName = new TestName();
9 | }
--------------------------------------------------------------------------------
/test/scala/react/test/utils/TestDomain.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 | package test.utils
3 |
4 | import java.util.ArrayDeque
5 | import org.junit.Assert._
6 |
7 | object dom extends TestDomain
8 |
9 | class TestDomain extends Domain {
10 | var engine = new TestEngine
11 | val scheduler = new ManualScheduler
12 |
13 | private val postTurnTodos = new ArrayDeque[() => Unit]
14 | def schedulePostTurn(op: => Unit) = postTurnTodos add (() => op)
15 |
16 | private def reset() {
17 | turnQueue.clear()
18 | postTurnTodos.clear()
19 | engine = new TestEngine
20 | }
21 |
22 | // add some test hooks to the standard engine
23 | class TestEngine extends Engine {
24 | override def runTurn() = super.runTurn()
25 | override def propagate() = {
26 | super.propagate()
27 | level = Int.MaxValue
28 | while (!postTurnTodos.isEmpty)
29 | postTurnTodos.poll().apply()
30 | }
31 | override def uncaughtException(e: Throwable) = {
32 | e.printStackTrace()
33 | postTurnTodos.clear()
34 | throw e
35 | }
36 | }
37 |
38 | def testTurns(turnsExpected: Int)(op: => Unit) {
39 | reset()
40 | dom.start()
41 | turn { op }
42 | while (!turnQueue.isEmpty) {
43 | val t = turnQueue.poll()
44 | dom schedule { t() }
45 | dom.engine.runTurn()
46 | }
47 | assertEquals(turnsExpected, engine.currentTurn - 1)
48 | }
49 |
50 | private val turnQueue = new ArrayDeque[() => Unit]
51 |
52 | // First run the given op, and then a turn.
53 | def turn(op: => Unit) {
54 | turnQueue add (() => op)
55 | }
56 |
57 | // run at the very end of current turn
58 | def postTurn(op: => Unit) = dom.schedulePostTurn(op)
59 |
60 | override def toString = "TestDomain"
61 | }
--------------------------------------------------------------------------------
/test/scala/react/test/utils/TestUtils.scala:
--------------------------------------------------------------------------------
1 | package scala.react
2 | package test.utils
3 |
4 | import org.junit.{ After, Before, Rule }
5 | import org.junit.Assert._
6 | import java.util.ArrayDeque
7 | import org.scalatest.junit.ShouldMatchersForJUnit._
8 | import scala.collection.mutable._
9 | import dom._
10 | import org.junit.BeforeClass
11 | import org.junit.rules.TestName
12 |
13 | abstract class TestUtils extends JTestUtils with Observing {
14 | val log = new Log
15 |
16 | class Log {
17 | private val buf = new ArrayBuffer[Any] with SynchronizedBuffer[Any]
18 | def apply(a: Any) {
19 | buf += a
20 | }
21 | def assert(as: Any*) { buf.toArray should equal(as.toArray); clear() }
22 | def clear() = buf.clear
23 | }
24 |
25 |
26 | @Before def setup() {
27 | log.clear()
28 | println("======= Running " + testName.getMethodName)
29 | }
30 |
31 | @After def tearDown() {
32 | log.assert()
33 | println("======= done with " + testName.getMethodName)
34 | }
35 |
36 | def logReactive[P](r: Reactive[P, Any]): Observer = observe(r){ log(_) }.name = "(Logger of " + r + ")"
37 | }
--------------------------------------------------------------------------------