All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scala.reactive.container.ReactTileMap.scala Maven / Gradle / Ivy

The newest version!
package scala.reactive
package container



import scala.reflect.ClassTag



class ReactTileMap[@spec(Int, Long, Double) T: ClassTag](
  private[reactive] var dim: Int,
  private[reactive] val dflt: T,
  private[reactive] val compress: Boolean = true
) extends ReactContainer[(Int, Int, T)] with ReactBuilder[(Int, Int, T), ReactTileMap[T]] with ReactContainer.Default[(Int, Int, T)] {
  self =>

  import ReactTileMap._

  private var pow2size = 0
  private var previous: Ref[T] = null
  private var sz = 0
  private[reactive] var hiddenRoot: Node[T] = null
  private[reactive] var valueContainer: ReactContainer.Emitter[T] = null
  private[reactive] var insertsEmitter: Reactive.Emitter[(Int, Int, T)] = null
  private[reactive] var removesEmitter: Reactive.Emitter[(Int, Int, T)] = null
  private[reactive] var updatesEmitter: Reactive.Emitter[XY] = null
  private[reactive] var dimensionsEmitter: Reactive.Emitter[Int] = null
  private[reactive] def quadRoot = root

  protected def root: Node[T] = hiddenRoot

  protected def root_=(r: Node[T]) = hiddenRoot = r

  protected def checkRoot(d: T) {
    // due to a bug in specialization, to ensure proper
    // specialized field is initialized
    if (hiddenRoot == null) {
      init(d)
    }
  }

  protected def init(d: T) {
    // we do this due to a bug in specialization
    // field writes in the constructor end up to the generic field
    // instead of a spec one, resulting in an IllegalAccessError
    pow2size = nextPow2(dim)
    previous = new Ref[T]
    hiddenRoot = new Node.Leaf(d)
    valueContainer = new ReactContainer.Emitter[T](f => foreachNonDefaultTile(0, 0, dim, dim)(new Applier[T] {
      def apply(x: Int, y: Int, elem: T) = f(elem)
    }), () => size)
    insertsEmitter = new Reactive.Emitter[(Int, Int, T)]
    removesEmitter = new Reactive.Emitter[(Int, Int, T)]
    updatesEmitter = new Reactive.Emitter[XY]
    dimensionsEmitter = new Reactive.Emitter[Int]
  }

  init(dflt)

  def builder: ReactBuilder[(Int, Int, T), ReactTileMap[T]] = this

  def +=(kv: (Int, Int, T)) = {
    if (kv._1 >= pow2size || kv._2 >= pow2size) dimension = math.max(kv._1, kv._2)
    update(kv._1, kv._2, kv._3)
    true
  }

  def -=(kv: (Int, Int, T)) = {
    update(kv._1, kv._2, dflt)
    true
  }

  def container = this
  
  def values: ReactContainer[T] = valueContainer

  def updates: Reactive[XY] = {
    checkRoot(dflt)
    updatesEmitter
  }

  def dimensions: Reactive[Int] = {
    checkRoot(dflt)
    dimensionsEmitter
  }

  def inserts: Reactive[(Int, Int, T)] = {
    checkRoot(dflt)
    insertsEmitter
  }

  def removes: Reactive[(Int, Int, T)] = {
    checkRoot(dflt)
    removesEmitter
  }

  def default = dflt

  def size = sz

  @inline final def contains(x: Int, y: Int) = x >= 0 && x < dim && y >= 0 && y < dim

  def leaf(x: Int, y: Int) = root.leaf(x, y, pow2size)

  def apply(x: Int, y: Int): T = {
    checkRoot(dflt)
    root.apply(x, y, pow2size)
  }

  def clamp(x: Int, y: Int) = if (contains(x, y)) apply(x, y) else {
    val xt = if (x < 0) 0 else if (x >= dim) dim - 1 else x
    val yt = if (y < 0) 0 else if (y >= dim) dim - 1 else y
    apply(xt, yt)
  }

  def orElse(x: Int, y: Int, elem: T) = {
    if (contains(x, y)) apply(x, y) else elem
  }

  def read(array: Array[T], width: Int, height: Int, fromx: Int, fromy: Int, untilx: Int, untily: Int) {
    checkRoot(dflt)
    root.read(array, width, height, fromx, fromy, untilx, untily, 0, 0, pow2size)
  }

  def update(xy: XY, elem: T): Unit = this(xy.x, xy.y) = elem

  def update(x: Int, y: Int, elem: T): Unit = {
    require(contains(x, y))
    checkRoot(dflt)
    
    val old = root
    root = root.update(x, y, pow2size, implicitly[ClassTag[T]], default, elem, compress, previous)
    
    val prevelem = previous.elem
    val prevDefault = prevelem == dflt
    val currDefault = elem == dflt

    if (prevDefault && !currDefault) sz += 1
    else if (!prevDefault && currDefault) sz -= 1

    if (!prevDefault) {
      valueContainer.removes += prevelem
      if (removesEmitter.hasSubscriptions) {
        removesEmitter += (x, y, prevelem)
      }
    }

    if (!currDefault) {
      valueContainer.inserts += elem
      if (insertsEmitter.hasSubscriptions) {
        insertsEmitter += (x, y, elem)
      }
    }
    updatesEmitter += XY(x, y)
  }

  def updateSafe(x: Int, y: Int, elem: T): Unit = {
    if (contains(x, y)) update(x, y, elem)
  }
  
  def foreachLeaf[U](f: T => U): Unit = root.foreachLeaf(f)

  def foreachNonDefaultTile[U](fromx: Int, fromy: Int, untilx: Int, untily: Int)(t: Applier[T]): Unit = {
    checkRoot(dflt)
    def clamp(v: Int) = if (v < 0) 0 else if (v >= dim) dim - 1 else v
    root.foreachTile[U](clamp(fromx), clamp(fromy), clamp(untilx), clamp(untily), 0, 0, pow2size, dflt, true)(t)
  }

  def foreachTile[U](fromx: Int, fromy: Int, untilx: Int, untily: Int)(t: Applier[T]): Unit = {
    checkRoot(dflt)
    def clamp(v: Int) = if (v < 0) 0 else if (v >= dim) dim - 1 else v
    root.foreachTile[U](clamp(fromx), clamp(fromy), clamp(untilx), clamp(untily), 0, 0, pow2size, dflt, false)(t)
  }

  def foreach(f: ((Int, Int, T)) => Unit) = foreachNonDefaultTile(0, 0, dim, dim)(new Applier[T] {
    def apply(x: Int, y: Int, elem: T) = f((x, y, elem))
  })

  protected def clearSafe(dummy: T) = {
    checkRoot(dflt)

    val oldroot = root
    root = new Node.Leaf(dflt)

    if (removesEmitter.hasSubscriptions) {
      oldroot.foreachTile(0, 0, dim, dim, 0, 0, dim, dflt, true)(new Applier[T] {
        def apply(x: Int, y: Int, elem: T) = {
          valueContainer.removes += elem
          removesEmitter += (x, y, elem)
        }
      })
    }
  }

  def clear() = clearSafe(dflt)

  def dimension = dim

  def dimension_=(ndim: Int) = setDimensionSafe(ndim, dflt)

  protected def setDimensionSafe(ndim: Int, dummy: T) {
    checkRoot(dflt)

    val npow2size = nextPow2(ndim)
    val nroot = {
      var x, y = 0
      var nr: Node[T] = new Node.Leaf(default)
      val ylimit = math.min(ndim, dim)
      val xlimit = math.min(ndim, dim)
      while (y < ylimit) {
        while (x < xlimit) {
          val elem = apply(x, y)
          nr = nr.update(x, y, npow2size, implicitly[ClassTag[T]], default, elem, compress, previous)
          x += 1
        }
        y += 1
      }
      nr
    }
    val oldpow2size = pow2size
    val oldroot = root
    val olddim = dim

    dim = ndim
    pow2size = npow2size
    root = nroot

    dimensionsEmitter += ndim

    if (ndim < olddim && removesEmitter.hasSubscriptions) {
      var x, y = 0
      while (y < olddim) {
        while (x < olddim) {
          if (x >= ndim || y >= ndim) {
            val elem = oldroot.apply(x, y, oldpow2size)
            if (elem != dflt) removesEmitter += (x, y, elem)
          }
          x += 1
        }
        y += 1
      }
    }
  }

}


