模拟场景渲染过程中驱动actor和绘图actor的信息交互过程

本例将表示几何图形的一组类的实例发送给actor,该actor将这组实例绘制到显示屏上。像是渲染工厂(rendering farm)在为动画生成场景。一旦场景渲染完毕,构成的场景的几何图形便会被发送给某一actor进行展示。


In [1]:
case class Point(x: Double = 0.0, y: Double = 0.0)

abstract class Shape() {
    // draw方法接受一个函数参数
    // 每个图形对象都会将自己的字符格式传给函数f,由f完成绘制工作
    def draw(f: String => Unit): Unit = f(s"draw: ${this.toString}")
}

case class Circle(center: Point, radius: Double) extends Shape
case class Rectangle(lowerLeft: Point, height: Double, width: Double) extends Shape
case class Triangle(point1: Point, point2: Point, point3: Point) extends Shape


defined class Point
defined class Shape
defined class Circle
defined class Rectangle
defined class Triangle

样本类(case class)让编译器自动生成很多方法,比如String、equals和hashCode方法。编译器还会为样本类同时生成一个伴生对象。


In [2]:
// Scala调用生成的equals方法来判断==这样的逻辑比较
val p20 = new Point(2.0)
val p20b = new Point(2.0)
val p02 = new Point(y = 2.0)


p20: Point = Point(2.0, 0.0)
p20b: Point = Point(2.0, 0.0)
p02: Point = Point(0.0, 2.0)

In [3]:
p20 == p02


res2: Boolean = false

In [4]:
p20 == p20b


res3: Boolean = true

In [5]:
// 使用Point伴生类的apply方法

val p1 = Point.apply(1.0, 2.0)


p1: Point = Point(1.0, 2.0)

Point.apply是构造Point对象的工厂方法,调用该方法就好像不通过new关键字调用Point的构造器一样。

object Point {
  def apply(x: Double = 0.0, y: Double = 0.0) = new Point(x, y)
  ...
}

以上定义好了形状类型,接下来回到actor上


In [6]:
object Messages {
    object Exit
    object Finished
    case class Response(message: String)
}


defined object Messages

In [7]:
load.jar("D:\\scala\\lib\\akka-actor_2.11-2.3.10.jar")




In [8]:
import akka.actor.Actor


import akka.actor.Actor

此处定义了一个actor类,用于绘制图形。实现了抽象方法Actor.receive,该方法是Actor的子类必须实现的方法,定义了如何处理接收到的消息。


In [9]:
class ShapesDrawingActor extends Actor {
    import Messages._
    
    def receive = {
        case s: Shape =>
            s.draw(str => println(s"ShapesDrawingActor: $str"))
            sender ! Response(s"ShapesDrawingActor: $s drawn")
        case Exit =>
            println(s"ShapesDrawingActor: exiting...")
            sender ! Finished
        case unexpected => //default. Equivalent to "unexpected: Any"
            val response = Response(s"Error: Unknown message: $unexpected")
            println(s"ShapesDrawingActor: $response")
            sender ! response
    }
}


defined class ShapesDrawingActor

receive是偏函数,接受单一的Any类型参数并返回Unit值。由于该函数返回Unit对象,因此函数体一定会产生副作用。由于actor系统采用了异步消息机制,它必须依靠副作用。通常由于传递消息后无法返回任何消息,代码块中便会发送一些其他消息,包括给发送者的返回消息。

偏函数中仅包含了一些case子句,对传递给函数的消息执行模型匹配。receive方法会尝试将接受到的各条消息与编写的模式匹配表达式进行匹配,并执行最先被匹配上的表达式。

Actor.sender函数返回了actor发送消息接收方的对象引用,而!方法用于发送异步消息。


In [10]:
import akka.actor.{Props, ActorRef, ActorSystem}


import akka.actor.{Props, ActorRef, ActorSystem}

In [11]:
object Start


defined object Start

In [21]:
class ShapesDrawingDriver(drawerActor: ActorRef) extends Actor {
    import Messages._
    
    def receive = {
        case Start =>
            drawerActor ! Circle(Point(0.0, 0.0), 1.0)
            drawerActor ! Rectangle(Point(0.0, 0.0), 2, 5)
            drawerActor ! 3.14159
            drawerActor ! Triangle(Point(0.0, 0.0), Point(2.0, 0.0), Point(1.0, 2.0))
            drawerActor ! Exit
        case Finished =>
            println(s"ShapesDrawingDriver: cleaning up...")
            context.system.shutdown()
        case response: Response =>
            println("ShapesDrawingDriver: Response =" + response)
        case unexpected =>
            println("ShapesDrawingDriver: ERROR: Receive an unexpected message =" + unexpected)
    }
}


defined class ShapesDrawingDriver

接下来是驱动应用的主方法,首先构建一个akka.actor.ActorSystem对象和两个actor对象,然后将ShapesDrawingActor对象的引用传递给ShapesDrawingDriver,最后向驱动对象发送Start命令,启动应用。


In [22]:
object ShapesDrawingApp extends App{
    val system = ActorSystem("DrawingActorSystem")
    val drawer = system.actorOf(Props(new ShapesDrawingActor), "drawingActor")
    val driver = system.actorOf(Props(new ShapesDrawingDriver(drawer)), "drawingService")
    driver ! Start
    system.shutdown
}


defined object ShapesDrawingApp

最后结果如图: