Saturday, August 27, 2011

ScalaSpace






Expect frequent changes.





ScalaSpace


The idea behind the ScalaSpace library is to model concurrent work in a data driven way.






Here is the Types object.
The type Work represents work that can be registered with a space by a worker.








package types

object Types {
type Work[X] = PartialFunction[X, Unit]
}




Spawner


Here is the Spawner object.
The method spawn spawns work w by applying it to data x.






package spawner

import java.util.Timer
import java.util.TimerTask

import types.Types.Work

object Spawner {
def spawn[X](x: X, w: Work[X]) {
val timer = new Timer()
timer.schedule(
new TimerTask() {
def run() = {
w(x)
timer.cancel()
}
}, 0)
}
}




Space


Here is the Space class.
It has a data queue xs and a work queue ws.
The method put either spawns the first work from the work queue with data (and removes the work from the work queue) or puts data in the data queue (when no work from the work queue can be applied to it).
The method reg either spawns work with the first data from the data queue (and removes the data from the data queue) or puts work in the work queue (when it cannot be applied to data from the data queue).






package space;

import scala.collection.immutable.Queue

import types.Types.Work

import spawner.Spawner.spawn

class Space[X] {
private var xs: Queue[X] = Queue()
private var ws: Queue[Work[X]] = Queue()

override def toString = "(" + xs + ")"

def put(x: X) = synchronized {
ws find (_.isDefinedAt(x)) match {
case None =>
xs = xs :+ x
case Some(w) =>
ws = ws diff Queue(w)
spawn(x, w)
}
}

def reg(w: Work[X]) = synchronized {
xs find (w.isDefinedAt(_)) match {
case None =>
ws = ws :+ w
case Some(x) =>
xs = xs diff Queue(x)
spawn(x, w)
}
}
}




Worker


Here is the Worker abstract class.
A worker has a reference to a space.
The abstract method work speaks for itself.






package worker

import space.Space

abstract class Worker[X](space: Space[X]) {
def work(): Unit
}




PingPongApp


We are ready for the first application PingPongApp.
Details will be explained in what follows: for now, just admire it's simplicity.






package pingpong.globals

import scala.math.random
import scala.math.round

import pingpong.table.Table
import pingpong.player.Pinger
import pingpong.player.Ponger
import pingpong.umpire.Umpire

object Globals {
val table = Table()

private def playTime = round(1000 * random)

val players =
Pinger("pinger_1", table, playTime) ::
Pinger("pinger_2", table, playTime) ::
Ponger("ponger_1", table, playTime) ::
Ponger("ponger_2", table, playTime) ::
Nil

val umpire = Umpire("_umpire_", players, table)

}

package apps

import pingpong.globals.Globals._
import pingpong.elements.Ping

object PingPongApp {
def main(args: Array[String]) = {
umpire.work()
for(player <- players) {
player.work()
}
table.put(Ping)
}
}




PingPong


The sealed abstract class PingPong and the case objects implementing it represent the data that drive the application.






package pingpong.elements

sealed abstract class PingPong
case object Ping extends PingPong
case object Pong extends PingPong
case object Over extends PingPong
case object Done extends PingPong




Table


The case class Table represents the pingpong table.






package pingpong.table

import space.Space

import pingpong.elements.PingPong

case class Table() extends Space[PingPong]




Player


The Player abstract class and its implementation case classes Pinger and Ponger represent the pingpong players.
A pinger can either ping, work with a Pong from a ponger which can either succeed or fail, or stop, work with a Done from the umpire.
A ponger can either pong, work with a Ping from a pinger which can either succeed or fail, or stop, work with a Done from the umpire.






package pingpong.player

import worker.Worker

import pingpong.table.Table
import pingpong.elements.PingPong
import pingpong.elements.Pong
import pingpong.elements.Ping
import pingpong.elements.Over
import pingpong.elements.Done

abstract class Player(name: String, table: Table, playTime: Long)
extends Worker[PingPong](table) {
override def toString: String = name
}


case class Pinger(name: String, table: Table, playTime: Long)
extends Player(name, table, playTime) {
def work() = table.reg {
case Pong =>
if (scala.math.random < 0.95) {
Thread.sleep(playTime)
println(this + " ping")
table.put(Ping)
} else {
Thread.sleep(playTime)
println(this + " over")
table.put(Over)
}
work()
case Done =>
println(this + " stop")
}
}

case class Ponger(name: String, table: Table, playTime: Long)
extends Player(name, table, playTime) {
def work() = table.reg {
case Ping =>
if (scala.math.random < 0.95) {
Thread.sleep(playTime)
println(this + " pong")
table.put(Pong)
} else {
Thread.sleep(playTime)
println(this + " over")
table.put(Over)
}
work()
case Done =>
println(this + " stop")
}
}




Umpire


The Umpire case class represents the pingpong umpire.
The umpire can stop the pingpong game, work with an Over from a pinger or a ponger.






package pingpong.umpire

import worker.Worker

import pingpong.elements.PingPong
import pingpong.elements.Over
import pingpong.elements.Done
import pingpong.table.Table
import pingpong.player.Player

case class Umpire(name: String, players: List[Player], table: Table)
extends Worker[PingPong](table) {
override def toString = name

def work() = table.reg {
case Over =>
println(this + " done")
for { _ <- players } table.put(Done)
println(this + " stop")
}
}




Running the PingPongApp


Running the pingpong app produces something like.