object ReactTileMap {

  trait Applier[@spec(Int, Long, Double) T] {
    def apply(x: Int, y: Int, elem: T): Unit
  }

  sealed trait Event
  case object NoEvent extends Event
  case class Resize(dim: Int) extends Event
  case class Update(x: Int, y: Int) extends Event
  case object Clear extends Event

  trait DefaultValue[@spec(Int, Long, Double) S] {
    def apply(): S
  }

  def DefaultValue[@spec(Int, Long, Double) S](v: S) = new DefaultValue[S] {
    def apply() = v
  }

  def apply[@spec(Int, Long, Double) T: ClassTag](size: Int, default: T) = new ReactTileMap[T](size, default)

  implicit def factory[@spec(Int, Long, Double) S](implicit d: DefaultValue[S], ct: ClassTag[S]) = new ReactBuilder.Factory[(Int, Int, S), ReactTileMap[S]] {
    def apply() = new ReactTileMap[S](1, d())
  }

  private[reactive] class Ref[@spec(Int, Long, Double) T] {
    var elem: T = _
  }

  /* implementation */

  final def matrixSize = 4

  trait Node[@spec(Int, Long, Double) T] {
    def apply(x: Int, y: Int, dim: Int): T
    def read(array: Array[T], width: Int, height: Int, fromx: Int, fromy: Int, untilx: Int, untily: Int, offsetx: Int, offsety: Int, dim: Int): Unit
    def update(x: Int, y: Int, dim: Int, tag: ClassTag[T], d: T, elem: T, compress: Boolean, previous: Ref[T]): Node[T]
    def foreachLeaf[U](f: T => U): Unit
    def foreachTile[U](fromx: Int, fromy: Int, untilx: Int, untily: Int, offsetx: Int, offsety: Int, dim: Int, dflt: T, nonDefault: Boolean)(f: Applier[T]): Unit
    def leaf(x: Int, y: Int, dim: Int): Node[T]
    def isLeaf: Boolean = false
    def asLeaf: Node.Leaf[T] = ???
  }

