diff --git a/.gitignore b/.gitignore index 45fe24daba..c4a1e0a3be 100755 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *~ *# +src_managed project/plugins/project/ project/boot/* */project/build/target diff --git a/akka-samples/akka-sample-ants/README.md b/akka-samples/akka-sample-ants/README.md new file mode 100644 index 0000000000..2e55246a5c --- /dev/null +++ b/akka-samples/akka-sample-ants/README.md @@ -0,0 +1,45 @@ +Ants +==== + +Ants is written by Peter Vlugter. + +Ants is based on the Clojure [ants simulation][ants.clj] by Rich Hickey, and ported to Scala using [Akka][akka] and [Spde][spde]. + +[ants.clj]:http://clojure.googlegroups.com/web/ants.clj +[akka]:http://akkasource.org +[spde]:http://technically.us/spde/ + + +Requirements +------------ + +To build and run Ants you need [Simple Build Tool][sbt] (sbt). + +[sbt]: http://code.google.com/p/simple-build-tool/ + + +Running +------- + +First time, 'sbt update' to get dependencies, then to run Ants use 'sbt run'. +Here is an example. First type 'sbt' to start SBT interactively, the run 'update' and 'run': + +% sbt +> update +> run + + +Notice +------ + +Ants is based on the Clojure ants simulation by Rich Hickey. + +Copyright (c) Rich Hickey. All rights reserved. +The use and distribution terms for this software are covered by the +Common Public License 1.0 ([http://opensource.org/licenses/cpl1.0.php][cpl]) +which can be found in the file cpl.txt at the root of this distribution. +By using this software in any fashion, you are agreeing to be bound by +the terms of this license. +You must not remove this notice, or any other, from this software. + +[cpl]: http://opensource.org/licenses/cpl1.0.php \ No newline at end of file diff --git a/akka-samples/akka-sample-ants/src/main/resources/akka.conf b/akka-samples/akka-sample-ants/src/main/resources/akka.conf new file mode 100644 index 0000000000..bcd5a09255 --- /dev/null +++ b/akka-samples/akka-sample-ants/src/main/resources/akka.conf @@ -0,0 +1,20 @@ + + level = "off" + console = off + + + + version = "0.9" + + + timeout = 5000 + serialize-messages = off + + + + service = on + fair = on + max-nr-of-retries = 10 + timeout = 1000 + + diff --git a/akka-samples/akka-sample-ants/src/main/scala/Ants.scala b/akka-samples/akka-sample-ants/src/main/scala/Ants.scala new file mode 100644 index 0000000000..85757f25e8 --- /dev/null +++ b/akka-samples/akka-sample-ants/src/main/scala/Ants.scala @@ -0,0 +1,207 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ + +package sample.ants + +import java.util.concurrent.TimeUnit +import scala.util.Random.{nextInt => randomInt} +import se.scalablesolutions.akka +import akka.actor.{ActorRef, Transactor, Scheduler} +import akka.actor.Actor.actorOf +import akka.stm.{Vector => _, _} +import akka.stm.Ref.Ref +import akka.stm.Transaction.Local._ + +object Config { + val Dim = 80 // dimensions of square world + val AntsSqrt = 7 // number of ants = AntsSqrt^2 + val FoodPlaces = 35 // number of places with food + val FoodRange = 100 // range of amount of food at a place + val PherScale = 10 // scale factor for pheromone drawing + val AntMillis = 100 // how often an ant behaves (milliseconds) + val EvapMillis = 1000 // how often pheromone evaporation occurs (milliseconds) + val EvapRate = 0.99f // pheromone evaporation rate + val StartDelay = 1000 // delay before everything kicks off (milliseconds) +} + +case class Ant(dir: Int, food: Boolean = false) { + def turn(i: Int) = copy(dir = Util.dirBound(dir + i)) + def turnAround = turn(4) + def pickUp = copy(food = true) + def dropOff = copy(food = false) +} + +case class Cell(food: Int = 0, pher: Float = 0, ant: Option[Ant] = None, home: Boolean = false) { + def addFood(i: Int) = copy(food = food + i) + def addPher(x: Float) = copy(pher = pher + x) + def alterPher(f: Float => Float) = copy(pher = f(pher)) + def putAnt(antOpt: Option[Ant]) = copy(ant = antOpt) + def makeHome = copy(home = true) +} + +object EmptyCell extends Cell + +class Place(initCell: Cell = EmptyCell) extends Ref(Some(initCell)) { + def cell: Cell = get.get + def food: Int = cell.food + def food(i: Int) = alter(_.addFood(i)) + def hasFood = food > 0 + def pher: Float = cell.pher + def pher(f: Float => Float) = alter(_.alterPher(f)) + def trail = alter(_.addPher(1)) + def ant: Option[Ant] = cell.ant + def ant(f: Ant => Ant): Cell = alter(_.putAnt(ant map f)) + def enter(antOpt: Option[Ant]): Cell = alter(_.putAnt(antOpt)) + def enter(ant: Ant): Cell = enter(Some(ant)) + def leave = enter(None) + def occupied: Boolean = ant.isDefined + def makeHome = alter(_.makeHome) + def home: Boolean = cell.home +} + +object World { + import Config._ + + val homeOff = Dim / 4 + lazy val places = Vector.fill(Dim, Dim)(new Place) + lazy val ants = setup + lazy val evaporator = actorOf[Evaporator].start + + def place(loc: (Int, Int)) = places(loc._1)(loc._2) + + private def setup = atomic { + for (i <- 1 to FoodPlaces) { + place(randomInt(Dim), randomInt(Dim)) food (randomInt(FoodRange)) + } + val homeRange = homeOff until (AntsSqrt + homeOff) + for (x <- homeRange; y <- homeRange) yield { + place(x, y).makeHome + place(x, y) enter Ant(randomInt(8)) + actorOf(new AntActor(x, y)).start + } + } + + def start = { + ants foreach (pingEvery(_, AntMillis)) + pingEvery(evaporator, EvapMillis) + } + + private def pingEvery(actor: ActorRef, millis: Long) = + Scheduler.schedule(actor, "ping", Config.StartDelay, millis, TimeUnit.MILLISECONDS) +} + +object Util { + import Config._ + + def bound(b: Int, n: Int) = { + val x = n % b + if (x < 0) x + b else x + } + + def dirBound(n: Int) = bound(8, n) + def dimBound(n: Int) = bound(Dim, n) + + val dirDelta = Map(0 -> (0, -1), 1 -> (1, -1), 2 -> (1, 0), 3 -> (1, 1), + 4 -> (0, 1), 5 -> (-1, 1), 6 -> (-1, 0), 7 -> (-1, -1)) + def deltaLoc(x: Int, y: Int, dir: Int) = { + val (dx, dy) = dirDelta(dirBound(dir)) + (dimBound(x + dx), dimBound(y + dy)) + } + + def rankBy[A, B: Ordering](xs: Seq[A], f: A => B) = Map(xs.sortBy(f).zip(Stream from 1): _*) + + def roulette(slices: Seq[Int]) = { + val total = slices.sum + val r = randomInt(total) + var i, sum = 0 + while ((sum + slices(i)) <= r) { + sum += slices(i) + i += 1 + } + i + } +} + +trait WorldActor extends Transactor { + def act + def receive = { case "ping" => act } +} + +class AntActor(initLoc: (Int, Int)) extends WorldActor { + import World._ + import Util._ + + val locRef = Ref(initLoc) + val homing = (p: Place) => p.pher + (100 * (if (p.home) 0 else 1)) + val foraging = (p: Place) => p.pher + p.food + + def loc = locRef.get.getOrElse(initLoc) + def newLoc(l: (Int, Int)) = locRef swap l + + def act = { + val (x, y) = loc + val current = place(x, y) + for (ant <- current.ant) { + val ahead = place(deltaLoc(x, y, ant.dir)) + if (ant.food) { // homing + if (current.home) dropFood + else if (ahead.home && !ahead.occupied) move + else random(homing) + } else { // foraging + if (!current.home && current.hasFood) pickUpFood + else if (!ahead.home && ahead.hasFood && !ahead.occupied) move + else random(foraging) + } + } + } + + def move = { + val (x, y) = loc + val from = place(x, y) + for (ant <- from.ant) { + val toLoc = deltaLoc(x, y, ant.dir) + val to = place(toLoc) + to enter ant + from.leave + if (!from.home) from.trail + newLoc(toLoc) + } + } + + def pickUpFood = { + val current = place(loc) + current food -1 + current ant (_.pickUp.turnAround) + } + + def dropFood = { + val current = place(loc) + current food +1 + current ant (_.dropOff.turnAround) + } + + def random[A: Ordering](ranking: Place => A) = { + val (x, y) = loc + val current = place(x, y) + for (ant <- current.ant) { + val delta = (turn: Int) => place(deltaLoc(x, y, ant.dir + turn)) + val ahead = delta(0) + val aheadLeft = delta(-1) + val aheadRight = delta(+1) + val locations = Seq(ahead, aheadLeft, aheadRight) + val ranks = rankBy(locations, ranking) + val ranked = Seq(ranks(aheadLeft), (if (ahead.occupied) 0 else ranks(ahead)), ranks(aheadRight)) + val dir = roulette(ranked) - 1 + if (dir == 0) move + else current ant (_.turn(dir)) + } + } +} + +class Evaporator extends WorldActor { + import Config._ + import World._ + val evaporate = (pher: Float) => pher * EvapRate + def act = for (x <- 0 until Dim; y <- 0 until Dim) place(x, y) pher evaporate +} diff --git a/akka-samples/akka-sample-ants/src/main/spde/Ants.spde b/akka-samples/akka-sample-ants/src/main/spde/Ants.spde new file mode 100644 index 0000000000..1d5bf4c39f --- /dev/null +++ b/akka-samples/akka-sample-ants/src/main/spde/Ants.spde @@ -0,0 +1,46 @@ +import sample.ants._ +import sample.ants.Config._ +import se.scalablesolutions.akka.stm.Transaction.Local._ + +val scale = 5 + +size(Dim * scale, Dim * scale) +smooth() + +override def setup() { + background(255) + World.start +} + +def draw() { + for (x <- 0 until Dim; y <- 0 until Dim) { + val cell = atomic { World.place(x, y).cell } + val (rx, ry, rw, rh) = (x * scale, y * scale, scale, scale) + noStroke() + fill(255) + rect(rx, ry, rw, rh) + if (cell.pher > 0) fill(0, 255, 0, cell.pher * PherScale) + if (cell.food > 0) fill(255, 0, 0, 255 * (cell.food / FoodRange.floatValue)) + rect(rx, ry, rw, rh) + for (ant <- cell.ant) { + if (ant.food) stroke(255, 0, 0) else stroke(0) + val (hx, hy, tx, ty) = antLine(ant.dir) + line(rx + hx, ry + hy, rx + tx, ry + ty) + } + stroke(0, 0, 255) + noFill() + val homeStart = World.homeOff * scale + val homeWidth = AntsSqrt * scale + rect(homeStart, homeStart, homeWidth, homeWidth) + } +} + +val s = scale - 1 +val m = s / 2 + +def antLine(dir: Int) = dir match { + case 0|4 => (m, 0, m, s) + case 1|5 => (s, 0, 0, s) + case 2|6 => (s, m, 0, m) + case _ => (s, s, 0, 0) +} diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index abd0b2fce8..b484a90bf5 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -4,6 +4,7 @@ import sbt._ import sbt.CompileOrder._ +import spde._ import java.util.jar.Attributes import java.util.jar.Attributes.Name._ @@ -198,7 +199,7 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) { } class AkkaRedisProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) { - val redis = "com.redis" % "redisclient" % "2.8.0.RC2-1.4-SNAPSHOT" % "compile" + val redis = "com.redis" % "redisclient" % "2.8.0.Beta1-1.3" % "compile" override def testOptions = TestFilter((name: String) => name.endsWith("Test")) :: Nil } @@ -249,8 +250,9 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) { //val atomikos_transactions_util = "com.atomikos" % "transactions-util" % "3.2.3" % "compile" val jta_spec = "org.apache.geronimo.specs" % "geronimo-jta_1.1_spec" % "1.1.1" % "compile" } + + // ================= TESTS ================== - // examples class AkkaFunTestProject(info: ProjectInfo) extends DefaultProject(info) { val jackson_core_asl = "org.codehaus.jackson" % "jackson-core-asl" % "1.2.1" % "compile" val stax_api = "javax.xml.stream" % "stax-api" % "1.0-2" % "compile" @@ -264,7 +266,15 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) { val jmock = "org.jmock" % "jmock" % "2.4.0" % "test" } + // ================= EXAMPLES ================== + + class AkkaSampleAntsProject(info: ProjectInfo) extends DefaultSpdeProject(info) { + val scalaToolsSnapshots = ScalaToolsSnapshots + override def spdeSourcePath = mainSourcePath / "spde" + } + class AkkaSampleChatProject(info: ProjectInfo) extends AkkaDefaultProject(info, deployPath) + class AkkaSamplePubSubProject(info: ProjectInfo) extends AkkaDefaultProject(info, deployPath) class AkkaSampleLiftProject(info: ProjectInfo) extends AkkaDefaultProject(info, deployPath) { @@ -300,6 +310,8 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) { } class AkkaSamplesParentProject(info: ProjectInfo) extends ParentProject(info) { + lazy val akka_sample_ants = project("akka-sample-ants", "akka-sample-ants", + new AkkaSampleAntsProject(_), akka_core) lazy val akka_sample_chat = project("akka-sample-chat", "akka-sample-chat", new AkkaSampleChatProject(_), akka_kernel) lazy val akka_sample_pubsub = project("akka-sample-pubsub", "akka-sample-pubsub", @@ -351,14 +363,17 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) { def deployPath: Path lazy val dist = distAction + def distAction = deployTask(jarPath, packageDocsJar, packageSrcJar, deployPath, true, true, true) dependsOn( `package`, packageDocs, packageSrc) describedAs("Deploying") + def deployTask(jar: Path, docs: Path, src: Path, toDir: Path, genJar: Boolean, genDocs: Boolean, genSource: Boolean) = task { gen(jar, toDir, genJar, "Deploying bits") orElse gen(docs, toDir, genDocs, "Deploying docs") orElse gen(src, toDir, genSource, "Deploying sources") } + private def gen(jar: Path, toDir: Path, flag: Boolean, msg: String): Option[String] = if (flag) { log.info(msg + " " + jar) diff --git a/project/plugins/Plugins.scala b/project/plugins/Plugins.scala index 4b21ea189b..f36c65ed13 100644 --- a/project/plugins/Plugins.scala +++ b/project/plugins/Plugins.scala @@ -1,6 +1,8 @@ import sbt._ class Plugins(info: ProjectInfo) extends PluginDefinition(info) { + val databinderRepo = "Databinder Repository" at "http://databinder.net/repo" + val spdeSbt = "us.technically.spde" % "spde-sbt-plugin" % "0.4.1" // val repo = "GH-pages repo" at "http://mpeltonen.github.com/maven/" // val idea = "com.github.mpeltonen" % "sbt-idea-plugin" % "0.1-SNAPSHOT" } \ No newline at end of file