Added akka-sample-ants as a sample showcasing STM and Transactors

This commit is contained in:
Jonas Bonér 2010-05-24 15:53:17 +02:00
parent 3aaaf94a7f
commit d51c82047c
7 changed files with 338 additions and 2 deletions

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
*~ *~
*# *#
src_managed
project/plugins/project/ project/plugins/project/
project/boot/* project/boot/*
*/project/build/target */project/build/target

View file

@ -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

View file

@ -0,0 +1,20 @@
<log>
level = "off"
console = off
</log>
<akka>
version = "0.9"
<actor>
timeout = 5000
serialize-messages = off
</actor>
<stm>
service = on
fair = on
max-nr-of-retries = 10
timeout = 1000
</stm>
</akka>

View file

@ -0,0 +1,207 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
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
}

View file

@ -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)
}

View file

@ -4,6 +4,7 @@
import sbt._ import sbt._
import sbt.CompileOrder._ import sbt.CompileOrder._
import spde._
import java.util.jar.Attributes import java.util.jar.Attributes
import java.util.jar.Attributes.Name._ 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) { 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 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 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" 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) { class AkkaFunTestProject(info: ProjectInfo) extends DefaultProject(info) {
val jackson_core_asl = "org.codehaus.jackson" % "jackson-core-asl" % "1.2.1" % "compile" 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" 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" 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 AkkaSampleChatProject(info: ProjectInfo) extends AkkaDefaultProject(info, deployPath)
class AkkaSamplePubSubProject(info: ProjectInfo) extends AkkaDefaultProject(info, deployPath) class AkkaSamplePubSubProject(info: ProjectInfo) extends AkkaDefaultProject(info, deployPath)
class AkkaSampleLiftProject(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) { 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", lazy val akka_sample_chat = project("akka-sample-chat", "akka-sample-chat",
new AkkaSampleChatProject(_), akka_kernel) new AkkaSampleChatProject(_), akka_kernel)
lazy val akka_sample_pubsub = project("akka-sample-pubsub", "akka-sample-pubsub", 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 def deployPath: Path
lazy val dist = distAction lazy val dist = distAction
def distAction = deployTask(jarPath, packageDocsJar, packageSrcJar, deployPath, true, true, true) dependsOn( def distAction = deployTask(jarPath, packageDocsJar, packageSrcJar, deployPath, true, true, true) dependsOn(
`package`, packageDocs, packageSrc) describedAs("Deploying") `package`, packageDocs, packageSrc) describedAs("Deploying")
def deployTask(jar: Path, docs: Path, src: Path, toDir: Path, def deployTask(jar: Path, docs: Path, src: Path, toDir: Path,
genJar: Boolean, genDocs: Boolean, genSource: Boolean) = task { genJar: Boolean, genDocs: Boolean, genSource: Boolean) = task {
gen(jar, toDir, genJar, "Deploying bits") orElse gen(jar, toDir, genJar, "Deploying bits") orElse
gen(docs, toDir, genDocs, "Deploying docs") orElse gen(docs, toDir, genDocs, "Deploying docs") orElse
gen(src, toDir, genSource, "Deploying sources") gen(src, toDir, genSource, "Deploying sources")
} }
private def gen(jar: Path, toDir: Path, flag: Boolean, msg: String): Option[String] = private def gen(jar: Path, toDir: Path, flag: Boolean, msg: String): Option[String] =
if (flag) { if (flag) {
log.info(msg + " " + jar) log.info(msg + " " + jar)

View file

@ -1,6 +1,8 @@
import sbt._ import sbt._
class Plugins(info: ProjectInfo) extends PluginDefinition(info) { 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 repo = "GH-pages repo" at "http://mpeltonen.github.com/maven/"
// val idea = "com.github.mpeltonen" % "sbt-idea-plugin" % "0.1-SNAPSHOT" // val idea = "com.github.mpeltonen" % "sbt-idea-plugin" % "0.1-SNAPSHOT"
} }