ponger_1 pong
pinger_1 ping
ponger_2 pong
pinger_2 ping
ponger_1 pong
pinger_1 ping
ponger_2 pong
pinger_2 ping
ponger_1 pong
pinger_1 ping
ponger_2 pong
pinger_2 ping
ponger_1 pong
pinger_1 ping
ponger_2 pong
pinger_2 ping
ponger_1 pong
pinger_1 ping
ponger_2 pong
pinger_2 ping
ponger_1 pong
pinger_1 ping
ponger_2 pong
pinger_2 ping
ponger_1 pong
pinger_1 ping
ponger_2 pong
pinger_2 ping
ponger_1 pong
pinger_1 over
_umpire_ done
ponger_2 stop
pinger_2 stop
ponger_1 stop
_umpire_ stop
pinger_1 stop




DiningApp


We are ready for the second application DiningApp.
Details will be explained in what follows: for now, just admire it's simplicity.






package dining.globals

import scala.math.random
import scala.math.round
import dining.table.Table
import dining.elements.ChopStick
import dining.philosopher.Philosopher

object Globals {
val size = 5

val table = Table()
(1 to size).foreach(i => table.put(ChopStick(i)))

private def thinkTime = round(5000 * random)
private def eatTime = round(3000 * random)
private def delayTime = round(1000 * random)

val philosophers =
for (i <- 1 to size)
yield new Philosopher(i, thinkTime, eatTime, delayTime, table)

def startTime = round(1000 * random)
}

package apps

import dining.globals.Globals.philosophers
import dining.globals.Globals.startTime

object DiningApp {
def main(args: Array[String]): Unit = {
for (philosopher <- philosophers) {
Thread.sleep(startTime)
philosopher.work()
}
}
}




ChopStick


The case class ChopStick represents the data that drive the application.






package dining.elements

case class ChopStick(i: Int) {
override def toString = "cs" + i
}




Table


The case class Table represents the dining table.






package dining.table

import space.Space

import dining.elements.ChopStick

case class Table() extends Space[ChopStick]




Philosopher


The Philosopher case class represents a dining philosopher.
A philosopher can eat, work with a ChopStick(`left`) and a ChopStick(`right`).






package dining.philosopher

import worker.Worker

import dining.globals.Globals.size
import dining.table.Table
import dining.elements.ChopStick

class Philosopher(i: Int, thinkTime: Long, eatTime: Long, delayTime: Long, table: Table)
extends Worker[ChopStick](table) {
private val left = i
private val right = if (i != size) { i + 1 } else { (i + 1) % size }
private var hasLeft = false
private var hasRight = false

override def toString = {
var string = "ph" + i + "("
if (hasLeft) { string += "cs" + left }
string += ","
if (hasRight) { string += "cs" + right }
string += ")"
string
}

def work() = table.reg {
case ChopStick(`left`) => {
hasLeft = true
Thread.sleep(delayTime)
table.reg {
case ChopStick(`right`) => {
hasRight = true
println(this + " starts eating [ table = " + table + " ]")
Thread.sleep(eatTime)
println(this + " stops eating [ table = " + table + " ]")
table.put(ChopStick(left))
hasLeft = false
table.put(ChopStick(right))
hasRight = false
Thread.sleep(thinkTime)
work()
}
}
}
}
}




Running the DiningApp


Running the dining app produces something like.






ph1(cs1,cs2) starts eating [ table = (Queue(cs3, cs4, cs5)) ]
ph1(cs1,cs2) stops eating [ table = (Queue(cs3, cs4, cs5)) ]
ph3(cs3,cs4) starts eating [ table = (Queue(cs5, cs1)) ]
ph3(cs3,cs4) stops eating [ table = (Queue(cs1)) ]
ph2(cs2,cs3) starts eating [ table = (Queue(cs1)) ]
ph5(cs5,cs1) starts eating [ table = (Queue()) ]
ph2(cs2,cs3) stops eating [ table = (Queue()) ]
ph5(cs5,cs1) stops eating [ table = (Queue(cs2)) ]
ph4(cs4,cs5) starts eating [ table = (Queue(cs2)) ]
ph1(cs1,cs2) starts eating [ table = (Queue()) ]
ph1(cs1,cs2) stops eating [ table = (Queue()) ]
ph4(cs4,cs5) stops eating [ table = (Queue(cs1, cs2)) ]
ph3(cs3,cs4) starts eating [ table = (Queue(cs1, cs2, cs5)) ]
ph3(cs3,cs4) stops eating [ table = (Queue(cs1, cs5)) ]
ph2(cs2,cs3) starts eating [ table = (Queue(cs1, cs5)) ]
ph4(cs4,cs5) starts eating [ table = (Queue(cs1)) ]
ph2(cs2,cs3) stops eating [ table = (Queue()) ]
ph1(cs1,cs2) starts eating [ table = (Queue()) ]
ph4(cs4,cs5) stops eating [ table = (Queue()) ]
ph3(cs3,cs4) starts eating [ table = (Queue()) ]
ph1(cs1,cs2) stops eating [ table = (Queue()) ]
ph5(cs5,cs1) starts eating [ table = (Queue(cs2)) ]
ph3(cs3,cs4) stops eating [ table = (Queue(cs2)) ]
ph5(cs5,cs1) stops eating [ table = (Queue(cs2, cs3)) ]
ph4(cs4,cs5) starts eating [ table = (Queue(cs2, cs3, cs1)) ]
ph4(cs4,cs5) stops eating [ table = (Queue()) ]
ph3(cs3,cs4) starts eating [ table = (Queue(cs5)) ]
ph3(cs3,cs4) stops eating [ table = (Queue(cs5)) ]
ph2(cs2,cs3) starts eating [ table = (Queue(cs5, cs4)) ]
...





It is instructive to play around with delay, eat and think times.


















No comments:

Post a Comment