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

de.sciss.lucre.synth.impl.ServerImpl.scala Maven / Gradle / Ivy

There is a newer version: 3.30.0
Show newest version
/*
 *  ServerImpl.scala
 *  (SoundProcesses)
 *
 *  Copyright (c) 2010-2019 Hanns Holger Rutz. All rights reserved.
 *
 *	This software is published under the GNU Lesser General Public License v2.1+
 *
 *
 *  For further information, please contact Hanns Holger Rutz at
 *  [email protected]
 */

package de.sciss.lucre.synth
package impl

import java.io.{ByteArrayOutputStream, DataOutputStream}

import de.sciss.lucre.stm.TxnLike.{peer => txPeer}
import de.sciss.osc
import de.sciss.osc.TimeTag
import de.sciss.synth.{AllocatorExhausted, ControlABusMap, ControlSet, UGenGraph, addToHead, message, Client => SClient, Server => SServer}
import de.sciss.topology.Topology

import scala.annotation.tailrec
import scala.collection.{IndexedSeq => SIndexedSeq}
import scala.collection.immutable.{IndexedSeq => Vec}
import scala.collection.mutable
import scala.concurrent.stm.{Ref, TMap}
import scala.concurrent.{ExecutionContext, Future, Promise}

object ServerImpl {
  def apply  (peer: SServer): Server          = OnlineImpl (peer)
  def offline(peer: SServer): Server.Offline  = OfflineImpl(peer)

  /** If `true`, checks against bundle size overflow (64K) and prints the bundle before crashing. */
  var VERIFY_BUNDLE_SIZE  = true
  /** If `true`, applies a few optimizations to messages within a bundle, in order to reduce its size */
  var USE_COMPRESSION     = true
  /** If `true`, check that wire-buffers are not exceeded before sending synth def */
  var VERIFY_WIRE_BUFFERS = true
  /** If `true` debug sending out stuff */
  var DEBUG               = false

  private final val MaxOnlinePacketSize   = 0x8000 // 0x10000 // 64K
  private final val MaxOfflinePacketSize  = 0x2000 // 8192

  // this is package private in ScalaOSC
  private final val SECONDS_FROM_1900_TO_1970 = 2208988800L

  def reduceFutures(futures: Vec[Future[Unit]])(implicit executionContext: ExecutionContext): Future[Unit] =
    futures match {
      case Vec()        => Future.successful(())
      case Vec(single)  => single
      case _ /* more */ => Future.reduce(futures)((_, _) => ()) // reduceLeft requires Scala 2.12
    }

  private final case class OnlineImpl(peer: SServer) extends Impl {
    override def toString: String = peer.toString

    def maxPacketSize: Int      = MaxOnlinePacketSize
    def isLocal      : Boolean  = peer.isLocal
    def isRealtime   : Boolean  = true

    // ---- compression ----

    private def compressControlSet(old: Seq[ControlSet], add: Seq[ControlSet]): Seq[ControlSet] = {
      var res = old
      add.foreach {
        case c @ ControlSet.Value(key, _) =>
          val j = res.indexWhere {
            case ControlSet.Value(`key`, _) => true
            case _ => false
          }
          res = if (j < 0) res :+ c else res.updated(j, c)

        case c => res :+= c
      }
      res
    }

    private def compress(b: osc.Bundle): osc.Bundle = {
      val in  = b.packets
      val num = in.length
      if (num < 10) return b   // don't bother

      // currently, we focus on the three messages
      // most frequently found in sound processes:
      // `/n_set`, `/n_mapan`, `/n_after`.
      // `/n_set` and `/n_mapan` can be collapsed
      // per node-id; `/n_after` can be all collapsed.
      // To ensure correctness, we must not collapse
      // across `/s_new` and `/g_new` boundaries.
      // For simplicity, we also restrict collapse
      // of `/n_after` to adjacent messages.
      // Another optimization is collapsing adjacent
      // `/n_free` messages

      // XXX TODO - actually, we don't yet optimize `/n_after`

      val out     = new Array[osc.Packet](num)
      var inOff   = 0
      var outOff  = 0

      var setMap    = Map.empty[Int, Int] // node-id to out-offset containing either n_set or s_new
      var mapanMap  = Map.empty[Int, Int] // node-id to out-offset containing n_mapan
      var nAfterIdx = -2
      var nFreeIdx  = -2

      while (inOff < num) {
        val p       = in(inOff)
        val append  = p match {
          case m: message.NodeSet =>
            val id = m.id
            val i = setMap.getOrElse(id, -1)
            val res = i < 0
            if (res) {
              setMap += id -> outOff
            } else {
              out(i) = (out(i): @unchecked) match {  // unfortunately those case classes do not have `copy` methods...
                case n: message.NodeSet =>
                  message.NodeSet(id, compressControlSet(n.pairs, m.pairs): _*)
                case n: message.SynthNew =>
                  message.SynthNew(n.defName, id, n.addAction, n.targetId,
                    compressControlSet(n.controls, m.pairs): _*)
              }
            }
            res

          case m: message.NodeMapan =>
            val id = m.id
            val i = mapanMap.getOrElse(id, -1)
            val res = i < 0
            if (res) {
              mapanMap += id -> outOff
            } else {
              var message.NodeMapan(_, mappings @ _*) = out(i)
              m.mappings.foreach {
                case c @ ControlABusMap.Single(key, _) =>
                  val j = mappings.indexWhere {
                    case ControlABusMap.Single(`key`, _) => true
                    case _ => false
                  }
                  mappings = if (j < 0) mappings :+ c else mappings.updated(j, c)

                case c @ ControlABusMap.Multi(key, _, numChannels) =>
                  val j = mappings.indexWhere {
                    case ControlABusMap.Multi(`key`, _, `numChannels`) => true
                    case _ => false
                  }
                  mappings = if (j < 0) mappings :+ c else mappings.updated(j, c)
              }
              out(i) = message.NodeMapan(id, mappings: _*)
            }
            res

          case m: message.NodeAfter =>
            val res = nAfterIdx != outOff - 1
            if (res) {  // predecessor was not n_after
              nAfterIdx = outOff

              //              // more ambitious:
              //              // collapse a single `/n_after` with an immediate
              //              // preceding `/s_new`.
              //              val g = m.groups
              //              if (g.size == 1) {
              //                val (id, after) = g.head
              //                val newIdx = setMap.getOrElse(id, -1)
              //                if (newIdx >= 0) {
              //                  ...
              //                }
              //              }

            } else {
              val message.NodeAfter(groups @ _*) = out(nAfterIdx)
              out(nAfterIdx) = message.NodeAfter(groups ++ m.groups: _*)
            }
            res

          case m: message.NodeFree =>
            val res = nFreeIdx != outOff - 1
            if (res) {  // predecessor was not n_after
              nFreeIdx = outOff
            } else {
              val message.NodeFree(ids @ _*) = out(nFreeIdx)
              out(nFreeIdx) = message.NodeFree(ids ++ m.ids: _*)
            }
            res

          case m: message.SynthNew =>
            val id = m.id
            // setMap   -= id
            setMap   += id -> outOff
            mapanMap -= id
            true

          case m: message.GroupNew =>
            m.groups.foreach { g =>
              val id = g.groupId
              setMap   -= id
              mapanMap -= id
            }
            true

          case _ => true
        }
        if (append) {
          out(outOff) = p
          outOff += 1
        }
        inOff += 1
      }

      if (outOff == num) b else {
        val outT = new Array[osc.Packet](outOff)
        System.arraycopy(out, 0, outT, 0, outOff)
        // XXX TODO 2.13: val outTW = ArraySeq.unsafeWrapArray(outT)
        val res = osc.Bundle(b.timeTag, outT: _*)
        //        Console.err.println("----------- BEFORE COMPRESSION -----------")
        //        osc.Packet.printTextOn(b  , Server.codec, Console.err)
        //        Console.err.println("----------- AFTER  COMPRESSION -----------")
        //        osc.Packet.printTextOn(res, Server.codec, Console.err)
        res
      }
    }

    // ---- side effects ----

    private def splitAndSend[A, B](init: A, it: Iterator[osc.Packet], addSize: Int)(fun: Vec[osc.Packet] => B)
                                  (combine: (A, B) => A): A = {
      @tailrec def loop(a: A, sz: Int, builder: mutable.Builder[osc.Packet, Vec[osc.Packet]]): A =
        if (it.isEmpty) {
          val res = builder.result()
          if (res.nonEmpty) {
            val a1 = fun(res)
            val a2  = combine(a, a1)
            a2
          } else a

        } else {
          val next  = it.next()
          val sz1   = next.encodedSize(Server.codec) + 4
          val sz2   = sz + sz1
          if (sz2 > MaxOnlinePacketSize) {
            val res = builder.result()
            if (res.isEmpty) sys.error(s"Cannot encode packet -- too large ($sz1)")
            val a1  = fun(res)
            val a2  = combine(a, a1)
            val newBuilder = Vec.newBuilder[osc.Packet]
            newBuilder += next
            loop(a2, 16 + addSize + sz1, newBuilder)
          } else {
            builder += next
            loop(a, sz2, builder)
          }
        }

      loop(init, 16 + addSize, Vec.newBuilder[osc.Packet])
    }

    def ! (p0: osc.Packet): Unit = {
      val p = p0 match {
        case b0: osc.Bundle if USE_COMPRESSION => compress(b0)
        case _ => p0
      }

      p match {
        case b: osc.Bundle if VERIFY_BUNDLE_SIZE =>
          val sz0 = Server.codec.encodedBundleSize(b)
          if (sz0 <= MaxOnlinePacketSize) {
            peer ! b
          } else {
            // Since the bundle is synchronous, it is not trivial to split it
            // into several bundles. And if we split at arbitrary points some
            // very bad things could happen, for example sound or feedback
            // going to wrong temporary buses, so this is absolutely forbidden.
            //
            // We take this as an "emergency" branch that one should avoid to
            // be taken at all costs. The solution for this branch is that we
            // temporarily pause the server's default group. That way no
            // damage can be done, but it may result in a short noticable bit
            // of silence. That's as good as it gets, I suppose...
            Console.err.println(s"WARNING: Bundle size $sz0 exceeds $MaxOnlinePacketSize. Splitting into multiple bundles")
            val gid   = peer.defaultGroup.id
            val iter  =
              Iterator.single(message.NodeRun(gid -> false)) ++ b.packets.iterator ++
              Iterator.single(message.NodeRun(gid -> true ))

            splitAndSend[Unit, Unit](init = (), it = iter, addSize = 0) { packets =>
              peer ! osc.Bundle(b.timeTag, packets: _*)
            } ((_, _) => ())
          }

        case _ =>
          peer ! p
      }
    }

    def !! (b0: osc.Bundle): Future[Unit] = {
      val b   = if (USE_COMPRESSION) compress(b0) else b0
      val tt  = b.timeTag
      if (VERIFY_BUNDLE_SIZE) {
        val sz0     = Server.codec.encodedBundleSize(b)
        if (sz0 + 20 <= MaxOnlinePacketSize) {
          perform_!!(tt, b.packets)
        } else {
          val it = b.packets.iterator
          val futures = splitAndSend[Vec[Future[Unit]], Future[Unit]](init = Vector.empty,
                                                                      it = it, addSize = 20) { packets =>
            perform_!!(tt, packets)
          } (_ :+ _)
          Future.reduce[Unit, Unit](futures)((_, _) => ()) // reduceLeft requires Scala 2.12
        }
      } else {
        perform_!!(tt, b.packets)
      }
    }

    private def perform_!!(tt: TimeTag, packets: Seq[osc.Packet]): Future[Unit] = {
      val syncMsg = peer.syncMsg()
      val syncId  = syncMsg.id
      val bndlS   = osc.Bundle(tt, packets :+ syncMsg: _*)
      peer.!!(bndlS) {
        case message.Synced(`syncId`) =>
      }
    }

    def commit(future: Future[Unit]): Unit = ()  // we don't use these
  }

  private final case class OfflineImpl(peer: SServer) extends Impl with Server.Offline {
    override def toString = s"$peer @offline"

    def isLocal      : Boolean  = true
    def isRealtime   : Boolean  = false
    def maxPacketSize: Int      = MaxOfflinePacketSize

    private val sync = new AnyRef

    var position  = 0L

    private var _bundles      = Vector.empty[osc.Bundle]
    private var _commits      = Vector.empty[Future[Unit]]

    private def time: Double  = position / sampleRate

    def committed(): Future[Unit] = sync.synchronized {
      val futures       = filteredCommits
      _commits          = Vector.empty
      // implicit val exec = peer.clientConfig.executionContext
      ServerImpl.reduceFutures(futures)
    }

    def bundles(addDefaultGroup: Boolean): Vec[osc.Bundle] = sync.synchronized {
      val res   = _bundles
      _bundles  = Vector.empty
      val res1  = if (res.isEmpty || !addDefaultGroup) res else {
        val b   = osc.Bundle(res.head.timeTag,
          message.GroupNew(message.GroupNew.Data(groupId = 1, addAction = addToHead.id, targetId = 0)))
        b +: res
      }
      res1
    }

    private def addBundle(b: osc.Bundle): Unit = sync.synchronized {
      val b1 = if (b.timeTag == osc.TimeTag.now) osc.Bundle.secs(time, b.packets: _*) else b
      val sz = Server.codec.encodedBundleSize(b1)
      // SuperCollider versions until 2014 have a hard-coded limit of 8K bundles in NRT!
      // cf. https://github.com/supercollider/supercollider/commit/f3f0f81de4259aa44983f1041589f895c91798a1
      val szOk = sz <= MaxOfflinePacketSize
      if (szOk || b1.packets.length == 1) {
        log(s"addBundle $b1")
        if (!szOk) log(s"addBundle - bundle exceeds ${MaxOfflinePacketSize/1024}k!")
        _bundles :+= b1
      } else {
        val tt = b1.timeTag
        b.packets.foreach(p => addBundle(osc.Bundle(tt, p)))
      }
    }

    def ! (p: osc.Packet): Unit = {
      val b = p match {
        case m : osc.Message  => osc.Bundle.secs(time, m)
        case b0: osc.Bundle   => b0
      }
      addBundle(b)
    }

    def !! (bndl: osc.Bundle): Future[Unit] = {
      addBundle(bndl)
      Future.successful(())
    }

    // caller must have `sync`
    private def filteredCommits = _commits.filterNot(_.isCompleted)

    def commit(future: Future[Unit]): Unit =
      sync.synchronized {
        _commits = filteredCommits :+ future
      }
  }

  private abstract class Impl extends Server { server =>

    implicit def executionContext: ExecutionContext = peer.clientConfig.executionContext

    final private[this] val controlBusAllocator = BlockAllocator("control", peer.config.controlBusChannels)
    final private[this] val audioBusAllocator   = BlockAllocator("audio"  , peer.config.audioBusChannels, peer.config.internalBusIndex)
    final private[this] val bufferAllocator     = BlockAllocator("buffer" , peer.config.audioBuffers)
    final private[this] val nodeAllocator       = NodeIdAllocator(peer.clientConfig.clientId, peer.clientConfig.nodeIdOffset)

    final val defaultGroup: Group = Group.wrap(this, peer.defaultGroup)

    final def config      : Server .Config = peer.config
    final def clientConfig: SClient.Config = peer.clientConfig

    final def sampleRate: Double              = peer.sampleRate
    final def counts    : message.StatusReply = peer.counts

    final def allocControlBus(numChannels: Int)(implicit tx: Txn): Int = {
      val res = controlBusAllocator.alloc(numChannels)
      if (res < 0) throw AllocatorExhausted(s"Control buses exhausted for $this")
      res
    }

    final def allocAudioBus(numChannels: Int)(implicit tx: Txn): Int = {
      val res = audioBusAllocator.alloc(numChannels)
      if (res < 0) throw AllocatorExhausted(s"Audio buses exhausted for $this")
      res
    }

    final def freeControlBus(index: Int, numChannels: Int)(implicit tx: Txn): Unit =
      controlBusAllocator.free(index, numChannels)

    final def freeAudioBus(index: Int, numChannels: Int)(implicit tx: Txn): Unit =
      audioBusAllocator.free(index, numChannels)

    final def allocBuffer(numConsecutive: Int)(implicit tx: Txn): Int = {
      val res = bufferAllocator.alloc(numConsecutive)
      if (res < 0) throw AllocatorExhausted(s"Buffers exhausted for $this")
      res
    }

    final def freeBuffer(index: Int, numConsecutive: Int)(implicit tx: Txn): Unit =
      bufferAllocator.free(index, numConsecutive)

    final def nextNodeId()(implicit tx: Txn): Int = nodeAllocator.alloc()

    // ---- former Server ----

    private type T = Topology[NodeRef, NodeRef.Edge]

    final private[this] val ugenGraphMap  = TMap      .empty[ SIndexedSeq[Byte], SynthDef]
    final private[this] val synthDefLRU   = Ref(Vector.empty[(SIndexedSeq[Byte], SynthDef)])

    // limit on number of online defs XXX TODO -- head room rather arbitrary
    final private[this] val maxDefs       = math.max(128, server.config.maxSynthDefs - 128)

    final private[this] val topologyRef = Ref[T](Topology.empty[NodeRef, NodeRef.Edge])

    final def topology(implicit tx: Txn): T = topologyRef()

    final def acquireSynthDef(graph: UGenGraph, nameHint: Option[String])(implicit tx: Txn): SynthDef = {
      val bos   = new ByteArrayOutputStream
      val dos   = new DataOutputStream(bos)
      graph.write(dos, version = 1) // Escape.write(graph, dos)
      dos.flush()
      dos.close()
      val bytes = bos.toByteArray
      val equ: SIndexedSeq[Byte] = bytes // opposed to plain `Array[Byte]`, this has correct definition of `equals`
      log(s"request for synth graph ${equ.hashCode()}")

      ugenGraphMap.get(equ).fold[SynthDef] {
        log(s"synth graph ${equ.hashCode()} is new")
        if (VERIFY_WIRE_BUFFERS) {
          val wires     = UGenGraph.calcWireBuffers(graph)
          val maxWires  = server.peer.config.wireBuffers
          if (wires > maxWires) {
            val nameS = nameHint.fold("")(n => s" for '$n'")
            throw new IndexOutOfBoundsException(s"UGen graph$nameS exceeds number of wire buffers ($wires > $maxWires")
          }
        }

        val name  = mkSynthDefName(nameHint)
        val peer  = de.sciss.synth.SynthDef(name, graph)
        val rd    = impl.SynthDefImpl(server, peer)
        val lru   = synthDefLRU.transformAndGet((equ, rd) +: _)
        if (lru.size == maxDefs) {
          val init :+ Tuple2(lastEqu, lastDf) = lru
          log(s"purging synth-def ${lastDf.name}")
          lastDf.dispose()
          ugenGraphMap.remove(lastEqu)
          synthDefLRU() = init
        }
        rd.recv()
        ugenGraphMap.put(equ, rd)
        rd
      } { rd =>
        synthDefLRU.transform { xs =>
          val idx = xs.indexWhere(_._1 == equ)
          val ys = xs.patch(idx, Nil, 1)  // remove from old spot
          (equ, rd) +: ys // put to head as most recently used item
        }
        rd
      }
    }

    final def addVertex(node: NodeRef)(implicit tx: Txn): Unit = {
      log(s"Server.addVertex($node)")
      topologyRef.transform(_.addVertex(node))
    }

    final def removeVertex(node: NodeRef)(implicit tx: Txn): Unit = {
      log(s"Server.removeVertex($node)")
      topologyRef.transform(_.removeVertex(node))
    }

    final def addEdge(edge: NodeRef.Edge)(implicit tx: Txn): Boolean = {
      log(s"Server.addEdge($edge)")
      val res = topologyRef().addEdge(edge)
      res.foreach { case (topNew, moveOpt) =>
        topologyRef() = topNew
        moveOpt.foreach {
          case Topology.MoveAfter (ref, aff) =>
            val refNode = ref.node
            aff.reverseIterator.foreach { x =>
              val xNode = x.node
              xNode.moveAfter(refNode)
            }
          case Topology.MoveBefore(ref, aff) =>
            val refNode = ref.node
            aff.foreach { x =>
              val xNode = x.node
              xNode.moveBefore(refNode)
            }
        }
      }
      res.isSuccess
    }

    final def removeEdge(edge: NodeRef.Edge)(implicit tx: Txn): Unit = {
      log(s"Server.removeEdge($edge)")
      topologyRef.transform(_.removeEdge(edge))
    }

    final private[this] val uniqueDefId = Ref(0)

    final private[this] def allCharsOk(name: String): Boolean = {
      val len = name.length
      var i   = 0
      while (i < len) {
        val c   = name.charAt(i).toInt
        val ok  = c > 36 && c < 123 || c != 95 // in particular, disallow underscore
        if (!ok) return false
        i += 1
      }
      true
    }

    final def mkSynthDefName(nameHint: Option[String])(implicit tx: Txn): String = {
      val id = nextDefId()
      abbreviate(nameHint.getOrElse("proc"), s"_$id")
    }

    final private[this] def abbreviate(name: String, suffix: String): String = {
      val len = name.length
      val lim = 16 - suffix.length
      val sb  = new StringBuffer(16)
      if ((len <= lim) && allCharsOk(name)) {
        sb.append(name)
      } else {
        var i   = 0
        while (i < len && sb.length() < lim) {
          val c = name.charAt(i).toInt
          val ok = c > 36 && c < 123 || c != 95 // in particular, disallow underscore
          if (ok) sb.append(c.toChar)
          i += 1
        }
      }
      sb.append(suffix)
      sb.toString
    }

    final private[this] def nextDefId()(implicit tx: Txn): Int =
      uniqueDefId.getAndTransform(_ + 1)

    // ---- sending ----

    final private[this] val msgStampRef = Ref(0)

    final private[synth] def messageTimeStamp: Ref[Int] = msgStampRef

    final private[this] val sync            = new AnyRef
    final private[this] var bundleWaiting   = Map.empty[Int, Vec[Scheduled]]
    final private[this] var bundleReplySeen = -1

    final private[this] class Scheduled(bundle: Txn.Bundle, timeTag: TimeTag, promise: Promise[Unit]) {
      def apply(): Future[Unit] = {
        val fut = sendNow(bundle, timeTag)
        promise.completeWith(fut)
        fut
      }
    }

    final private[this] def sendAdvance(stamp: Int): Future[Unit] = {
      if (DEBUG) println(s"ADVANCE $stamp")
      val futures: Vec[Future[Unit]] = sync.synchronized {
        val i = bundleReplySeen + 1
        if (i <= stamp) {
          bundleReplySeen = stamp
          val scheduled: Vec[Scheduled] = (i to stamp).flatMap { j =>
            bundleWaiting.get(j) match {
              case Some(sch)  => bundleWaiting -= j; sch
              case _          => Vector.empty
            }
          }
          scheduled.map(_.apply())
        }
        else Vector.empty
      }
      reduceFutures(futures)
    }

    final private[this] def sendNow(bundle: Txn.Bundle, timeTag: TimeTag): Future[Unit] = {
      import bundle.{messages, stamp}
      if (messages.isEmpty) return sendAdvance(stamp)

      // for simplicity, if a time-tag is used (`&& timeTag`), we require acknowledgement
      // of completion through `/synced`. a more performative variant for the
      // case of `allSync` might be to store the time-tag somewhere so that if
      // a dependent message comes later with time-tag now, it would have to
      // be adapted to a time-tag no smaller than this.
      // the disadvantage of the forced sync will be that high frequency
      // parameter changes might be jammed, even though correctness is preserved.
      val allSync = (stamp & 1) == 1 && timeTag == TimeTag.now
      if (DEBUG) println(s"SEND NOW $messages - allSync? $allSync; stamp = $stamp")
      if (allSync) {
        val p = if (messages.size == 1 && timeTag == TimeTag.now) messages.head
        else osc.Bundle(timeTag, messages: _*)
        server ! p
        sendAdvance(stamp)

      } else {
        val bndl  = osc.Bundle(timeTag, messages: _*)
        val fut   = server.!!(bndl)
        val futR  = fut.recover {
          case message.Timeout() =>
            log("TIMEOUT while sending OSC bundle!")
        }
        futR.flatMap(_ => sendAdvance(stamp))
      }
    }

    final def send(bundles: Txn.Bundles, systemTimeNanos: Long): Future[Unit] = {
      // this time-tag is used for the first synchronous bundle (index 1 in bundles)
      val ttLatency = if (systemTimeNanos == 0L /* || tail.nonEmpty */) TimeTag.now else {
        // ttLatency
        val latencyNanos    = (clientConfig.latency * 1000000000L).toLong
        val targetNanos     = systemTimeNanos + latencyNanos
        val secsSince1900   =  targetNanos / 1000000000L + SECONDS_FROM_1900_TO_1970
        val secsFractional = ((targetNanos % 1000000000L) << 32) / 1000000000L
        TimeTag((secsSince1900 << 32) | secsFractional)
      }

      val res = sync.synchronized {
        val (now, later) = bundles.partition(bundleReplySeen >= _.depStamp)

        // it is important to process the 'later' bundles first,
        // because now they might rely on some `bundleReplySeen` that is
        // increased by processing the `now` bundles.

        var i = now.size
        val futuresLater = later.map { m =>
          val p   = Promise[Unit]()
          val tt  = if (i == 1) ttLatency else TimeTag.now
          val sch = new Scheduled(m, tt, p)
          bundleWaiting += m.depStamp -> (bundleWaiting.getOrElse(m.depStamp, Vector.empty) :+ sch)
          i += 1
          p.future
        }
        i = 0
        val futuresNow = now.map { m =>
          val tt  = if (i == 1) ttLatency else TimeTag.now
          i += 1
          sendNow(m, tt)
        }
        reduceFutures(futuresNow ++ futuresLater)
      }
      server.commit(res)
      res
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy