1212
1313package java .util .concurrent
1414
15+ import scala .language .higherKinds
16+
1517import java .lang .Cloneable
1618import java .lang .Utils ._
1719import java .lang .{reflect => jlr }
@@ -23,32 +25,46 @@ import scala.annotation.tailrec
2325import ScalaOps ._
2426
2527import scala .scalajs ._
28+ import scala .scalajs .LinkingInfo ._
2629
27- class CopyOnWriteArrayList [E <: AnyRef ] private (private var inner : js. Array [ E ] )
30+ class CopyOnWriteArrayList [E <: AnyRef ] private (initialCapacity : Int )
2831 extends List [E ] with RandomAccess with Cloneable with Serializable {
2932 self =>
3033
34+ /* This class has two different implementations for the
35+ * internal data storage, depending on whether we are on Wasm or JS.
36+ * We use `js.Array` on JS, and `scala.Array` on Wasm.
37+ * The initialCapacity parameter is effective only in Wasm,
38+ * since js.Array doesn't support explicit pre-allocation.
39+ *
40+ * On Wasm, we store the length at the index 0 of the array.
41+ */
42+
43+ import CopyOnWriteArrayList ._
44+
45+ private var inner : innerImpl.Repr [E ] = innerImpl.make(initialCapacity)
46+
3147 // requiresCopyOnWrite is false if and only if no other object
3248 // (like the iterator) may have a reference to inner
3349 private var requiresCopyOnWrite = false
3450
3551 def this () = {
36- this (new js. Array [ E ] )
52+ this (16 )
3753 }
3854
3955 def this (c : Collection [_ <: E ]) = {
40- this ()
56+ this (c.size() )
4157 addAll(c)
4258 }
4359
4460 def this (toCopyIn : Array [E ]) = {
45- this ()
61+ this (toCopyIn.length )
4662 for (i <- 0 until toCopyIn.length)
4763 add(toCopyIn(i))
4864 }
4965
5066 def size (): Int =
51- inner .length
67+ innerImpl .length(inner)
5268
5369 def isEmpty (): Boolean =
5470 size() == 0
@@ -175,7 +191,7 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E])
175191 }
176192
177193 def clear (): Unit = {
178- inner = new js. Array [ E ]
194+ inner = innerImpl.make( 16 )
179195 requiresCopyOnWrite = false
180196 }
181197
@@ -274,43 +290,49 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E])
274290 }
275291
276292 protected def innerGet (index : Int ): E =
277- inner( index)
293+ innerImpl.get(inner, index)
278294
279295 protected def innerSet (index : Int , elem : E ): Unit =
280- inner( index) = elem
296+ innerImpl.set(inner, index, elem)
281297
282- protected def innerPush (elem : E ): Unit =
283- inner.push(elem)
298+ protected def innerPush (elem : E ): Unit = {
299+ val newInner = innerImpl.push(inner, elem)
300+ if (LinkingInfo .isWebAssembly) // opt: for JS we know it's always the same
301+ inner = newInner
302+ }
284303
285- protected def innerInsert (index : Int , elem : E ): Unit =
286- inner.splice(index, 0 , elem)
304+ protected def innerInsert (index : Int , elem : E ): Unit = {
305+ val newInner = innerImpl.add(inner, index, elem)
306+ if (LinkingInfo .isWebAssembly) // opt: for JS we know it's always the same
307+ inner = newInner
308+ }
287309
288310 protected def innerInsertMany (index : Int , items : Collection [_ <: E ]): Unit = {
289- val itemsArray = js. Array [ E ]( )
290- items.scalaOps.foreach(itemsArray.push(_))
291- inner.splice(index, 0 , itemsArray.toSeq : _* )
311+ val newInner = innerImpl.add(inner, index, items )
312+ if ( LinkingInfo .isWebAssembly) // opt: for JS we know it's always the same
313+ inner = newInner
292314 }
293315
294316 protected def innerRemove (index : Int ): E =
295- arrayRemoveAndGet (inner, index)
317+ innerImpl.remove (inner, index)
296318
297319 protected def innerRemoveMany (index : Int , count : Int ): Unit =
298- inner.splice( index, count)
320+ innerImpl.remove(inner, index, count)
299321
300322 protected def copyIfNeeded (): Unit = {
301323 if (requiresCopyOnWrite) {
302- inner = inner.jsSlice( )
324+ inner = innerImpl.clone(inner )
303325 requiresCopyOnWrite = false
304326 }
305327 }
306328
307- protected def innerSnapshot (): js. Array [E ] = {
329+ protected def innerSnapshot (): innerImpl. Repr [E ] = {
308330 requiresCopyOnWrite = true
309331 inner
310332 }
311333
312334 private class CopyOnWriteArrayListView (fromIndex : Int , private var toIndex : Int )
313- extends CopyOnWriteArrayList [E ](null : js. Array [ E ] ) {
335+ extends CopyOnWriteArrayList [E ](initialCapacity ) {
314336 viewSelf =>
315337
316338 override def size (): Int =
@@ -381,7 +403,7 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E])
381403 override protected def copyIfNeeded (): Unit =
382404 self.copyIfNeeded()
383405
384- override protected def innerSnapshot (): js. Array [E ] =
406+ override protected def innerSnapshot (): innerImpl. Repr [E ] =
385407 self.innerSnapshot()
386408
387409 protected def changeSize (delta : Int ): Unit =
@@ -399,7 +421,8 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E])
399421 }
400422}
401423
402- private class CopyOnWriteArrayListIterator [E ](arraySnapshot : js.Array [E ], i : Int , start : Int , end : Int )
424+ private class CopyOnWriteArrayListIterator [E ](
425+ arraySnapshot : CopyOnWriteArrayList .innerImpl.Repr [E ], i : Int , start : Int , end : Int )
403426 extends AbstractRandomAccessListIterator [E ](i, start, end) {
404427 override def remove (): Unit =
405428 throw new UnsupportedOperationException
@@ -411,7 +434,7 @@ private class CopyOnWriteArrayListIterator[E](arraySnapshot: js.Array[E], i: Int
411434 throw new UnsupportedOperationException
412435
413436 protected def get (index : Int ): E =
414- arraySnapshot( index)
437+ CopyOnWriteArrayList .innerImpl.get(arraySnapshot, index)
415438
416439 protected def remove (index : Int ): Unit =
417440 throw new UnsupportedOperationException
@@ -422,3 +445,162 @@ private class CopyOnWriteArrayListIterator[E](arraySnapshot: js.Array[E], i: Int
422445 protected def add (index : Int , e : E ): Unit =
423446 throw new UnsupportedOperationException
424447}
448+
449+ object CopyOnWriteArrayList {
450+
451+ /* Get the best implementation of inner array for the given platform.
452+ *
453+ * Use Array[AnyRef] in WebAssembly to avoid JS-interop. In JS, use js.Array.
454+ * It is resizable by nature, so manual resizing is not needed.
455+ *
456+ * `linkTimeIf` is needed here to ensure the optimizer knows
457+ * there is only one implementation of `InnerArrayImpl`, and de-virtualize/inline
458+ * the function calls.
459+ */
460+
461+ // package private so that `protected def innerSnapshot` can access this field.
462+ private [concurrent] val innerImpl : InnerArrayImpl = {
463+ LinkingInfo .linkTimeIf[InnerArrayImpl ](LinkingInfo .isWebAssembly) {
464+ InnerArrayImpl .JArrayImpl
465+ } {
466+ InnerArrayImpl .JSArrayImpl
467+ }
468+ }
469+
470+ private [concurrent] sealed abstract class InnerArrayImpl {
471+ type Repr [E ] <: AnyRef
472+
473+ def make [E ](initialCapacity : Int ): Repr [E ]
474+ def length (v : Repr [_]): Int
475+ def get [E ](v : Repr [E ], index : Int ): E
476+ def set [E ](v : Repr [E ], index : Int , e : E ): Unit
477+ def push [E ](v : Repr [E ], e : E ): Repr [E ]
478+ def add [E ](v : Repr [E ], index : Int , e : E ): Repr [E ]
479+ def add [E ](v : Repr [E ], index : Int , items : Collection [_ <: E ]): Repr [E ]
480+ def remove [E ](v : Repr [E ], index : Int ): E
481+ def remove (v : Repr [_], index : Int , count : Int ): Unit
482+ def clone [E ](v : Repr [E ]): Repr [E ]
483+ }
484+
485+ private object InnerArrayImpl {
486+ object JSArrayImpl extends InnerArrayImpl {
487+ import scala .scalajs .js
488+
489+ type Repr [E ] = js.Array [AnyRef ]
490+
491+ @ inline def make [E ](_initialCapacity : Int ): Repr [E ] = js.Array [AnyRef ]()
492+
493+ @ inline def length (v : Repr [_]): Int = v.length
494+
495+ @ inline def get [E ](v : Repr [E ], index : Int ): E = v(index).asInstanceOf [E ]
496+
497+ @ inline def set [E ](v : Repr [E ], index : Int , e : E ): Unit =
498+ v(index) = e.asInstanceOf [AnyRef ]
499+
500+ @ inline def push [E ](v : Repr [E ], e : E ): Repr [E ] = {
501+ v.push(e.asInstanceOf [AnyRef ])
502+ v
503+ }
504+
505+ @ inline def add [E ](v : Repr [E ], index : Int , e : E ): Repr [E ] = {
506+ v.splice(index, 0 , e.asInstanceOf [AnyRef ])
507+ v
508+ }
509+
510+ @ inline def add [E ](v : Repr [E ], index : Int , items : Collection [_ <: E ]): Repr [E ] = {
511+ val itemsArray = js.Array [AnyRef ]()
512+ items.scalaOps.foreach(e => itemsArray.push(e.asInstanceOf [AnyRef ]))
513+ v.splice(index, 0 , itemsArray.toSeq: _* )
514+ v
515+ }
516+
517+ @ inline def remove [E ](v : Repr [E ], index : Int ): E =
518+ arrayRemoveAndGet(v, index).asInstanceOf [E ]
519+
520+ @ inline def remove (v : Repr [_], index : Int , count : Int ): Unit =
521+ v.splice(index, count)
522+
523+ @ inline def clone [E ](v : Repr [E ]): Repr [E ] =
524+ v.jsSlice(0 )
525+ }
526+
527+ object JArrayImpl extends InnerArrayImpl {
528+ type Repr [E ] = Array [AnyRef ]
529+
530+ @ inline def make [E ](initialCapacity : Int ): Repr [E ] = {
531+ val v = new Array [AnyRef ](roundUpToPowerOfTwo(initialCapacity + 1 ))
532+ v(0 ) = 0 .asInstanceOf [AnyRef ]
533+ v
534+ }
535+
536+ @ inline def length (v : Repr [_]): Int = v(0 ).asInstanceOf [Int ]
537+
538+ @ inline def get [E ](v : Repr [E ], index : Int ): E = v(index + 1 ).asInstanceOf [E ] // Index 0 stores the length
539+
540+ @ inline def set [E ](v : Repr [E ], index : Int , e : E ): Unit =
541+ v(index + 1 ) = e.asInstanceOf [AnyRef ]
542+
543+ @ inline def push [E ](v : Repr [E ], e : E ): Repr [E ] = {
544+ val size = length(v)
545+ val newArr = ensureCapacity(v, size + 1 )
546+ newArr(size + 1 ) = e.asInstanceOf [AnyRef ]
547+ newArr(0 ) = (size + 1 ).asInstanceOf [AnyRef ]
548+ newArr
549+ }
550+
551+ @ inline def add [E ](v : Repr [E ], index : Int , e : E ): Repr [E ] = {
552+ val innerIdx = index + 1
553+ val size = length(v)
554+ val newArr = ensureCapacity(v, size + 1 )
555+ System .arraycopy(newArr, innerIdx, newArr, innerIdx + 1 , size + 1 - innerIdx)
556+ newArr(innerIdx) = e.asInstanceOf [AnyRef ]
557+ newArr(0 ) = (size + 1 ).asInstanceOf [AnyRef ]
558+ newArr
559+ }
560+
561+ @ inline def add [E ](v : Repr [E ], index : Int , items : Collection [_ <: E ]): Repr [E ] = {
562+ val innerIdx = index + 1
563+ val size = length(v)
564+ val itemsSize = items.size()
565+ val newArr = ensureCapacity(v, size + itemsSize)
566+ System .arraycopy(newArr, innerIdx, newArr, innerIdx + itemsSize, size + 1 - innerIdx)
567+ System .arraycopy(items.toArray(), 0 , newArr, innerIdx, itemsSize)
568+ newArr(0 ) = (size + itemsSize).asInstanceOf [AnyRef ]
569+ newArr
570+ }
571+
572+ @ inline def remove [E ](v : Repr [E ], index : Int ): E = {
573+ val innerIdx = index + 1
574+ val size = length(v)
575+ val removed = v(innerIdx)
576+ System .arraycopy(v, innerIdx + 1 , v, innerIdx, size - innerIdx)
577+ v(size) = null // free reference for GC
578+ v(0 ) = (size - 1 ).asInstanceOf [AnyRef ]
579+ removed.asInstanceOf [E ]
580+ }
581+
582+ @ inline def remove (v : Repr [_], index : Int , count : Int ): Unit = {
583+ val innerIdx = index + 1
584+ val size = length(v)
585+ val toIndex = innerIdx + count
586+ System .arraycopy(v, toIndex, v, innerIdx, size + 1 - toIndex)
587+ val newSize = size - count
588+ Arrays .fill(v, newSize + 1 , newSize + 1 + count, null ) // free references for GC
589+ v(0 ) = newSize.asInstanceOf [AnyRef ]
590+ }
591+
592+ @ inline def clone [E ](v : Repr [E ]): Repr [E ] =
593+ v.clone()
594+
595+ @ inline private def ensureCapacity [E ](v : Repr [E ], minCapacity : Int ): Repr [E ] = {
596+ val capacity = v.length - 1
597+ if (capacity < minCapacity) {
598+ val newCapacity = roundUpToPowerOfTwo(minCapacity + 1 ) // Index 0 stores the length
599+ Arrays .copyOf(v, newCapacity)
600+ } else {
601+ v
602+ }
603+ }
604+ }
605+ }
606+ }
0 commit comments