ラベル Scala の投稿を表示しています。 すべての投稿を表示
ラベル Scala の投稿を表示しています。 すべての投稿を表示

2015年8月13日木曜日

[Scala] FutureとスレッドプールとAkka

僕は、実は最近までFutureについてよく分かってなかったんです。単に別のスレッドで処理をして値を返すものという認識ぐらいしかありませんでした。Future.applymapflatMapもあまり気にせず使っていました。コンパイル時にExecutionContextがないって怒られたら、コンパイラの指定通りにimport scala.concurrent.ExecutionContext.Implicits.globalをソースコードに盲目的に追加するということもやっちゃってました。とりあえず、コンパイラは通るしプログラムも動くし、いいっしょ!っていう。でも、よく分からないまま使っていて、問題が起こった時に困るのは自分です。もしかしたら、周りの人にも迷惑がかかるかもしれません。反省。
ということで、今回は、Future周りのことについて調べたことをまとめます。

ExecutionContextってなに?
暗黙的にimport scala.concurrent.ExecutionContext.Implicits.globalを記述し、Futureを使っていたんですが、Future.applyにもmapにもflatMapにもExecutionContextを渡さないといけないんです。scala.concurrent.ExecutionContext.Implicits.globalってのは、Scalaがデフォルトで用意しているimplicit宣言されているExecutionContextなんですね。だからこれをimportすると暗黙的にFuture.applyにもmapにもflatMapにも渡されることになります。では、このExecutionContextってなんなのよ?っていう話です。これは、簡単に言うと、スレッドプールです。つまり、Futureで処理されることは、Futureを生成するときに渡されたスレッドプールが管理しているスレッド内で実行されるんです。

scala.concurrent.ExecutionContext.Implicits.globalってなに?
scala.concurrent.ExecutionContext.Implicits.globalってのは、Scalaがデフォルトで用意しているスレッドプールというのは、先に述べましたが、それは一体どんなスレッドプールなのか?
とりあえず、知っておくべきことは、このスレッドプールが管理しているスレッドの数は、プロセッサ(コア)の数と同じだということです。例えば、4つしかコアがなくてFutureを4つ作った場合は、スレッドプールのスレッドを全て専有しちゃうので、別にFutureを作っても、その処理は動かないんです。なので、Future内で時間のかかる処理とか、Thread.sleepとかしないほうがいいんです。時間のかからない処理がいっぱいあるようなプログラムだったら、あまり問題になることはないんじゃないでしょうか?

ExecutorServiceを使う
もちろん、他のスレッドプールを使うこともできます。デフォルト以外で簡単なものとしては、JavaExecutorsを使ってスレッドプールを生成するのが簡単だと思います。例えば、固定数のスレッドを持ったスレッドプールを作る場合には、次のようにします。
import scala.concurrent.ExecutionContext
import java.util.concurrent.Executors
implicit val executionContext = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(10))
JavaExecutorServiceは、比較的に時間のかかる処理などに適しているようなので、状況に応じて使いわけるとよいでしょう。

Akkaのスレッドプールを使う
Actorのフレームワークを提供しているAkkaのスレッドプールを使用することもできます。Akkaは、設定ファイルでスレッドプールの設定をすることができるので、設定が容易です。ここでは、比較的に時間のかからない処理をいっぱい行うのに適しているFork/Joinフレームワークを使ったスレッドプールの設定を行います。
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
# sample-akka.conf
akka {
  actor {
    # デフォルトのスレッドプールの設定。
    default-dispatcher {
       # スレッドプールの仕組みの設定。
       # デフォルトでは、"fork-join-executor"なので、ここの設定を省略してもよい。
       executor = "fork-join-executor"

       # "fork-join-executor"の設定。
       # executorが"fork-join-executor"の場合に、この設定が使用される。
       fork-join-executor {
         # 最小のスレッド数
         parallelism-min = 8
         
         # プロセッサ(コア)数の何倍のスレッドを生成するか
         # parallelism-minからparallelism-maxまでの範囲を出ることはない。
         parallelism-factor = 10.0
         
         # 最大スレッド数
         parallelism-max = 64
       }
    }
  }
}
次に、この設定を使ったExecutorContextを取得します。
import com.typesafe.config.{ ConfigFactory }
import akka.actor.{ ActorSystem }
val conf = "sample-akka.conf"
val url = this.getClass.getResource( conf )
val system = ActorSystem( "ActorSystem"ConfigFactory.parseURL( url ) )
implicit val executionContext = system.dispatcher

2014年2月20日木曜日

フォトンマップのための八分木の実装


フォトンマップとは?

今までの記事では、パストレーシングを利用してレンダリングしてきましたが、一番の問題は、なんといってもレンダリングに時間がかかりすぎることです。フォトンマップを使ったレンダリングは、この問題を解決してくれます。フォトンマップとは、光源から放射されたフォトン(レイ、光線)が拡散面に衝突した位置(と入射方向や反射方向)とその衝突面の材質を格納するデータ構造です。このフォトンマップを使えば、拡散面における輝度推定が容易になります。つまり、フォトンマップを使ったレンダリング過程は、大きくわけると2段階にわけられます。第1段階は、フォトンマップの構築です。第2段階は、レイトレーシングまたはパストレーシングを使って、拡散面の輝度推定値を構築されたフォトンマップから計算してレンダリングします。

フォトンマップのデータ構造

拡散面に衝突したフォトンの情報が格納されてれば、どんなデータ構造を使おうが問題ありません。ただ、輝度推定の過程で、格納された全てのフォトンの中から目的のフォトンを効率よく見つけ出す必要があるので、この目的に合ったデータ構造を使えば、より短い時間でレンダリングできます。フォトンマップによる輝度推定では、拡散面上の対象の点に近い位置にあるフォトンを集めて輝度を計算します。このような計算に対して、フォトンを効率よく見つけるデータ構造をいろいろ考えだされているようですが、ここでは、パストレーシングに比べてかなり早くなっていれば、他のデータ構造に比べてちょっと遅いくらいは問題ないので、比較的単純な構造である八分木を実装することにします。二分木の3次元版みたいなものだから、イメージしやすいし。

八分木の仕様

八分木は空間を再帰的に8分割して、その子空間にデータを格納する木構造です。この木構造をフォトンマップとして使えるように、次のような仕様にしました。
  • 空間として直方体を使う
  • 直方体の内側に位置するフォトンは、その直方体に格納される
  • 1つの直方体はフォトンに対して容量を持っていて、フォトンを格納できる数が決まっている
  • 直方体のフォトンが容量に達したら、直方体を8分割し、該当する子の直方体にフォトンを格納する

八分木の実装

八分木クラスの主要なメソッドを概観します。

コンストラクタ

コンストラクタでは、直方体を定義します。
  abstract class Octree[B, C <: Octree[B, C]](
    val xmin: Float, val xmax: Float,
    val ymin: Float, val ymax: Float,
    val zmin: Float, val zmax: Float,
    val capacity: Int,
    val parent: Option[C]
  )
型パラメータBは、格納するデータの型です。型パラメータCは、サブクラスの型です。子の直方体を生成する際には、C型のインスタンスを生成するようにします。これは、サブクラスで定義したメンバーを使用できるようにするためです。

子を生成するメソッド

直方体を8分割して子の直方体を生成するときに使用されるメソッドです。コンストラクタと同じ引数を取ります。サブクラスで実装します。
    protected def create(
      xmin: Float, xmax: Float,
      ymin: Float, ymax: Float,
      zmin: Float, zmax: Float,
      capacity: Int,
      parent: Option[C]
    ): C
戻り値の型は、上述したようにサブクラスの型です。

フォトンの追加

フォトンを追加するメソッドです。既に最大容量に達していれば、子を作成し、子に格納する。
10 
11 
    def add( a: Vector3, b: B )(implicit tag: ClassTag[C]): Unit = if( ndata < capacity ) {
      _data = (a, b) :: _data
      ndata += 1
      onUpdate( b )
    } else {
      createChildren // 既に子を持っている場合は子を作成しない
      // このOctreeが持っているデータを子に移す
      _data.foreach { case (p, q) => findChild( p ).foreach( _.add( p, q ) ) }
      findChild( a ).foreach( _.add( a, b ) )
      _data = Nil
    }

フォトンの探索

指定した球に含まれる、または接している最下層の直方体を見つけ、クロージャに渡す。
10 
11 
    def filterChildren( b: BoundingSphere, f: (Int, C) => Unit ): Unit = boundingSphere.contains( b ) match {
      // 離れている場合
      case 3 => ()
      // bがこのOctreeを含んでいる場合
      case 2 => f( 2, this )
      // このOctreeがbを含んでいるか接している場合
      case n => _children match {
        case None => f( n, this )
        case Some( cs ) => cs.foreach { _.filterChildren( b, f ) }
      }
    }

2014年1月8日水曜日

[3DCG] Scalaでパストレーシング実装中

最近は3DCGのプログラミングをしています。Scalaを使ってます。まだまだ実装途中ですが、ここまでの結果をログとして記録しておこうと思います。

今はパストレーシングを実装中。光源から光の届き具合を調べる為に、直接光が当たる部分と間接的に光が当たる部分の画像に分けて出力しました。ピクセル毎に500パスを使用して描画しました。以下は、その結果の画像です。ちなみに、まだ色は考えてないので、白黒の画像です。輝度のみです。

直接光による寄与

各ピクセルから500本の光線を飛ばし、拡散面で1度だけ反射して光源に到達した光線を描いた画像。手前の球に光源が映っているところがおかしい。ノイズがある。光線の本数が足りないのかなぁ。


間接光による寄与

直接光の画像と同じように、各ピクセルから500本の光線を飛ばしました。違いは、2度拡散面で反射し光源に到達した光線を描いたところが違います。全体的に光が分散してる感じなので悪くないのでは!?


直接光+間接光

上の2つの画像を足しあわせた画像。うーん、いまいち。何かが足りない感じ。参考にしている本によれば、100paht/pixelくらいで結構キレイな画像生成しているだけどなぁ。


あとは、これをもうちょっと改造して色をつけてって感じです。最終的にはフォトンマップまで実装しようと思ってます。

2013年12月25日水曜日

GAE + SBT + Unfiltered + Scalate


GAEの環境でUnfilteredScalateを使ってみました。

SBTでGAEを開発するときの設定はこちらを参照。
SBTでUnfilteredの設定をするにはこちらを参照。

Unfilteredのintentメソッドの実装

まず、前回の復習から。

GAEでは、サーブレットを使って開発するので、サーブレット対応のUnfilteredであるunfiltered-filterパッケージを使用します。

unfiltered-filterを使った基本的な開発の流れ

  1. unfiltered.filter.Planを継承したフィルターを作る
  2. intentメソッドを実装する
  3. web.xmlにフィルターを登録する

unfiltered.filter.Planは、javax.servlet.Filterを継承しています。javax.servlet.Filterを継承したクラスは、doFilterメソッドを実装しないといけないんですが、それはunfiltered.filter.Planクラスで実装されているので実装する必要はありません。その代わりにintentメソッドを実装しなければいけません。

intentメソッドは、unfiltered.request.HttpRequest[HttpServletRequest]を受け取り、unfiltered.response.ResponseFunction[HttpServletResponse]を返すPartialFunctionを返すメソッドです。PartialFunctionになっているので、リクエストの処理をmatch式でやろうというわけですね。

unfiltered.request.HttpRequestは、HttpServletRequestのラッパーで、unfiltered.response.ResponseFunctionは、HttpServletResponseOutputStreamにレスポンスを書き込むトレイトです。

レスポンスを返すには、unfiltered.response.ResponseFunctionを実装したインスタンスを返さなければいけないんですが、それが面倒な人の為にちゃんと予め実装されたサブクラスが幾つか用意されいます。よく使うであろうと思われるのは、ResponseStringでしょう。これを使ってintentメソッドを実装すると、例えば次のようになります。

class Filter extends unfiltered.filter.Plan {
  def intent = {
    case Path("/index.html") => new ResponseString( "Hoge" )
  }
}

web.xmlへの登録は、前回やったので、スキップしま~す。

GAEでのUnfiltered

今回は、Scalateを使ってレスポンスを返すので、ResponseStringは使えません。なぜなら、Scalateの処理をするためには、HttpServletResponseが持っているOutputStreamインスタンスが必要だからです。少なくとも上記の例では、OutputStreamは登場してません。

そこでScalateを使うために、unfiltered.response.ResponseFunctionのサブトレイトResponseWriterを使います。このトレイトは、writeメソッドを実装する必要があるます。このwriteメソッドは、引数としてHttpServletResponseOutputStreamWriterを取るので、このメソッドの中でOutputStreamを使ってScalateの処理を行うことができます。

intentメソッド内でScalateを使う

Scalateとは

Scalateとは、テンプレートエンジンです。いろいろな書式のテンプレートに対応しています。
  • Jade
  • Mustache
  • Scaml(HamlのScala版)
  • SSP(Scala Server Page, JSPのScala版)
  • Scuery

基本的な使い方

Scalateの基本的な使い方は、とても簡単です。TemplateEngineのインスタンスを生成して、layoutメソッドを呼び出すだけです。layoutは、String型のHTMLを返します。

val engine = new TemplateEngine
val html = engine.layout("index.ssp"Map("name" -> "Hoge")List(Biding("name""String")))

layoutは、テンプレートのソースコードをコンパイルして一時領域にクラスファイルを出力します。キャッシュが効くので、テンプレートのソースコードに変更がなければ、2度目移行の呼び出しは、コンパイルは回避されて、一時領域のクライスファイルを使ってHTMLを出力します。

layoutメソッドの詳細
def layout (uri: String, attributes: Map[String, Any] = Map(), extraBindings: Traversable[Binding] = Nil): String
引数:
  • uri
  • Ssp(Scala Server Page)やScaml(HamlのScala版)のテンプレートのソースコードのパス。
  • attributes
  • URIの場所にあるテンプレートに渡す値。複数渡せます。上記の例では、Stringの"Hoge"という値をnameという変数で渡しています。テンプレート側で使用するには、テンプレート側でnameという変数を宣言する必要があります。次の引数のextraBindingsを渡せば宣言も必要なくなります。
  • extraBindings
  • テンプレート側で暗黙的に使える変数の宣言。上記の例では、nameという変数をテンプレート側で宣言せずに使えるようになります。

ただし、GAEのような環境で使用するには、上記では上手く行きません。サーブレット環境であることとファイルの書き込みができないからです。

プリコンパイル

ファイルの書き込みができない場合は、予めコンパイルしてクラスファイルを作っておくという手があります。

Scalateでは、テンプレートのソースコードを動的にコンパイルしJavaのクラスファイルを生成します。そして、そのクラスファイルをシステムの一時領域に保存します。GAEでは、ファイルの書き込みができないので、一時的にコンパイルしたクラスファイルを保存しておくことはできません。これを避ける為に、予めテンプレートをコンパイルしておく必要があるのです。Scalateでは、プリコンパイルされていればテンプレートのソースコードを再度コンパイルすることはないのでGAEでも大丈夫です。プリコンパイルするには、SBTのプラグインxsbt-scalate-generatorを使います。

SBTの設定
project/plugins.sbtに次のコードを追加します。
addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.4.2")

build.sbtに次のコードを追記します。
10 
11 
12 
13 
14 
15 
16 
seq(scalateSettings:_*)

scalateTemplateConfig in Compile <<= (sourceDirectory in Compile){ base =>
  Seq(
    TemplateConfig(
      // テンプレートの場所
      base / "webapp" / "WEB-INF"
      // 暗黙的にテンプレートにインポートします
      List("import scala.math._")
      // 暗黙的にテンプレートに変数を宣言します
      List(Binding("userAgent""String"))
      // 出力クラスのパッケージ
      Some("webTmpl") 
    )
  )
}

プリコンパイル
特別に何もする必要はありません。sbtのcompileコマンドやpackageコマンドでwarファイル作成時に自動的にテンプレートをコンパイルしてくれます。出力先は、targetディレクトリです。

サーブレット対応

サーブレット対応したintentメソッドは次のようになります。
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
    def intent = {
      // ResponseWriterを返すクロージャ
      case req @ Path(Seg( _ :: Nil )) => new ResponseWriter {
        def write( writer: OutputStreamWriter ) = {
          val engine = new TemplateEngine
          // サーブレットの環境を設定する
          engine.resourceLoader = new ServletResourceLoader(config.getServletContext)
          // build.sbtで指定したディレクトリ
          engine.workingDirectory = new File("WEB-INF")
          // build.sbtで指定したパッケージ
          engine.packagePrefix = "webTmpl"
          // layoutテンプレートの場所の設定
          engine.layoutStrategy = new DefaultLayoutStrategy(engine, TemplateEngine.templateTypes.map("WEB-INF/layouts/default." + _):_*)
          // プリコンパイルされている場合は、URIと同じ名前のクラスファイルを読み込む。
          // そうでない場合はテンプレートをコンパイルする。
          val scalateTemplate = engine.load(req.underlying.getServletPath)
          // 描画環境を設定
          val context = new DefaultRenderContext(req.underlying.getServletPath, engine, new PrintWriter(writer))
          // テンプレートを描画
          engine.layout( scalateTemplate, context )
        }
      }
    }

ちなみに、ServletTemplateEngineServletRenderContextとうクラスもあるので、こっちを使ってもできるかもしれませんが、試してません。

テンプレートファイルの作成

では、簡単なテンプレートを作成してみましょう。今回はScamlを使います。

レイアウトテンプレート
src/main/webapp/WEB-INF/layouts/default.scaml
-@ var body: String
%html
  %head
    %title Title
    %meta(http-equiv="Content-Type" content="text/html; charset=UTF-8")
  %body
    #{unescape(body)}

レイアウトテンプレートは、全てのテンプレートに自動的に適応されます。テンプレートの内容が#{unescape(body)}の部分に置換されます。

ページのテンプレート
src/main/webapp/index.scaml
%div Hello World

この場合は、%div Hello Worldがレイアウトテンプレートの#{unescape(body)}の部分に置換されます。

コンパイル

> sbt package

サーバを起動してブラウザで確認

開発用サーバを起動:
> appengine-java-sdk-***/bin/dev_appserver.cmd sbt/target/webapp

ブラウザでhttp://localhost:8080/index.scamlに行って、動作を確認してみましょう。ブラウザにHello Worldと表示されていれば成功です。

関連記事

GAE + SBT + Unfiltered


GAEをSBTで使う記事を書きましたが、今回はリクエストの処理にUnfilteredを使ってみることにしました。

Unfilteredとは

Unfilteredとは、いろんなフレームワークや環境に使えるリクエスト処理するライブラリらしい。Servletにも対応しているので、GAEでも使えるようです。

Servletに対応しているパッケージは、unfiltered-filterのようなので、これを使ってみましょう。

build.sbtの設定

build.sbtに次の記述を追加すれば、Unfilteredを使えるようになります。

libraryDependencies += "net.databinder" %% "unfiltered-filter" % "0.7.1"

フィルターの作成

リクエストを処理するフィルターを作成してweb.xmlに登録します。

Unfilteredでは、Planというトレイトを継承してフィルターを作るようです。

src/main/scala/mypackage/Filter.scalaを作成します。

10 
11 
12 
package mypackage {

  import unfiltered.request._
  import unfiltered.response._

  class Filter extends unfiltered.filter.Plan {
    def intent = {
      case Path(Seg( x :: Nil )) => ResponseString( x )
    }
  }

}

intentというメソッドでリクエストを処理します。caseでパス名を取得して、パス名をそのままページに表示するようにしました。

詳しい使い方は、こちらを参照してください。

web.xmlの設定

作ったフィルターをweb.xmlに登録します。

10 
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
  <filter>
    <filter-name>filter</filter-name>
    <filter-class>mypackage.Filter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>filter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

とりあえず、全てのリクエストはこのフィルターを通すように設定しました。

コンパイル

SBTのインタープリタに入り、packageと入力します。

> sbt
sbt> package

サーバの起動

appengine-java-sdk-***/bin/dev_appserver.cmd sbt/target/webapp
ブラウザでhttp://localhost:8080/hogeに行って、動作を確認してみましょう。ブラウザにhogeと表示されていれば成功です。

関連記事

2013年12月20日金曜日

Google App Engine + SBTで始めました

Google App EngineをSBTで使ってみました!

GAE用のSBTのプラグインsbt-appengineがあるようなので、これを使ってもいいのですが、GAE初心者の僕としては、GAEを理解する為に、sbt-appengineを使わずにやってみました。


Google App Engine SDK for Javaのダウンロード

ここからGoogle App Engine SDK for Javaをダウンロードします。

Google App Engine SDK for Javaの解凍

ダウンロードしたGoogle App Engine SDK for Javaを好きな場所に解凍します。

SBTのダウンロード

ここからZIPまたはTGZ版をダウンロードします。

SBTの解凍

ダウンロードしたSBTを好きな場所に解凍します。

xsbt-web-plugin

GAEはWARファイルを使ってデプロイするようなので、sbt-appengineは使いませんが、WARファイルを作る為のプラグインxsbt-web-pluginは使います。

plugins.sbtの設定

解凍したSBTディレクトリの中にsbt/project/plugins.sbtを作成します。

addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "0.5.0")

build.sbtの作成

解凍したSBTディレクトリの中にsbt/build.sbtを作成します。

10 
11 
12 
13 
name := "****"

organization := "****"

version := "0.1.0"

scalaVersion := "2.10.3"

seq(webSettings :_*)

libraryDependencies += "org.eclipse.jetty" % "jetty-webapp" % "9.1.0.v20131115" % "container"

libraryDependencies += "org.eclipse.jetty" % "jetty-plus" % "9.1.0.v20131115" % "container"

WARファイルに組み込むライブラリ

xsbt-web-pluginで作るWARファイルに入れるライブラリをsbt/libに置きます。必要なライブラリは、appengine-java-sdk-***/lib/userの中のjar全てです。サブディレクトリの中のjarもです。だだし、sbt/libにコピーするときに、全てのjarファイルをsbt/lib直下に置く必要があります。ディレクトリ構造をそのままコピーしなように注意が必要です。

外部ライブラリ参照

WARファイルに組み込まないライブラリも使用するので、それらのライブラリを参照するようにクラスパスの設定をする必要があります。

必要な外部ライブラリは、appengine-java-sdk-***/lib/sharedの中のライブラリです。

sbt/build.sbtに次の記述を追加します。

unmanagedJars in Compile ++= {
  val base = baseDirectory.value
  val gae = base / ".." / "appengine-java-sdk-1.8.8" / "lib" / "shared"
  val jars = (gae ** "*.jar") +++ (gae / "jsp" / "*.jar")
  jars.classpath
}

web.xmlの作成

sbt/src/main/webapp/WEB-INFweb.xmlを作成します。とりあえず、空のやつを作ります。

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
</web-app>

appengine-web.xmlの作成

sbt/src/main/webapp/WEB-INFappengine-web.xmlを作成します。とりあえず、最小限の項目を記述します。

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
  <application>application-id</application>
  <version>1</version>
  <threadsafe>true</threadsafe>
</appengine-web-app>

<application>タグに、アプリケーションIDを指定するのを忘れずに。アプリケーションIDは、ここから取得できます。

<threadsafe>タグは、アプリケーションが同時に複数のリクエストを受け付けられるようになっているかを指定する項目です。1つづしかリクエスト受け付けられなければfalse、同時に複数のリクエストを処理できる場合にはtrueを指定します。

静的ファイルでHello World

sbt/src/main/webappindex.htmlを作成ます。とりあえず、Hello Worldです。

<html>
  <head>
    <title>Title</title>
  </head>
  <body>
    Hello World
  </body>
</html>

WARファイルの作成

SBTのインタープリタに入り、packageと入力します。

> sbt
sbt> package

すると、sbt/targetにWARファイル化する前のwebappディレクトリが現れます。GAEの開発用ウェブサーバを起動するときには、このディレクトリを指定するといいでしょう。さらに、sbt/target/scala-***には、(project name)_(scala version)-(project version).warが作成されています。

開発用ウェブサーバの起動

開発用に使用するウェブサーバもGAE SDKについてきます。appengine-java-sdk-***/bin/dev_appserver.cmd(Windows)です。

起動
appengine-java-sdk-***/bin/dev_appserver.cmd sbt/target/webapp

起動したら、ブラウザでhttp://localhost:8080にアクセスしてみましょう。Hello Worldと表示されていれば成功です。

アプリケーションのアップロード

次のコマンドでアップロードができます。

appengine-java-sdk-***/bin/appcfg.cmd update sbt/target/webapp

途中でemailとpasswordを聞かれるので、入力します。しばらくすると完了します。

管理画面に行って、デプロイされている確認しましょう。

関連記事