  object Node {
    final case class Leaf[@spec(Int, Long, Double) T](val element: T) extends Node[T] {
      def apply(x: Int, y: Int, dim: Int) = element
      def update(x: Int, y: Int, dim: Int, tag: ClassTag[T], d: T, elem: T, compress: Boolean, previous: Ref[T]) = if (element == elem) this else {
        if (dim > matrixSize) {
          val fork = new Fork[T](
            new Leaf(element),
            new Leaf(element),
            new Leaf(element),
            new Leaf(element)
          )
          fork.update(x, y, dim, tag, d, elem, compress, previous)
        } else {
          val matrix = new Matrix[T](tag.newArray(matrixSize * matrixSize))
          matrix.fill(element)
          matrix.update(x, y, dim, tag, d, elem, compress, previous)
        }
      }
      def read(array: Array[T], width: Int, height: Int, fromx: Int, fromy: Int, untilx: Int, untily: Int, offsetx: Int, offsety: Int, dim: Int) {
        val xlimit = math.min(offsetx + dim, untilx)
        val ylimit = math.min(offsety + dim, untily)
        var x = math.max(offsetx, fromx)
        while (x < xlimit) {
          var y = math.max(offsety, fromy)
          while (y < ylimit) {
            array(width * (y - fromy) + (x - fromx)) = element
            y += 1
          }
          x += 1
        }
      }
      def foreachLeaf[U](f: T => U) {
        f(element)
      }
      def foreachTile[U](fromx: Int, fromy: Int, untilx: Int, untily: Int, offsetx: Int, offsety: Int, dim: Int, dflt: T, nonDefault: Boolean)(f: Applier[T]) {
        if (!nonDefault || (element != dflt)) {
          var x = offsetx
          while (x < offsetx + dim) {
            var y = offsety
            while (y < offsety + dim) {
              f(x, y, element)
              y += 1
            }
            x += 1
          }
        }
      }
      def leaf(x: Int, y: Int, dim: Int): Node[T] = {
        assert(dim > matrixSize, dim)
        this
      }
      override def isLeaf = true
      override def asLeaf = this.asInstanceOf[Leaf[T]]
    }

