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

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

/*
 *  ServerImpl.scala
 *  (ScalaCollider)
 *
 *  Copyright (c) 2008-2013 Hanns Holger Rutz. All rights reserved.
 *
 *  This software is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either
 *  version 2, june 1991 of the License, or (at your option) any later version.
 *
 *  This software is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public
 *  License (gpl.txt) along with this software; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *
 *  For further information, please contact Hanns Holger Rutz at
 *  [email protected]
 */

package de.sciss.synth
package impl

import java.net.InetSocketAddress
import de.sciss.osc.{Client => OSCClient, Dump, Message, Packet}
import java.util.{TimerTask, Timer}
import java.io.IOException
import scala.Some
import actors.{OutputChannel, DaemonActor, Actor, Channel}

private[synth] object ServerImpl {
   def add( s: Server ) {
      this.synchronized {
         if( Server.default == null ) Server.default = s
      }
   }

   def remove( s: Server ) {
      this.synchronized {
         if( Server.default == s ) Server.default = null
      }
   }
}
private[synth] final class ServerImpl( val name: String, c: OSCClient, val addr: InetSocketAddress,
                                       val config: Server.Config, val clientConfig: Client.Config )
extends Server {
   server =>

   import Server._

   private var aliveThread: Option[StatusWatcher]	= None
   private var countsVar							      = new osc.StatusReplyMessage( 0, 0, 0, 0, 0f, 0f, 0.0, 0.0 )
   private val condSync                            = new AnyRef
   private var conditionVar: Condition 			   = Running // Offline
   private var pendingCondition: Condition      	= NoPending

   val rootNode                                    = new Group( this, 0 )
   val defaultGroup                                = new Group( this, 1 )
   val nodeManager                                 = new NodeManager( this )
   val bufManager                                  = new BufferManager( this )

   private val nodeAllocator        = new NodeIDAllocator( clientConfig.clientID, clientConfig.nodeIDOffset )
   private val controlBusAllocator  = new ContiguousBlockAllocator( config.controlBusChannels )
   private val audioBusAllocator    = new ContiguousBlockAllocator( config.audioBusChannels, config.internalBusIndex )
   private val bufferAllocator      = new ContiguousBlockAllocator( config.audioBuffers )
   private var uniqueID             = 0
   private val uniqueSync           = new AnyRef

   // ---- constructor ----
   OSCReceiverActor.start()
   c.action = OSCReceiverActor.!
   ServerImpl.add( server )

   def isLocal : Boolean = {
      val host = addr.getAddress
      host.isLoopbackAddress || host.isSiteLocalAddress
   }

   def isConnected = c.isConnected
   def isRunning = condSync.synchronized { conditionVar == Running }
   def isOffline = condSync.synchronized { conditionVar == Offline }

   def nextNodeID() = nodeAllocator.alloc()
   def allocControlBus( numChannels: Int ) = controlBusAllocator.alloc( numChannels )
   def allocAudioBus( numChannels: Int ) = audioBusAllocator.alloc( numChannels )
   def freeControlBus( index: Int ) { controlBusAllocator.free( index )}
   def freeAudioBus( index: Int ) { audioBusAllocator.free( index )}
   def allocBuffer( numChannels: Int ) = bufferAllocator.alloc( numChannels )
   def freeBuffer( index: Int ) { bufferAllocator.free( index )}
   def nextSyncID() : Int = uniqueSync.synchronized { val res = uniqueID; uniqueID += 1; res }

   def !( p: Packet ) { c ! p }

//   /**
//    * Sends out an OSC packet that generates some kind of reply, and
//    * returns immediately a `RevocableFuture` representing the parsed reply.
//    * This parsing is done by a handler which is registered.
//    * The handler is tested for each incoming OSC message (using its
//    * `isDefinedAt` method) and invoked and removed in case of a
//    * match. Note that the caller is responsible for timing out
//    * the handler after a reasonable time. To do this, the
//    * method `revoke` on the returned future must be called, which
//    * will silently unregister the handler.
//    *
//    * '''Warning''': It is crucial that the Future is awaited
//    * only within a dedicated actor thread. In particular you must
//    * be careful and aware of the fact that the handler is executed
//    * on the OSC receiver actor's body, and that you must not
//    * try to await the future from ''any'' handler function
//    * registered with OSC reception, because it would not be
//    * possible to pull the reply message of the OSC receiver's
//    * mailbox while the actor body blocks.
//    *
//    * @param   p        the packet to send out
//    * @param   handler  the handler to match against incoming messages
//    *    or timeout
//    * @return  the future representing the parsed reply, and providing
//    *    a `revoke` method to issue a timeout.
//    *
//    * @see  [[scala.actors.Futures]]
//    */
//   def !![ A ]( p: Packet, handler: PartialFunction[ Message, A ]) : RevocableFuture[ A ] = {
//      val c    = new Channel[ A ]( Actor.self )
//      val a = new FutureActor[ A ]( c ) {
//         val sync    = new AnyRef
//         var revoked = false
//         var oh: Option[ osc.Handler ] = None
//
//         def body( res: SyncVar[ A ]) {
//            val futCh   = new Channel[ A ]( Actor.self )
//            sync.synchronized { if( !revoked ) {
//               val h = new OSCInfHandler( handler, futCh )
//               oh = Some( h )
//               OSCReceiverActor.addHandler( h )
//               server ! p // only after addHandler!
//            }}
//            futCh.react { case r => res.set( r )}
//         }
//         def revoke() { sync.synchronized {
//            revoked = true
//            oh.foreach( OSCReceiverActor.removeHandler( _ ))
//            oh = None
//         }}
//      }
//      a.start()
//// NOTE: race condition, addHandler might take longer than
//// the /done, notify!
////      this ! p
//      a
//   }

   /**
    * Sends out an OSC packet that generates some kind of reply, and
    * returns immediately. It registers a handler to parse that reply.
    * The handler is tested for each incoming OSC message (using its
    * `isDefinedAt` method) and invoked and removed in case of a
    * match. If the handler doesn't match in the given timeout period,
    * it is invoked with message `TIMEOUT` and removed. If the handler
    * wishes not to do anything particular in the case of a timeout,
    * it simply should not add a case for `TIMEOUT`.
    *
    * @param   timeOut  the timeout in milliseconds
    * @param   p        the packet to send out
    * @param   handler  the handler to match against incoming messages
    *    or timeout
    *
    * @see  [[de.sciss.synth.osc.TIMEOUT]]
    */
   def !?( timeOut: Long, p: Packet, handler: PartialFunction[ Any, Unit ]) {
      val a = new DaemonActor {
         def act() {
            val futCh   = new Channel[ Any ]( Actor.self )
            val oh      = new OSCTimeOutHandler( handler, futCh )
            OSCReceiverActor.addHandler( oh )
            server ! p // only after addHandler!
            futCh.reactWithin( timeOut ) {
               case actors.TIMEOUT  => OSCReceiverActor.timeOutHandler( oh )
               case r               =>
            }
         }
      }
      a.start()
// NOTE: race condition, addHandler might take longer than
// the /done, notify!
//      this ! p
   }

   def counts = countsVar
   private[synth] def counts_=( newCounts: osc.StatusReplyMessage ) {
      countsVar = newCounts
      dispatch( Counts( newCounts ))
   }

   def sampleRate = counts.sampleRate

   def dumpTree( controls: Boolean = false ) {
      import Ops._
      rootNode.dumpTree( controls )
   }

   def condition = condSync.synchronized { conditionVar }
   private[synth] def condition_=( newCondition: Condition ) {
      condSync.synchronized {
         if( newCondition != conditionVar ) {
            conditionVar = newCondition
            if( newCondition == Offline ) {
               pendingCondition = Server.NoPending
               serverLost()
            }
//            else if( newCondition == Running ) {
//               if( pendingCondition == Booting ) {
//                  pendingCondition = NoPending
//                  collBootCompletion.foreach( action => try {
//                        action.apply( this )
//                     }
//                     catch { case e => e.printStackTrace() }
//                  )
//                  collBootCompletion = Queue.empty
//               }
//            }
            dispatch( newCondition )
         }
      }
   }

   def startAliveThread( delay: Float = 0.25f, period: Float = 0.25f, deathBounces: Int = 25 ) {
      condSync.synchronized {
         if( aliveThread.isEmpty ) {
            val statusWatcher = new StatusWatcher( delay, period, deathBounces )
            aliveThread = Some( statusWatcher )
            statusWatcher.start()
         }
      }
   }

   def stopAliveThread() {
      condSync.synchronized {
         aliveThread.foreach( _.stop() )
         aliveThread = None
      }
  }

   def queryCounts() {
      this ! osc.StatusMessage
   }

   def dumpOSC( mode: Dump = Dump.Text ) {
      c.dumpIn( mode, filter = {
         case m: osc.StatusReplyMessage => false
         case _ => true
      })
      c.dumpOut( mode, filter = {
         case osc.StatusMessage => false
         case _ => true
      })
   }

   private def serverLost() {
      nodeManager.clear()
      bufManager.clear()
      OSCReceiverActor.clear()
   }

   def serverOffline() {
      condSync.synchronized {
         stopAliveThread()
         condition = Offline
      }
   }

   def quit() {
      this ! quitMsg
      dispose()
   }

   def addResponder( resp: osc.Responder ) {
      OSCReceiverActor.addHandler( resp )
   }

   def removeResponder( resp: osc.Responder ) {
      OSCReceiverActor.removeHandler( resp )
   }

   def initTree() {
      nodeManager.register( defaultGroup )
      server ! defaultGroup.newMsg( rootNode, addToHead )
   }

   def dispose() {
      condSync.synchronized {
         serverOffline()
         ServerImpl.remove( this )
         c.close()
         OSCReceiverActor.dispose()
      }
   }

   // -------- internal class StatusWatcher --------

   private class StatusWatcher( delay: Float, period: Float, deathBounces: Int )
   extends Runnable {
      watcher =>

      private var	alive			   = deathBounces
      private val	delayMillis		= (delay * 1000).toInt
      private val	periodMillis	= (period * 1000).toInt
//      private val	timer			   = new SwingTimer( periodMillis, this )
      private var timer: Option[ Timer ] = None
      private var callServerContacted  = true
      private val sync           = new AnyRef

//      // ---- constructor ----
//      timer.setInitialDelay( delayMillis )

      def start() {
         stop()
         timer = {
            val t = new Timer( "StatusWatcher", true )
            t.schedule( new TimerTask {
               def run() { watcher.run() } // invokeOnMainThread( watcher )
            }, delayMillis, periodMillis )
            Some( t )
         }
      }

      def stop() {
//         timer.stop
         timer.foreach { t =>
            t.cancel()
            timer = None
         }
      }

      def run() {
         sync.synchronized {
            alive -= 1
            if( alive < 0 ) {
               callServerContacted = true
               condition = Offline
            }
         }
         try {
            queryCounts()
         }
         catch { case e: IOException => printError( "Server.status", e )}
      }

      def statusReply( msg: osc.StatusReplyMessage ) {
         sync.synchronized {
            alive = deathBounces
            // note: put the counts before running
            // because that way e.g. the sampleRate
            // is instantly available
            counts = msg
            if( !isRunning && callServerContacted ) {
               callServerContacted = false
//               serverContacted
               condition = Running
            }
         }
      }
   }

   private object OSCReceiverActor extends DaemonActor {
      private case object Clear
      private case object Dispose
//      private case class  ReceivedMessage( msg: Message, sender: SocketAddress, time: Long )
      private case class  AddHandler( h: osc.Handler )
      private case class  RemoveHandler( h: osc.Handler )
      private case class  TimeOutHandler( h: OSCTimeOutHandler )

      def clear() {
         this ! Clear
      }

      def dispose() {
         clear()
         this ! Dispose
      }

      def addHandler( handler: osc.Handler ) {
         this ! AddHandler( handler )
      }

      def removeHandler( handler: osc.Handler ) {
         this ! RemoveHandler( handler )
      }

      def timeOutHandler( handler: OSCTimeOutHandler ) {
         this ! TimeOutHandler( handler )
      }

      // ------------ OSCListener interface ------------

//      def messageReceived( p: Packet ) {
////if( msg.name == "/synced" ) println( "" + new java.aux.Date() + " : ! : " + msg )
//         this ! p
//      }

      def act() {
         var running    = true
         var handlers   = Set.empty[ osc.Handler ]
//         while( running )( receive { })
         loopWhile( running )( react {
            case msg: Message => debug( msg ) {
//            case ReceivedMessage( msg, sender, time ) => debug( msg ) {
//if( msg.name == "/synced" ) println( "" + new java.aux.Date() + " : received : " + msg )
               msg match {
                  case nodeMsg:        osc.NodeChange           => nodeManager.nodeChange( nodeMsg )
                  case bufInfoMsg:     osc.BufferInfoMessage    => bufManager.bufferInfo( bufInfoMsg )
                  case statusReplyMsg: osc.StatusReplyMessage   => aliveThread.foreach( _.statusReply( statusReplyMsg ))
                  case _ =>
               }
//if( msg.name == "/synced" ) println( "" + new java.aux.Date() + " : handlers" )
               handlers.foreach( h => if( h.handle( msg )) handlers -= h )
            }
            case AddHandler( h )    => handlers += h
            case RemoveHandler( h ) => if( handlers.contains( h )) { handlers -= h; h.removed() }
            case TimeOutHandler( h )=> if( handlers.contains( h )) { handlers -= h; h.timedOut() }
            case Clear              => handlers.foreach( _.removed() ); handlers = Set.empty
            case Dispose            => running = false
            case m                  => println( "Received illegal message " + m )
         })
      }
   }

   private def debug( msg: AnyRef )( code: => Unit ) {
      val t1 = System.currentTimeMillis
      try {
         code
      } catch {
         case e: Throwable => println( "" + new java.util.Date() + " OOOPS : msg " + msg + " produced " + e )
      }
      val t2 = System.currentTimeMillis
      if( (t2 - t1) > 2000 ) println( "" + new java.util.Date() + " WOW this took long (" + (t2-t1) + "): " + msg )
   }

   // -------- internal osc.Handler implementations --------

   private class OSCInfHandler[ A ]( fun: PartialFunction[ Message, A ], ch: OutputChannel[ A ])
   extends osc.Handler {
      def handle( msg: Message ) : Boolean = {
         val handled = fun.isDefinedAt( msg )
//if( msg.name == "/synced" ) println( "" + new java.aux.Date() + " : inf handled : " + msg + " ? " + handled )
         if( handled ) try {
            ch ! fun.apply( msg )
         } catch { case e: Throwable => e.printStackTrace() }
         handled
      }
      def removed() {}
   }

   private class OSCTimeOutHandler( fun: PartialFunction[ Any, Unit ], ch: OutputChannel[ Any ])
   extends osc.Handler {
      def handle( msg: Message ) : Boolean = {
         val handled = fun.isDefinedAt( msg )
//if( msg.name == "/synced" ) println( "" + new java.aux.Date() + " : to handled : " + msg + " ? " + handled )
         if( handled ) try {
            ch ! fun.apply( msg )
         } catch { case e: Throwable => e.printStackTrace() }
         handled
      }
      def removed() {}
      def timedOut() {
         if( fun.isDefinedAt( osc.TIMEOUT )) try {
            fun.apply( osc.TIMEOUT )
         } catch { case e: Throwable => e.printStackTrace() }
      }
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy