-
Notifications
You must be signed in to change notification settings - Fork 108
/
SafeBuffer.scala
163 lines (131 loc) · 3.73 KB
/
SafeBuffer.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package com.thoughtworks.binding
import com.thoughtworks.enableMembersIf
import scala.annotation.tailrec
import scala.collection.mutable
private[binding] object SafeBuffer {
@enableMembersIf(c => !c.compilerSettings.exists(_.matches("""^-Xplugin:.*scalajs-compiler_[0-9\.\-]*\.jar$""")))
private[SafeBuffer] object Jvm {
def newBuffer[A] = collection.mutable.ArrayBuffer.empty[A]
}
@enableMembersIf(c => c.compilerSettings.exists(_.matches("""^-Xplugin:.*scalajs-compiler_[0-9\.\-]*\.jar$""")))
private[SafeBuffer] object Js {
@inline
def newBuffer[A] = new scalajs.js.Array[A]
@inline
implicit final class ReduceToSizeOps[A] @inline()(array: scalajs.js.Array[A]) {
@inline def reduceToSize(newSize: Int) = array.length = newSize
}
}
private[SafeBuffer] sealed trait State
case object Idle extends State
case object CleanForeach extends State
case object DirtyForeach extends State
final val Hole = new AnyRef
}
/** Similar to [[scala.collection.mutable.ArrayBuffer]],
* except that this [[SafeBuffer]] allows adding or removing elements via [[+=]] and [[-=]]
* inside a [[foreach]] block.
*
* @note A [[java.lang.IllegalStateException]] will be thrown when invoking methods other than [[+=]] and [[-=]] in a [[foreach]] block.
*/
final class SafeBuffer[A] extends mutable.Buffer[A] {
import SafeBuffer._
import Js._
import Jvm._
private val data = newBuffer[Any]
@volatile
private var state: State = Idle
@inline
override def nonEmpty = !isEmpty
@inline
override def isEmpty = data.forall(_ == Hole)
override def foreach[U](f: A => U): Unit = {
state match {
case Idle =>
state = CleanForeach
data.withFilter(_ != Hole).foreach(f.asInstanceOf[Any => U])
state match {
case DirtyForeach => {
@tailrec
def compact(i: Int, j: Int): Unit = {
if (i < data.length) {
val x = data(i)
if (x == Hole) {
compact(i + 1, j)
} else {
data(j) = x
compact(i + 1, j + 1)
}
} else {
data.reduceToSize(j)
}
}
compact(0, 0)
state = Idle
}
case CleanForeach =>
state = Idle
case Idle =>
throw new IllegalStateException("Expect CleanForeach or DirtyForeach")
}
case CleanForeach | DirtyForeach =>
data.withFilter(_ != Hole).foreach(f.asInstanceOf[Any => U])
}
}
@inline
override def +=(x: A): this.type = {
data += x
this
}
@inline
override def -=(x: A): this.type = {
state match {
case Idle =>
data -= x
case CleanForeach =>
data(data.indexOf(x)) = Hole
state = DirtyForeach
case DirtyForeach =>
data(data.indexOf(x)) = Hole
}
this
}
private def checkIdle() = {
if (Idle != state)
throw new IllegalStateException(
"Not allowed to invoke methods other than `+=` and `-=` when `foreach` is running.")
}
def +=:(elem: A): this.type = {
checkIdle()
data.+=:(elem)
this
}
def apply(n: Int): A = {
checkIdle()
data(n).asInstanceOf[A]
}
def clear(): Unit = {
checkIdle()
data.clear()
}
def insertAll(n: Int, elems: Traversable[A]): Unit = {
checkIdle()
data.insertAll(n, elems)
}
def length: Int = {
checkIdle()
data.length
}
def remove(n: Int): A = {
checkIdle()
data.remove(n).asInstanceOf[A]
}
def update(n: Int, newelem: A): Unit = {
checkIdle()
data.update(n, newelem)
}
def iterator: Iterator[A] = {
checkIdle()
data.iterator.asInstanceOf[Iterator[A]]
}
}