    final case class Fork[@spec(Int, Long, Double) T](
      var nw: Node[T],
      var ne: Node[T],
      var sw: Node[T],
      var se: Node[T]
    ) extends Node[T] {
      def apply(x: Int, y: Int, dim: Int) = {
        val xmid = dim / 2
        val ymid = dim / 2
        if (x < xmid) {
          if (y < ymid) sw.apply(x, y, dim / 2)
          else nw.apply(x, y - ymid, dim / 2)
        } else {
          if (y < ymid) se.apply(x - xmid, y, dim / 2)
          else ne.apply(x - xmid, y - ymid, dim / 2)
        }
      }
      def read(array: Array[T], width: Int, height: Int, fromx: Int, fromy: Int, untilx: Int, untily: Int, offsetx: Int, offsety: Int, dim: Int) {
        val xmid = offsetx + dim / 2
        val ymid = offsety + dim / 2
        if (fromx < xmid) {
          if (fromy < ymid) sw.read(array, width, height, fromx, fromy, untilx, untily, offsetx, offsety, dim / 2)
          if (untily > ymid) nw.read(array, width, height, fromx, fromy, untilx, untily, offsetx, ymid, dim / 2)
        }
        if (untilx > xmid) {
          if (fromy < ymid) se.read(array, width, height, fromx, fromy, untilx, untily, xmid, offsety, dim / 2)
          if (untily > ymid) ne.read(array, width, height, fromx, fromy, untilx, untily, xmid, ymid, dim / 2)
        }
      }
      def update(x: Int, y: Int, dim: Int, tag: ClassTag[T], d: T, elem: T, compress: Boolean, previous: Ref[T]) = {
        val xmid = dim / 2
        val ymid = dim / 2

        if (x < xmid) {
          if (y < ymid) sw = sw.update(x, y, dim / 2, tag, d, elem, compress, previous)
          else nw = nw.update(x, y - ymid, dim / 2, tag, d, elem, compress, previous)
        } else {
          if (y < ymid) se = se.update(x - xmid, y, dim / 2, tag, d, elem, compress, previous)
          else ne = ne.update(x - xmid, y - ymid, dim / 2, tag, d, elem, compress, previous)
        }

        if (compress && nw.isLeaf && ne.isLeaf && sw.isLeaf && se.isLeaf) {
          val nwe = nw.asLeaf.element
          val nee = ne.asLeaf.element
          val swe = sw.asLeaf.element
          val see = se.asLeaf.element
          if (nwe == nee && nwe == swe && nwe == see) new Leaf(nwe)
          else this
        } else this
      }
      def foreachLeaf[U](f: T => U) {
        nw.foreachLeaf(f)
        ne.foreachLeaf(f)
        sw.foreachLeaf(f)
        se.foreachLeaf(f)
      }
      def foreachTile[U](fromx: Int, fromy: Int, untilx: Int, untily: Int, offsetx: Int, offsety: Int, dim: Int, dflt: T, nonDefault: Boolean)(f: Applier[T]) {
        val xmid = offsetx + dim / 2
        val ymid = offsety + dim / 2
        if (fromx < xmid) {
          if (fromy < ymid) sw.foreachTile(fromx, fromy, untilx, untily, offsetx, offsety, dim / 2, dflt, nonDefault)(f)
          if (untily > ymid) nw.foreachTile(fromx, fromy, untilx, untily, offsetx, ymid, dim / 2, dflt, nonDefault)(f)
        }
        if (untilx > xmid) {
          if (fromy < ymid) se.foreachTile(fromx, fromy, untilx, untily, xmid, offsety, dim / 2, dflt, nonDefault)(f)
          if (untily > ymid) ne.foreachTile(fromx, fromy, untilx, untily, xmid, ymid, dim / 2, dflt, nonDefault)(f)
        }
      }
      def leaf(x: Int, y: Int, dim: Int): Node[T] = {
        val xmid = dim / 2
        val ymid = dim / 2
        if (x < xmid) {
          if (y < ymid) sw.leaf(x, y, dim / 2)
          else nw.leaf(x, y - ymid, dim / 2)
        } else {
          if (y < ymid) se.leaf(x - xmid, y, dim / 2)
          else ne.leaf(x - xmid, y - ymid, dim / 2)
        }
      }
    }

    final case class Matrix[@spec(Int, Long, Double) T](val array: Array[T]) extends Node[T] {
      def apply(x: Int, y: Int, dim: Int) = {
        array(y * matrixSize + x)
      }
      def read(output: Array[T], width: Int, height: Int, fromx: Int, fromy: Int, untilx: Int, untily: Int, offsetx: Int, offsety: Int, dim: Int) {
        val xlimit = math.min(offsetx + dim, untilx)
        val ylimit = math.min(offsety + dim, untily)
        var y = math.max(offsety, fromy)
        while (y < ylimit) {
          var x = math.max(offsetx, fromx)
          while (x < xlimit) {
            val element = array((y - offsety) * matrixSize + (x - offsetx))
            output(width * (y - fromy) + (x - fromx)) = element
            x += 1
          }
          y += 1
        }
      }
      private def allSame(elem: T): Boolean = {
        var i = 1
        while (i < matrixSize * matrixSize) {
          if (elem != array(i)) return false
          i += 1
        }
        true
      }
      def fill(elem: T) {
        var i = 0
        while (i < array.length) {
          array(i) = elem
          i += 1
        }
      }
      def update(x: Int, y: Int, dim: Int, tag: ClassTag[T], d: T, elem: T, compress: Boolean, previous: Ref[T]) = {
        val prev = array(y * matrixSize + x)
        array(y * matrixSize + x) = elem
        previous.elem = prev

        if (compress && allSame(array(0))) new Leaf(elem)
        else this
      }
      def foreachLeaf[U](f: T => U) {
        var x = 0
        while (x < matrixSize) {
          var y = 0
          while (y < matrixSize) {
            f(array(y * matrixSize + x))
            y += 1
          }
          x += 1
        }
      }
      def foreachTile[U](fromx: Int, fromy: Int, untilx: Int, untily: Int, offsetx: Int, offsety: Int, dim: Int, dflt: T, nonDefault: Boolean)(f: Applier[T]) {
        val xlimit = math.min(offsetx + dim, untilx)
        val ylimit = math.min(offsety + dim, untily)
        var y = math.max(offsety, fromy)
        while (y < ylimit) {
          var x = math.max(offsetx, fromx)
          while (x < xlimit) {
            val element = array((y - offsety) * matrixSize + (x - offsetx))
            if (!nonDefault || (element != dflt)) f(x, y, element)
            x += 1
          }
          y += 1
        }
      }
      def leaf(x: Int, y: Int, dim: Int): Node[T] = this
    }
  }

}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy