From b541698eb0c7db0395235cac37842548ee1bc678 Mon Sep 17 00:00:00 2001 From: Suzuki Takeo Date: Mon, 11 May 2020 19:52:09 +0900 Subject: [PATCH 1/2] wip --- README.md | 25 ++ .../apiServer/Main.scala | 23 +- .../apiServer/RestServiceHandler.scala | 59 +++++ .../apiServer/Routes.scala | 20 -- ...oordinate.scala => CoordinateRecord.scala} | 4 +- .../model/Direction.scala | 10 + .../model/ErrorType.scala | 6 + .../model/Member.scala | 3 + .../model/WebsocketData.scala | 42 ++++ .../repository/CoordinateRepository.scala | 2 +- .../service/RoomActorRef.scala | 26 --- .../service/RoomAggregate.scala | 19 +- .../service/RoomAggregates.scala | 117 ++++------ .../service/RoomServiceImpl.scala | 187 +++++++++++++++ .../service/RoomServicePowerApiImpl.scala | 220 +++++++++--------- build.sbt | 3 +- docker-compose-local.yaml | 1 + project/Dependencies.scala | 1 + 18 files changed, 510 insertions(+), 258 deletions(-) create mode 100644 apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/apiServer/RestServiceHandler.scala delete mode 100644 apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/apiServer/Routes.scala rename apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/{Coordinate.scala => CoordinateRecord.scala} (81%) create mode 100644 apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/Direction.scala create mode 100644 apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/ErrorType.scala create mode 100644 apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/Member.scala create mode 100644 apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/WebsocketData.scala delete mode 100644 apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomActorRef.scala create mode 100644 apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomServiceImpl.scala diff --git a/README.md b/README.md index f8cd8a1..dbef716 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,31 @@ $ grpcurl -v -plaintext -import-path . -proto apiServer/src/main/protobuf/room.p ```bash grpcurl -v -plaintext -import-path . -proto apiServer/src/main/protobuf/room.proto -d '{"RoomId":"dfa65e98a18340cbb77a4fb9738d9a16","AccountId":"parent","ghostRecord":[{"x":0.1,"y":0.1,"z":0.1,"date":0}],"isGameClear":true,"date":10}' ${SERVER_ENDPOINT} room.RoomService/SendResult +``` + +## ws +```bash +$ wscat -c ws://localhost:18080/health + + +$ wscat -c "ws://localhost:18080/create_room?accountId=parent&accountName=parentName" +> {"":""} +``` + +```bash +$ wscat -c "ws://localhost:18080/join_room?accountId=child1&accountName=child1Name" +> {"direction":{"direction":"Up"}, "strength":0.1} + +$ wscat -c "ws://localhost:18080/join_room?accountId=child2&accountName=child2Name" +$ wscat -c "ws://localhost:18080/join_room?accountId=child3&accountName=child3Name" + + + + + + + + ``` ## ssh鍵作成 diff --git a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/apiServer/Main.scala b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/apiServer/Main.scala index bf4a77b..d15adaf 100644 --- a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/apiServer/Main.scala +++ b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/apiServer/Main.scala @@ -6,12 +6,10 @@ import akka.http.scaladsl.{Http, HttpConnectionContext} import akka.http.scaladsl.UseHttp2.Always import akka.http.scaladsl.model.{HttpRequest, HttpResponse} import akka.stream.ActorMaterializer -import com.github.CA21engineer.HouseHackathonUnityServer.grpc.room.RoomServicePowerApiHandler -import com.github.CA21engineer.HouseHackathonUnityServer.service.RoomServicePowerApiImpl +import com.github.CA21engineer.HouseHackathonUnityServer.service.{RoomAggregates, RoomServiceImpl} import com.typesafe.config.ConfigFactory import scala.concurrent.{ExecutionContextExecutor, Future} - import scalikejdbc._ object Main extends App { @@ -28,21 +26,18 @@ object Main extends App { Class.forName("com.mysql.jdbc.Driver") ConnectionPool.singleton("jdbc:mysql://mysql:3306/unity?rewriteBatchedStatements=true", "ca21engineer", "pass") - val roomService: PartialFunction[HttpRequest, Future[HttpResponse]] = RoomServicePowerApiHandler.partial(new RoomServicePowerApiImpl) + val roomAggregates = new RoomAggregates() - val serviceHandlers: HttpRequest => Future[HttpResponse] = - ServiceHandler.concatOrNotFound(roomService) + val restServiceHandlers = new RestServiceHandler(new RoomServiceImpl(roomAggregates)) + val restBindingFuture = Http().bindAndHandle( + handler = restServiceHandlers.toRoutes, + interface = "0.0.0.0", + port = 18080 + ) - val bindingFuture = - Http().bindAndHandleAsync( - handler = serviceHandlers, - interface = "0.0.0.0", - port = 18080, - connectionContext = HttpConnectionContext(http2 = Always) - ) sys.addShutdownHook { - bindingFuture + restBindingFuture .flatMap(_.unbind()) .onComplete(_ => system.terminate()) } diff --git a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/apiServer/RestServiceHandler.scala b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/apiServer/RestServiceHandler.scala new file mode 100644 index 0000000..13341ee --- /dev/null +++ b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/apiServer/RestServiceHandler.scala @@ -0,0 +1,59 @@ +package com.github.CA21engineer.HouseHackathonUnityServer.apiServer + +import akka.http.scaladsl.model.HttpMethods._ +import akka.http.scaladsl.model.StatusCodes +import akka.http.scaladsl.model.ws.Message +import akka.http.scaladsl.server.Directives._ +import akka.http.scaladsl.server._ +import akka.stream.{KillSwitches, Materializer} +import akka.stream.scaladsl.Flow +import ch.megard.akka.http.cors.scaladsl.CorsDirectives._ +import com.github.CA21engineer.HouseHackathonUnityServer.model._ +import com.github.CA21engineer.HouseHackathonUnityServer.service.RoomServiceImpl +import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport +import org.slf4j.{Logger, LoggerFactory} + +class RestServiceHandler(roomService: RoomServiceImpl)(implicit materializer: Materializer) extends FailFastCirceSupport { + val logger: Logger = LoggerFactory.getLogger(getClass) + + type QueryP[Q] = Directive[Q] => Route + + val killSwitch = KillSwitches.shared("health") + val flow = Flow[Message] via killSwitch.flow + + def toRoutes: Route = cors() { + Router( + route(GET, "create_room", createRoom), + route(GET, "join_room", joinRoom), + route(GET, "health", health) + ).create + } + + def health: QueryP[Unit] = _ { + val newFlow = flow.watchTermination()((f, d) => { + d.foreach { _ => + logger.info("RoomAggregates: watchTermination.watchParentSource") + killSwitch.shutdown() + }(materializer.executionContext) + f + }) + handleWebSocketMessages(newFlow) + } + + def createRoom: QueryP[Unit] = _ { + parameters('accountId.as[String], 'roomKey.as[String]?, 'accountName.as[String]) { (accountId, roomKey, accountName) => + handleWebSocketMessages(roomService.parentFlow(CreateRoomRequest(accountId, roomKey, accountName))) + } + } + + def joinRoom: QueryP[Unit] = _ { + parameters('accountId.as[String], 'roomKey.as[String]?, 'accountName.as[String]) { (accountId, roomKey, accountName) => + handleWebSocketMessages(roomService.childFlow(JoinRoomRequest(accountId, roomKey, accountName))) + } + } + + + + + +} diff --git a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/apiServer/Routes.scala b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/apiServer/Routes.scala deleted file mode 100644 index 74abe70..0000000 --- a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/apiServer/Routes.scala +++ /dev/null @@ -1,20 +0,0 @@ -package com.github.CA21engineer.HouseHackathonUnityServer.apiServer - -import akka.http.scaladsl.model.HttpMethods._ -import akka.http.scaladsl.model.StatusCodes -import akka.http.scaladsl.server.Directives.complete -import akka.http.scaladsl.server._ - -object Routes { - - def toRoutes: Router = { - Router( - route(GET, "health", health) - ) - } - - def health: Directive[Unit] => Route = _ { - complete(StatusCodes.OK) - } - -} diff --git a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/Coordinate.scala b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/CoordinateRecord.scala similarity index 81% rename from apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/Coordinate.scala rename to apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/CoordinateRecord.scala index a8005e7..40d6982 100644 --- a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/Coordinate.scala +++ b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/CoordinateRecord.scala @@ -1,9 +1,9 @@ package com.github.CA21engineer.HouseHackathonUnityServer.model -case class Coordinate( +case class CoordinateRecord( roomID: String, x: Float, y: Float, z: Float, pastMilliSecond: Int, - ) \ No newline at end of file + ) diff --git a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/Direction.scala b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/Direction.scala new file mode 100644 index 0000000..b4abae3 --- /dev/null +++ b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/Direction.scala @@ -0,0 +1,10 @@ +package com.github.CA21engineer.HouseHackathonUnityServer.model + +// Up, Down, Left, Right +case class Direction(direction: String) + +object Direction { + def all: List[Direction] = List(Direction("Up"), Direction("Down"), Direction("Left"), Direction("Right")) + + def shuffle: List[Direction] = scala.util.Random.shuffle(all) +} diff --git a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/ErrorType.scala b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/ErrorType.scala new file mode 100644 index 0000000..d0979ae --- /dev/null +++ b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/ErrorType.scala @@ -0,0 +1,6 @@ +package com.github.CA21engineer.HouseHackathonUnityServer.model + +sealed class ErrorType(val message: String) +case object RoomNotFound extends ErrorType("RoomNotFound") +case object LostConnection extends ErrorType("LostConnection") +case object MalformedMessageType extends ErrorType("MalformedMessageType") diff --git a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/Member.scala b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/Member.scala new file mode 100644 index 0000000..2f4ce6b --- /dev/null +++ b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/Member.scala @@ -0,0 +1,3 @@ +package com.github.CA21engineer.HouseHackathonUnityServer.model + +case class Member(accountName: String, direction: Direction) diff --git a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/WebsocketData.scala b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/WebsocketData.scala new file mode 100644 index 0000000..f5621f9 --- /dev/null +++ b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/model/WebsocketData.scala @@ -0,0 +1,42 @@ +package com.github.CA21engineer.HouseHackathonUnityServer.model + +//import io.circe.syntax._ +//import io.circe.generic.auto._ +// +//case class WebsocketData(event: String, data: String) { +// +// override def toString: String = this.asJson.noSpaces +// +//} +// +//object WebsocketData { +// +// def error(errorType: ErrorType): WebsocketData = +// WebsocketData("error", errorType.message) +//} +// +// + +import shapeless._ + + +sealed trait WebsocketData +sealed trait ParentData extends WebsocketData +sealed trait ChildData extends WebsocketData + + +case class CreateRoomRequest(accountId: String, roomKey: Option[String], accountName: String) +case class JoinRoomRequest(accountId: String, roomKey: Option[String], accountName: String) + +case class JoinRoomResponse(roomId: String, vagrant: Int) extends WebsocketData + +case class ReadyResponse(roomId: String, ghostRecord: Seq[Any], member: List[Member], yourDirection: Direction) extends WebsocketData + +case class Coordinate(x: Float, y: Float, z: Float, elapsedTime: Int) extends WebsocketData +case class Operation(direction: Direction, strength: Float) extends WebsocketData + +case class ErrorResponse(errorType: ErrorType, message: String) extends WebsocketData + + + + diff --git a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/repository/CoordinateRepository.scala b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/repository/CoordinateRepository.scala index 99a054e..c6d8c40 100644 --- a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/repository/CoordinateRepository.scala +++ b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/repository/CoordinateRepository.scala @@ -1,6 +1,6 @@ package com.github.CA21engineer.HouseHackathonUnityServer.repository -import com.github.CA21engineer.HouseHackathonUnityServer.model.{Room, Coordinate} +import com.github.CA21engineer.HouseHackathonUnityServer.model.{Room, CoordinateRecord} import com.github.CA21engineer.HouseHackathonUnityServer.grpc._ import scalikejdbc._ diff --git a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomActorRef.scala b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomActorRef.scala deleted file mode 100644 index 22938fa..0000000 --- a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomActorRef.scala +++ /dev/null @@ -1,26 +0,0 @@ -package com.github.CA21engineer.HouseHackathonUnityServer.service - -import akka.NotUsed -import akka.actor.ActorRef -import akka.stream._ -import akka.stream.scaladsl._ - -case class RoomActorRef[Coordinate, Operation](playingDataSharingActorRef: (ActorRef, Source[Coordinate, NotUsed]), operationSharingActorRef: (ActorRef, Source[Operation, NotUsed])) - -object RoomActorRef { - - // TODO - def create[Coordinate, Operation](killSwitch: SharedKillSwitch)(implicit materializer: Materializer): RoomActorRef[Coordinate, Operation] = { - RoomActorRef[Coordinate, Operation]( - playingDataSharingActorRef = createSource(killSwitch), - operationSharingActorRef = createSource(killSwitch) - ) - } - - def createSource[T](killSwitch: SharedKillSwitch)(implicit materializer: Materializer): (ActorRef, Source[T, NotUsed]) = { - val actorRefSource: Source[T, ActorRef] = Source.actorRef[T](bufferSize = 1000, OverflowStrategy.fail) via killSwitch.flow - actorRefSource.toMat(BroadcastHub.sink[T](bufferSize = 256))(Keep.both).run() - } - -} - diff --git a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomAggregate.scala b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomAggregate.scala index b1045c5..ef4b9d8 100644 --- a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomAggregate.scala +++ b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomAggregate.scala @@ -4,10 +4,13 @@ import akka.NotUsed import akka.actor.ActorRef import akka.stream.{KillSwitches, Materializer, OverflowStrategy, SharedKillSwitch} import akka.stream.scaladsl.{BroadcastHub, Keep, Source} +import com.github.CA21engineer.HouseHackathonUnityServer.model.WebsocketData import scala.util.Try -case class RoomAggregate[T, Coordinate, Operation](parent: (String, String, ActorRef), children: Set[(String, String, ActorRef)], roomRef: RoomActorRef[Coordinate, Operation], roomKey: Option[String], killSwitch: SharedKillSwitch) { +case class AccountAggregate(accountId: String, accountName: String, actorRef: ActorRef) + +case class RoomAggregate(parent: AccountAggregate, children: Set[AccountAggregate], roomKey: Option[String], killSwitch: SharedKillSwitch) { // 親を除いた定員 private val maxCapacity = 3 @@ -37,26 +40,26 @@ case class RoomAggregate[T, Coordinate, Operation](parent: (String, String, Acto * @param roomKey ルームの合言葉 * @return 満員でなく、プライベートなルームの場合は`roomKey`が一致していたら`Success[RoomAggregate]` */ - def joinRoom(accountId: String, accountName: String, roomKey: Option[String])(implicit materializer: Materializer): Try[(RoomAggregate[T, Coordinate, Operation], Source[T, NotUsed])] = Try { + def joinRoom(accountId: String, accountName: String, roomKey: Option[String])(implicit materializer: Materializer): Try[(RoomAggregate, Source[WebsocketData, NotUsed])] = Try { require(this.canParticipate(roomKey), "合言葉が違います") - require(!this.children.exists(_._1 == accountId), "accountId重複") + require(!this.children.exists(_.accountId == accountId), "accountId重複") val (actorRef, source) = RoomAggregate.createActorRef - val newChildren = (accountId, accountName, actorRef) + val newChildren = AccountAggregate(accountId, accountName, actorRef) (copy(children = this.children + newChildren), source via killSwitch.flow) } - def leaveRoom(accountId: String): RoomAggregate[T, Coordinate, Operation] = { - copy(children = this.children.takeWhile(_._1 != accountId)) + def leaveRoom(accountId: String): RoomAggregate = { + copy(children = this.children.takeWhile(_.accountId != accountId)) } } object RoomAggregate { - def create[T, Coordinate, Operation](authorAccountId: String, authorAccountName: String, roomKey: Option[String], roomId: String)(implicit materializer: Materializer): (RoomAggregate[T, Coordinate, Operation], Source[T, NotUsed]) = { + def create(authorAccountId: String, authorAccountName: String, roomKey: Option[String], roomId: String)(implicit materializer: Materializer): (RoomAggregate, Source[WebsocketData, NotUsed]) = { val (actorRef, source) = createActorRef val killSwitch = KillSwitches.shared(roomId) - (RoomAggregate((authorAccountId, authorAccountName, actorRef), Set.empty, RoomActorRef.create(killSwitch), roomKey, killSwitch) ,source via killSwitch.flow) + (RoomAggregate(AccountAggregate(authorAccountId, authorAccountName, actorRef), Set.empty, roomKey, killSwitch), source via killSwitch.flow) } def createActorRef[T](implicit materializer: Materializer): (ActorRef, Source[T, NotUsed]) = { diff --git a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomAggregates.scala b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomAggregates.scala index 9e3aecc..3efe8f0 100644 --- a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomAggregates.scala +++ b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomAggregates.scala @@ -2,21 +2,22 @@ package com.github.CA21engineer.HouseHackathonUnityServer.service import akka.NotUsed import akka.stream.Materializer -import akka.stream.scaladsl.Source +import akka.stream.scaladsl.{Flow, Source} import scala.util.{Failure, Success, Try} import com.github.CA21engineer.HouseHackathonUnityServer.repository -import com.github.CA21engineer.HouseHackathonUnityServer.grpc.room._ +import com.github.CA21engineer.HouseHackathonUnityServer.model.{Direction, ErrorResponse, JoinRoomResponse, LostConnection, Member, ReadyResponse, WebsocketData} import org.slf4j.{Logger, LoggerFactory} -class RoomAggregates[T, Coordinate, Operation](implicit materializer: Materializer) { +class RoomAggregates(implicit materializer: Materializer) { val logger: Logger = LoggerFactory.getLogger(getClass) - val rooms: scala.collection.mutable.Map[String, RoomAggregate[T, Coordinate, Operation]] = scala.collection.mutable.Map.empty + val rooms: scala.collection.mutable.Map[String, RoomAggregate] = scala.collection.mutable.Map.empty - def watchParentSource[S](source: Source[S, NotUsed], roomId: String): Source[S, NotUsed] = { - source.watchTermination()((f, d) => { + def watchParentSource[I, O](in: Flow[I, O, NotUsed], roomId: String): Flow[I, O, NotUsed] = { + in.watchTermination()((f, d) => { d.foreach { _ => + logger.info("RoomAggregates: watchTermination.watchParentSource") this.rooms.get(roomId) .foreach { roomAggregate => this.rooms.remove(roomId) @@ -27,15 +28,17 @@ class RoomAggregates[T, Coordinate, Operation](implicit materializer: Materializ }) } - def watchLeavingRoomSource[S](source: Source[S, NotUsed], roomId: String, accountId: String): Source[S, NotUsed] = { - source.watchTermination()((f, d) => { + def watchLeavingRoomSource[I, O](in: Flow[I, O, NotUsed], roomId: String, accountId: String): Flow[I, O, NotUsed] = { + in.watchTermination()((f, d) => { d.foreach(_ => { - // 退室処理 + logger.info("RoomAggregates: watchTermination.watchLeavingRoomSource") this.rooms .get(roomId) .foreach { roomAggregate => - if (roomAggregate.isFull) sendErrorMessageToEveryOne(roomAggregate) - else { + if (roomAggregate.isFull) { + this.rooms.remove(roomId) + sendErrorMessageToEveryOne(roomAggregate) + } else { val newRoomAggregate = roomAggregate.leaveRoom(accountId) logger.info(s"LeavingRoom: roomId = $roomId, accountId = $accountId, vacantPeople = ${newRoomAggregate.vacantPeople}, children = ${newRoomAggregate.children}") this.rooms.update(roomId, newRoomAggregate) @@ -47,14 +50,29 @@ class RoomAggregates[T, Coordinate, Operation](implicit materializer: Materializ }) } - def sendErrorMessageToEveryOne(roomAggregate: RoomAggregate[T, Coordinate, Operation]): Unit = { + def sendErrorMessageToEveryOne(roomAggregate: RoomAggregate): Unit = { val m = roomAggregate.children + roomAggregate.parent - m.foreach(_._3 ! RoomResponse(RoomResponse.Response.Error(ErrorType.LOST_CONNECTION_ERROR))) + m.foreach(_.actorRef ! ErrorResponse(LostConnection, "LostConnection...")) + roomAggregate.killSwitch.shutdown() } - def sendJoinResponse(roomId: String, roomAggregate: RoomAggregate[T, Coordinate, Operation]): Unit = { + def sendJoinResponse(roomId: String, roomAggregate: RoomAggregate): Unit = { val m = roomAggregate.children + roomAggregate.parent - m.foreach(_._3 ! RoomResponse(RoomResponse.Response.JoinRoomResponse(JoinRoomResponse(roomId = roomId, vagrant = roomAggregate.vacantPeople)))) + m.foreach(_.actorRef ! JoinRoomResponse(roomId = roomId, vagrant = roomAggregate.vacantPeople)) + } + + def sendReadyResponse(roomId: String, ghostRecord: Seq[Any], roomAggregate: RoomAggregate): Unit = { + val directions: Seq[Direction] = Direction.shuffle + val allMember = roomAggregate.children + roomAggregate.parent + val allMemberHasDirection = allMember.zip(directions) + allMemberHasDirection.foreach(a => { + a._1.actorRef ! ReadyResponse( + roomId = roomId, + ghostRecord = ghostRecord, + member = allMemberHasDirection.map(a => Member(a._1.accountName, a._2)).toList, + direction = a._2 + ) + }) } def generateRoomId(): String = @@ -68,88 +86,37 @@ class RoomAggregates[T, Coordinate, Operation](implicit materializer: Materializ * @param roomKey ルームの合言葉: Some -> プラーベートなルーム, None -> パブリックなルーム * @return */ - def createRoom(authorAccountId: String, authorAccountName: String, roomKey: Option[String]): Source[T, NotUsed] = { + def createRoom(authorAccountId: String, authorAccountName: String, roomKey: Option[String]): (String, Source[WebsocketData, NotUsed]) = { val roomId = generateRoomId() - val (roomAggregate, source) = RoomAggregate.create[T, Coordinate, Operation](authorAccountId, authorAccountName, roomKey, roomId) + val (roomAggregate, source) = RoomAggregate.create(authorAccountId, authorAccountName, roomKey, roomId) rooms(roomId) = roomAggregate - watchParentSource(source, roomId) + (roomId, source) } - def searchVacantRoom(roomKey: Option[String]): Try[(String, RoomAggregate[T, Coordinate, Operation])] = { + def searchVacantRoom(roomKey: Option[String]): Try[(String, RoomAggregate)] = { this.rooms.find(_._2.canParticipate(roomKey)) .map(Success(_)).getOrElse(Failure(new Exception("参加可能な部屋がありません"))) } - def getRoomAggregate(roomId: String, accountId: String): Option[RoomAggregate[T, Coordinate, Operation]] = { - this.rooms - .get(roomId) - .collect { - case roomAggregate if roomAggregate.parent._1 == accountId => - roomAggregate.copy( - roomRef = roomAggregate.roomRef.copy( - playingDataSharingActorRef = ( - roomAggregate.roomRef.playingDataSharingActorRef._1, - watchParentSource(roomAggregate.roomRef.playingDataSharingActorRef._2, roomId) - ), - operationSharingActorRef = ( - roomAggregate.roomRef.operationSharingActorRef._1, - watchParentSource(roomAggregate.roomRef.operationSharingActorRef._2, roomId) - ) - ) - ) - case roomAggregate if roomAggregate.children.exists(_._1 == accountId) => - roomAggregate.copy( - roomRef = roomAggregate.roomRef.copy( - playingDataSharingActorRef = ( - roomAggregate.roomRef.playingDataSharingActorRef._1, - watchParentSource(roomAggregate.roomRef.playingDataSharingActorRef._2, roomId) - ), - operationSharingActorRef = ( - roomAggregate.roomRef.operationSharingActorRef._1, - watchParentSource(roomAggregate.roomRef.operationSharingActorRef._2, roomId) - ) - ) - ) - } - } + def getRoomAggregate(roomId: String): Option[RoomAggregate] = + this.rooms.get(roomId) - def joinRoom(accountId: String, accountName: String, roomKey: Option[String]): Try[Source[T, NotUsed]] = + def joinRoom(accountId: String, accountName: String, roomKey: Option[String]): Try[(String, Source[WebsocketData, NotUsed])] = for { (roomId, roomAggregate) <- this.searchVacantRoom(roomKey) (newRoomAggregate, source) <- roomAggregate.joinRoom(accountId, accountName, roomKey) } yield { - val allMember = newRoomAggregate.children + newRoomAggregate.parent if (newRoomAggregate.isFull) { - // 操作方向の抽選 - val directions: Seq[Direction] = scala.util.Random.shuffle(List(Direction.Up,Direction.Down,Direction.Left,Direction.Right)) - - val allMemberHasDirection = allMember.zip(directions) - // ゴーストレコードの取得 val ghostRec = repository.CoordinateRepository.findBestRecord() - - val readyResponse = { direction: Direction => - RoomResponse(RoomResponse.Response.ReadyResponse(ReadyResponse( - roomId = roomId, - ghostRecord = ghostRec, - member = allMemberHasDirection.map(a => Member(a._1._2, a._2)).toSeq, - direction = direction, - date = java.time.Instant.now().toString - ))) - } - // 準備完了通知 - allMemberHasDirection - .foreach { a => - logger.info(s"ReadyNotification: accountId = ${a._1._1}, accountName = ${a._1._2}") - a._1._3 ! readyResponse(a._2) - } + sendReadyResponse(roomId, ghostRec, newRoomAggregate) repository.RoomRepository.create(roomId) // insert db } this.rooms.update(roomId, newRoomAggregate) sendJoinResponse(roomId, newRoomAggregate) - watchLeavingRoomSource(source, roomId, accountId) + (roomId, source) } } diff --git a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomServiceImpl.scala b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomServiceImpl.scala new file mode 100644 index 0000000..71ae222 --- /dev/null +++ b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomServiceImpl.scala @@ -0,0 +1,187 @@ +package com.github.CA21engineer.HouseHackathonUnityServer.service + +import akka.NotUsed +import akka.actor.ActorRef +import akka.http.scaladsl.model.ws.{Message, TextMessage} +import akka.stream.{Materializer, OverflowStrategy} +import akka.stream.scaladsl.{BroadcastHub, Flow, Keep, Sink, Source} +import com.github.CA21engineer.HouseHackathonUnityServer.model.{ChildData, Coordinate, CoordinateRecord, CreateRoomRequest, ErrorResponse, JoinRoomRequest, JoinRoomResponse, MalformedMessageType, Operation, ParentData, ReadyResponse, RoomNotFound, WebsocketData} +import org.slf4j.{Logger, LoggerFactory} +import shapeless.{:+:, CNil, Coproduct} +import io.circe.generic.auto._ +import io.circe.shapes._ +import io.circe.parser + +import scala.util.{Failure, Success, Try} + + +class RoomServiceImpl(roomAggregates: RoomAggregates)(implicit materializer: Materializer) { + val logger: Logger = LoggerFactory.getLogger(getClass) + + type Event = Coordinate :+: Operation :+: CNil + + def createRoom(in: CreateRoomRequest, inSource: Source[WebsocketData, NotUsed]): (String, Source[WebsocketData, NotUsed]) = { + logger.info(s"CreateRoomRequest: accountId = ${in.accountId}, accountName = ${in.accountName}, roomKey = ${in.roomKey}") + val (roomId, outSource) = roomAggregates.createRoom(in.accountId, in.accountName, in.roomKey) + roomAggregates + .getRoomAggregate(roomId) + .foreach { roomAggregate => + inSource.map { + case v: Coordinate => + roomAggregate.children.foreach(_.actorRef ! v) + case v: ErrorResponse => + roomAggregate.parent.actorRef ! v + } + } + (roomId, outSource) + } + + def joinRoom(in: JoinRoomRequest, inSource: Source[WebsocketData, NotUsed]): Try[(String, Source[WebsocketData, NotUsed])] = { + logger.info(s"JoinRoomRequest: accountId = ${in.accountId}, accountName = ${in.accountName}, roomKey = ${in.roomKey}") + roomAggregates + .joinRoom(in.accountId, in.accountName, in.roomKey) + .map { a => + roomAggregates + .getRoomAggregate(a._1) + .foreach { roomAggregate => + inSource.map { + case v: Operation => + roomAggregate.parent.actorRef ! v + case v: ErrorResponse => + roomAggregate.parent.actorRef ! v + } + } + a + } + } + + + def parentFlow(in: CreateRoomRequest): Flow[Message, Message, NotUsed] = { + val actorRefSource: Source[WebsocketData, ActorRef] = Source.actorRef[WebsocketData](bufferSize = 1000, OverflowStrategy.fail) + val (actorRef, inSource) = actorRefSource.toMat(BroadcastHub.sink[WebsocketData](bufferSize = 256))(Keep.both).run() + val inDecode = Flow[Message].collect { + case TextMessage.Strict(message) => + logger.info(s"ReceiveParentMessage: $message") + parser.decode[Event](message) match { + case Right(v) => + logger.info(s"ReceiveParentMessage: Right#$v") + Coproduct.unsafeGet(v).asInstanceOf[WebsocketData] + case Left(e) => + logger.info(s"ReceiveParentMessage: Right#$e") + ErrorResponse(MalformedMessageType, e.getMessage) + } + } + val outDecode = Flow[WebsocketData].map { s => + logger.info(s"Send to client: ${s.toString}") + TextMessage(s.toString) + } + val (roomId, outSource) = createRoom(in, inSource) + val flow = inDecode via Flow.fromSinkAndSource[WebsocketData, WebsocketData](Sink.actorRef(actorRef, "Complete"), outSource) via outDecode + roomAggregates.watchParentSource(flow, roomId) + } + + def childFlow(in: JoinRoomRequest): Flow[Message, Message, NotUsed] = { + val actorRefSource: Source[WebsocketData, ActorRef] = Source.actorRef[WebsocketData](bufferSize = 1000, OverflowStrategy.fail) + val (actorRef, inSource) = actorRefSource.toMat(BroadcastHub.sink[WebsocketData](bufferSize = 256))(Keep.both).run() + val inDecode = Flow[Message].collect { + case TextMessage.Strict(message) => + logger.info(s"ReceiveChildMessage: $message") + parser.decode[Event](message) match { + case Right(v) => + logger.info(s"ReceiveChildMessage: Right#$v") + Coproduct.unsafeGet(v).asInstanceOf[WebsocketData] + case Left(e) => + logger.info(s"ReceiveChildMessage: Right#$e") + ErrorResponse(MalformedMessageType, e.getMessage) + } + } + val outDecode = Flow[WebsocketData].map { s => + logger.info(s"Send to client: ${s.toString}") + TextMessage(s.toString) + } + joinRoom(in, inSource) match { + case Failure(_) => + inDecode via Flow.fromSinkAndSource[WebsocketData, WebsocketData](Sink.ignore, Source.empty) via outDecode + case Success((roomId, outSource)) => + val flow = inDecode via Flow.fromSinkAndSource[WebsocketData, WebsocketData](Sink.actorRef(actorRef, "Complete"), outSource) via outDecode + roomAggregates.watchLeavingRoomSource(flow, roomId, in.accountId) + } + } + +// override def coordinateSharing(in: Source[CoordinateRecord, NotUsed], metadata: Metadata): Source[WebsocketData, NotUsed] = { +// logger.info(s"CoordinateSharingRequest") +// (metadata.getText("roomid"), metadata.getText("accountid")) match { +// case (Some(roomId), Some(accountId)) => +// logger.info(s"CoordinateSharingRequest: roomId = $roomId, accountId = $accountId") +// roomAggregates +// .getRoomAggregate(roomId, accountId) +// .map(_.roomRef.playingDataSharingActorRef) +// .map { ref => +// in.runForeach(a => ref._1 ! a) +// ref._2 +// } +// .getOrElse({ +// logger.error("CoordinateSharingRequest: failed") +// Source.empty +// }) +// case _ => +// logger.error(s"CoordinateSharingRequest: meta failed, roomId = ${metadata.getText("roomid")}, accountId = ${metadata.getText("accountid")}") +// Source.empty +// } +// } +// +// override def childOperation(in: Source[Operation, NotUsed], metadata: Metadata): Source[Empty, NotUsed] = { +// logger.info(s"ChildOperationRequest") +// (metadata.getText("roomid"), metadata.getText("accountid")) match { +// case (Some(roomId), Some(accountId)) => +// logger.info(s"ChildOperationRequest: roomId = $roomId, accountId = $accountId") +// roomAggregates +// .getRoomAggregate(roomId, accountId) +// .map(_.roomRef.operationSharingActorRef._1) +// .map { ref => +// in.map(a => { +// logger.debug(s"ChildOperationRequest: $a") +// ref ! a +// Empty() +// }) +// } +// .getOrElse({ +// logger.error("ChildOperationRequest: failed") +// Source.empty +// }) +// case _ => +// logger.error(s"ChildOperationRequest: meta failed, roomId = ${metadata.getText("roomid")}, accountId = ${metadata.getText("accountid")}") +// Source.empty +// } +// } +// +// override def parentOperation(in: ParentOperationRequest, metadata: Metadata): Source[Operation, NotUsed] = { +// logger.info(s"ParentOperationRequest: roomId = ${in.roomId}, accountId = ${in.accountId}") +// roomAggregates +// .getRoomAggregate(in.roomId, in.accountId) +// .map(_.roomRef.operationSharingActorRef._2) +// .getOrElse({ +// logger.error("ParentOperationRequest: failed") +// Source.empty +// }) +// } +// +// override def sendResult(in: SendResultRequest, metadata: Metadata): Future[Empty] = { +// // 親のみ書き込み可能 +// logger.info(s"SendResultRequest: ${in.roomId}, ${in.accountId}, ${in.ghostRecord}") +// roomAggregates +// .getRoomAggregate(in.roomId, in.accountId) +// .filter(_.parent._1 == in.accountId) +// .map { aggregate => +// logger.info(s"SendResultRequest: roomId = ${in.roomId}, accountId = ${in.accountId}, ghostRecordSize = ${in.ghostRecord.size}, isGameClear = ${in.isGameClear}, clearTime = ${in.date}") +// val start = java.time.Instant.now().toEpochMilli +// Future { CoordinateRepository.recordData(100, in.roomId, in.ghostRecord) }(materializer.executionContext) +// .onComplete(_ => logger.info(s"CoordinateRepository: processing time = ${java.time.Instant.now().toEpochMilli - start}"))(materializer.executionContext) +// aggregate.children.foreach(_._3 ! RoomResponse(RoomResponse.Response.Result(SimpleGameResult(in.isGameClear, in.date)))) +// } +// .fold({ +// logger.info("SendResultRequest: failed") +// Future.failed[Empty](new Exception("Internal Error!!!")) +// })(_ => Future.successful(Empty())) +// } +} diff --git a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomServicePowerApiImpl.scala b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomServicePowerApiImpl.scala index bf544d0..0dc9657 100644 --- a/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomServicePowerApiImpl.scala +++ b/apiServer/src/main/scala/com/github/CA21engineer/HouseHackathonUnityServer/service/RoomServicePowerApiImpl.scala @@ -1,111 +1,109 @@ -package com.github.CA21engineer.HouseHackathonUnityServer.service - -import akka.NotUsed -import akka.stream.Materializer -import akka.stream.scaladsl.Source -import com.github.CA21engineer.HouseHackathonUnityServer.grpc.room._ -import akka.grpc.scaladsl.Metadata - -import scala.concurrent.Future -import com.github.CA21engineer.HouseHackathonUnityServer.repository.CoordinateRepository -import org.slf4j.{Logger, LoggerFactory} - -class RoomServicePowerApiImpl(implicit materializer: Materializer) extends RoomServicePowerApi { - val logger: Logger = LoggerFactory.getLogger(getClass) - - val roomAggregates = new RoomAggregates[RoomResponse, Coordinate, Operation]() - - override def createRoom(in: CreateRoomRequest, metadata: Metadata): Source[RoomResponse, NotUsed] = { - val roomKey = if (in.roomKey.nonEmpty) Some(in.roomKey) else None - logger.info(s"CreateRoomRequest: accountId = ${in.accountId}, accountName = ${in.accountName}, roomKey = $roomKey") - roomAggregates.createRoom(in.accountId, in.accountName, roomKey) - } - - override def joinRoom(in: JoinRoomRequest, metadata: Metadata): Source[RoomResponse, NotUsed] = { - val roomKey = if (in.roomKey.nonEmpty) Some(in.roomKey) else None - logger.info(s"JoinRoomRequest: accountId = ${in.accountId}, accountName = ${in.accountName}, roomKey = $roomKey") - roomAggregates - .joinRoom(in.accountId, in.accountName, roomKey) - .getOrElse({ - logger.error("JoinRoomRequest: failed") - Source.single(RoomResponse(RoomResponse.Response.Error(ErrorType.ROOM_NOT_FOUND_ERROR))) - }) - } - - override def coordinateSharing(in: Source[Coordinate, NotUsed], metadata: Metadata): Source[Coordinate, NotUsed] = { - logger.info(s"CoordinateSharingRequest") - (metadata.getText("roomid"), metadata.getText("accountid")) match { - case (Some(roomId), Some(accountId)) => - logger.info(s"CoordinateSharingRequest: roomId = $roomId, accountId = $accountId") - roomAggregates - .getRoomAggregate(roomId, accountId) - .map(_.roomRef.playingDataSharingActorRef) - .map { ref => - in.runForeach(a => ref._1 ! a) - ref._2 - } - .getOrElse({ - logger.error("CoordinateSharingRequest: failed") - Source.empty - }) - case _ => - logger.error(s"CoordinateSharingRequest: meta failed, roomId = ${metadata.getText("roomid")}, accountId = ${metadata.getText("accountid")}") - Source.empty - } - } - - override def childOperation(in: Source[Operation, NotUsed], metadata: Metadata): Source[Empty, NotUsed] = { - logger.info(s"ChildOperationRequest") - (metadata.getText("roomid"), metadata.getText("accountid")) match { - case (Some(roomId), Some(accountId)) => - logger.info(s"ChildOperationRequest: roomId = $roomId, accountId = $accountId") - roomAggregates - .getRoomAggregate(roomId, accountId) - .map(_.roomRef.operationSharingActorRef._1) - .map { ref => - in.map(a => { - logger.debug(s"ChildOperationRequest: $a") - ref ! a - Empty() - }) - } - .getOrElse({ - logger.error("ChildOperationRequest: failed") - Source.empty - }) - case _ => - logger.error(s"ChildOperationRequest: meta failed, roomId = ${metadata.getText("roomid")}, accountId = ${metadata.getText("accountid")}") - Source.empty - } - } - - override def parentOperation(in: ParentOperationRequest, metadata: Metadata): Source[Operation, NotUsed] = { - logger.info(s"ParentOperationRequest: roomId = ${in.roomId}, accountId = ${in.accountId}") - roomAggregates - .getRoomAggregate(in.roomId, in.accountId) - .map(_.roomRef.operationSharingActorRef._2) - .getOrElse({ - logger.error("ParentOperationRequest: failed") - Source.empty - }) - } - - override def sendResult(in: SendResultRequest, metadata: Metadata): Future[Empty] = { - // 親のみ書き込み可能 - logger.info(s"SendResultRequest: ${in.roomId}, ${in.accountId}, ${in.ghostRecord}") - roomAggregates - .getRoomAggregate(in.roomId, in.accountId) - .filter(_.parent._1 == in.accountId) - .map { aggregate => - logger.info(s"SendResultRequest: roomId = ${in.roomId}, accountId = ${in.accountId}, ghostRecordSize = ${in.ghostRecord.size}, isGameClear = ${in.isGameClear}, clearTime = ${in.date}") - val start = java.time.Instant.now().toEpochMilli - Future { CoordinateRepository.recordData(100, in.roomId, in.ghostRecord) }(materializer.executionContext) - .onComplete(_ => logger.info(s"CoordinateRepository: processing time = ${java.time.Instant.now().toEpochMilli - start}"))(materializer.executionContext) - aggregate.children.foreach(_._3 ! RoomResponse(RoomResponse.Response.Result(SimpleGameResult(in.isGameClear, in.date)))) - } - .fold({ - logger.info("SendResultRequest: failed") - Future.failed[Empty](new Exception("Internal Error!!!")) - })(_ => Future.successful(Empty())) - } -} +//package com.github.CA21engineer.HouseHackathonUnityServer.service +// +//import akka.NotUsed +//import akka.stream.Materializer +//import akka.stream.scaladsl.Source +//import com.github.CA21engineer.HouseHackathonUnityServer.grpc.room._ +//import akka.grpc.scaladsl.Metadata +// +//import scala.concurrent.Future +//import com.github.CA21engineer.HouseHackathonUnityServer.repository.CoordinateRepository +//import org.slf4j.{Logger, LoggerFactory} +// +//class RoomServicePowerApiImpl(roomAggregates: RoomAggregates[RoomResponse, CoordinateRecord, Operation])(implicit materializer: Materializer) extends RoomServicePowerApi { +// val logger: Logger = LoggerFactory.getLogger(getClass) +// +// override def createRoom(in: CreateRoomRequest, metadata: Metadata): Source[RoomResponse, NotUsed] = { +// val roomKey = if (in.roomKey.nonEmpty) Some(in.roomKey) else None +// logger.info(s"CreateRoomRequest: accountId = ${in.accountId}, accountName = ${in.accountName}, roomKey = $roomKey") +// roomAggregates.createRoom(in.accountId, in.accountName, roomKey) +// } +// +// override def joinRoom(in: JoinRoomRequest, metadata: Metadata): Source[RoomResponse, NotUsed] = { +// val roomKey = if (in.roomKey.nonEmpty) Some(in.roomKey) else None +// logger.info(s"JoinRoomRequest: accountId = ${in.accountId}, accountName = ${in.accountName}, roomKey = $roomKey") +// roomAggregates +// .joinRoom(in.accountId, in.accountName, roomKey) +// .getOrElse({ +// logger.error("JoinRoomRequest: failed") +// Source.single(RoomResponse(RoomResponse.Response.Error(ErrorType.ROOM_NOT_FOUND_ERROR))) +// }) +// } +// +// override def coordinateSharing(in: Source[CoordinateRecord, NotUsed], metadata: Metadata): Source[CoordinateRecord, NotUsed] = { +// logger.info(s"CoordinateSharingRequest") +// (metadata.getText("roomid"), metadata.getText("accountid")) match { +// case (Some(roomId), Some(accountId)) => +// logger.info(s"CoordinateSharingRequest: roomId = $roomId, accountId = $accountId") +// roomAggregates +// .getRoomAggregate(roomId, accountId) +// .map(_.roomRef.playingDataSharingActorRef) +// .map { ref => +// in.runForeach(a => ref._1 ! a) +// ref._2 +// } +// .getOrElse({ +// logger.error("CoordinateSharingRequest: failed") +// Source.empty +// }) +// case _ => +// logger.error(s"CoordinateSharingRequest: meta failed, roomId = ${metadata.getText("roomid")}, accountId = ${metadata.getText("accountid")}") +// Source.empty +// } +// } +// +// override def childOperation(in: Source[Operation, NotUsed], metadata: Metadata): Source[Empty, NotUsed] = { +// logger.info(s"ChildOperationRequest") +// (metadata.getText("roomid"), metadata.getText("accountid")) match { +// case (Some(roomId), Some(accountId)) => +// logger.info(s"ChildOperationRequest: roomId = $roomId, accountId = $accountId") +// roomAggregates +// .getRoomAggregate(roomId, accountId) +// .map(_.roomRef.operationSharingActorRef._1) +// .map { ref => +// in.map(a => { +// logger.debug(s"ChildOperationRequest: $a") +// ref ! a +// Empty() +// }) +// } +// .getOrElse({ +// logger.error("ChildOperationRequest: failed") +// Source.empty +// }) +// case _ => +// logger.error(s"ChildOperationRequest: meta failed, roomId = ${metadata.getText("roomid")}, accountId = ${metadata.getText("accountid")}") +// Source.empty +// } +// } +// +// override def parentOperation(in: ParentOperationRequest, metadata: Metadata): Source[Operation, NotUsed] = { +// logger.info(s"ParentOperationRequest: roomId = ${in.roomId}, accountId = ${in.accountId}") +// roomAggregates +// .getRoomAggregate(in.roomId, in.accountId) +// .map(_.roomRef.operationSharingActorRef._2) +// .getOrElse({ +// logger.error("ParentOperationRequest: failed") +// Source.empty +// }) +// } +// +// override def sendResult(in: SendResultRequest, metadata: Metadata): Future[Empty] = { +// // 親のみ書き込み可能 +// logger.info(s"SendResultRequest: ${in.roomId}, ${in.accountId}, ${in.ghostRecord}") +// roomAggregates +// .getRoomAggregate(in.roomId, in.accountId) +// .filter(_.parent._1 == in.accountId) +// .map { aggregate => +// logger.info(s"SendResultRequest: roomId = ${in.roomId}, accountId = ${in.accountId}, ghostRecordSize = ${in.ghostRecord.size}, isGameClear = ${in.isGameClear}, clearTime = ${in.date}") +// val start = java.time.Instant.now().toEpochMilli +// Future { CoordinateRepository.recordData(100, in.roomId, in.ghostRecord) }(materializer.executionContext) +// .onComplete(_ => logger.info(s"CoordinateRepository: processing time = ${java.time.Instant.now().toEpochMilli - start}"))(materializer.executionContext) +// aggregate.children.foreach(_._3 ! RoomResponse(RoomResponse.Response.Result(SimpleGameResult(in.isGameClear, in.date)))) +// } +// .fold({ +// logger.info("SendResultRequest: failed") +// Future.failed[Empty](new Exception("Internal Error!!!")) +// })(_ => Future.successful(Empty())) +// } +//} diff --git a/build.sbt b/build.sbt index f2223a4..353eab6 100644 --- a/build.sbt +++ b/build.sbt @@ -19,7 +19,7 @@ lazy val apiServer = (project in file("apiServer")) dockerUsername := Some("bambootuna"), mainClass in (Compile, bashScriptDefines) := Some("com.github.CA21engineer.HouseHackathonUnityServer.apiServer.Main"), packageName in Docker := name.value, - dockerExposedPorts := Seq(18080), + dockerExposedPorts := Seq(18080, 18081), javaAgents += "org.mortbay.jetty.alpn" % "jetty-alpn-agent" % "2.0.9" % "runtime", ) .settings( @@ -42,6 +42,7 @@ lazy val apiServer = (project in file("apiServer")) Akka.slf4j, Akka.contrib, Akka.`akka-http-crice`, + Akka.cors, Logback.classic, LogstashLogbackEncoder.encoder, Config.core, diff --git a/docker-compose-local.yaml b/docker-compose-local.yaml index 584458a..a5d9446 100644 --- a/docker-compose-local.yaml +++ b/docker-compose-local.yaml @@ -20,6 +20,7 @@ services: build: apiServer/target/docker/stage ports: - 18080:18080 + - 18081:18081 environment: - MYSQL_HOST=mysql - MYSQL_PORT=3306 diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 29f357f..400fb32 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -30,6 +30,7 @@ object Akka { val httpTestKit = "com.typesafe.akka" %% "akka-http-testkit" % httpVersion val `akka-http-crice` = "de.heikoseeberger" %% "akka-http-circe" % "1.24.3" + val cors = "ch.megard" %% "akka-http-cors" % "0.4.3" } object Circe { From b9e51015794d5f61c78b7183fe8a1b2c4c2d6c9c Mon Sep 17 00:00:00 2001 From: Suzuki Takeo Date: Mon, 11 May 2020 22:34:41 +0900 Subject: [PATCH 2/2] refactor: proto --- README.md | 10 +- apiServer/src/main/protobuf/model.proto | 40 ++++++++ apiServer/src/main/protobuf/request.proto | 27 +++++ apiServer/src/main/protobuf/response.proto | 34 +++++++ apiServer/src/main/protobuf/room.proto | 111 --------------------- grpc-code-gen/Dockerfile | 27 +++++ grpc-code-gen/code-gen-c#.sh | 15 +++ 7 files changed, 148 insertions(+), 116 deletions(-) create mode 100644 apiServer/src/main/protobuf/model.proto create mode 100644 apiServer/src/main/protobuf/request.proto create mode 100644 apiServer/src/main/protobuf/response.proto delete mode 100644 apiServer/src/main/protobuf/room.proto create mode 100644 grpc-code-gen/Dockerfile create mode 100755 grpc-code-gen/code-gen-c#.sh diff --git a/README.md b/README.md index dbef716..de0a5bb 100644 --- a/README.md +++ b/README.md @@ -150,12 +150,12 @@ $ wscat -c "ws://localhost:18080/join_room?accountId=child2&accountName=child2Na $ wscat -c "ws://localhost:18080/join_room?accountId=child3&accountName=child3Name" +``` - - - - - +## C# code gen +```bash +$ chmod +x ./grpc-code-gen/code-gen-c#.sh +$ ./grpc-code-gen/code-gen-c#.sh ./apiServer/src/main/protobuf ./pb ``` ## ssh鍵作成 diff --git a/apiServer/src/main/protobuf/model.proto b/apiServer/src/main/protobuf/model.proto new file mode 100644 index 0000000..bf13e98 --- /dev/null +++ b/apiServer/src/main/protobuf/model.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "com.github.CA21engineer.HouseHackathonUnityServer.grpc"; + +package model; + +message Operation { + Direction direction = 1; + float strength = 2; // 0 ~ 1 +} + +message Coordinate { + float x = 1; + float y = 2; + float z = 3; + // dateはゲームスタートからの開始ミリ秒(1s = 1000ms) + int64 date = 4; // ゲームスタートからの経過時間 +} + + +message Member { + string AccountName = 1; + Direction Direction = 2; +} + +enum Direction { + UNKNOWN = 0; + Up = 1; + Down = 2; + Left = 3; + Right =4; +} + +enum ErrorType { + UNKNOWN_MESSAGE_TYPE = 0; + LOST_CONNECTION_ERROR = 1; // 親or子の接続切れ + ROOM_NOT_FOUND_ERROR = 2; // ルームが見つからない, Privateな部屋で合言葉間違いもこれ + MALFORMED_MESSAGE_TYPE = 3; // 送信のデータ形式が間違っている +} diff --git a/apiServer/src/main/protobuf/request.proto b/apiServer/src/main/protobuf/request.proto new file mode 100644 index 0000000..7687a82 --- /dev/null +++ b/apiServer/src/main/protobuf/request.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "com.github.CA21engineer.HouseHackathonUnityServer.grpc"; + +import "model.proto"; + +package request; + + +message CreateRoomRequest { + string accountId = 1; + string accountName = 2; + string roomKey = 3; // Optional +} + +message JoinRoomRequest { + string accountId = 1; + string accountName = 2; + string roomKey = 3; // Optional +} + +message SendResultRequest { + repeated model.Coordinate ghostRecord = 1; + bool isGameClear = 2; // ゲームクリアならtrue、ゲームオーバーならfalse + int64 elapsedTime = 3; // 終了時点でのゲームスタートからの経過時間 +} diff --git a/apiServer/src/main/protobuf/response.proto b/apiServer/src/main/protobuf/response.proto new file mode 100644 index 0000000..6fbc38c --- /dev/null +++ b/apiServer/src/main/protobuf/response.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "com.github.CA21engineer.HouseHackathonUnityServer.grpc"; + +import "model.proto"; + +package room; + +message CreateRoomResponse { + string roomId = 1; +} + +message JoinRoomResponse { + string roomId = 1; + int32 vagrant = 2; // 空き人数 +} + +message ReadyResponse { + string roomId = 1; + repeated model.Coordinate ghostRecord = 2; + repeated model.Member member = 3; + model.Direction yourDirection = 4; +} + +message SimpleGameResultResponse { + bool isGameClear = 1; // ゲームクリアならtrue、ゲームオーバーならfalse + int64 elapsedTime = 2; // 終了時点でのゲームスタートからの経過時間 +} + +message ErrorResponse { + model.ErrorType errorType = 1; + string message = 2; +} diff --git a/apiServer/src/main/protobuf/room.proto b/apiServer/src/main/protobuf/room.proto deleted file mode 100644 index 2679552..0000000 --- a/apiServer/src/main/protobuf/room.proto +++ /dev/null @@ -1,111 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "com.github.CA21engineer.HouseHackathonUnityServer.grpc"; -option java_outer_classname = "RoomService"; - -//import "google/protobuf/timestamp.proto"; - -package room; - -message RoomResponse { - oneof response { - JoinRoomResponse joinRoomResponse = 1; - ReadyResponse readyResponse = 2; - SimpleGameResult result = 3; - ErrorType error = 4; - } -} - -enum ErrorType { - UNKNOWN_MESSAGE_TYPE = 0; - LOST_CONNECTION_ERROR = 1; // 親or子の接続切れ - ROOM_NOT_FOUND_ERROR = 2; // ルームが見つからない, Privateな部屋で合言葉間違いもこれ -} - -message CreateRoomRequest { - string AccountId = 1; - string roomKey = 2; // Optional - string AccountName = 3; -} - -message JoinRoomRequest { - string AccountId = 1; - string roomKey = 2; // Option - string AccountName = 3; -} -message JoinRoomResponse { - string RoomId = 1; - int32 vagrant = 2; // 空き人数 -} - -message ReadyResponse { - string RoomId = 1; - repeated Coordinate ghostRecord = 2; - repeated Member Member = 3; - Direction Direction = 4; - string date = 5; // 準備完了時間UTC -} - -message Member { - string AccountName = 1; - Direction Direction = 2; -} - -enum Direction { - UNKNOWN = 0; - Up = 1; - Down = 2; - Left = 3; - Right =4; -} - -// require MetaData: string roomid, string accountid -message Coordinate { - float x = 1; - float y = 2; - // dateはゲームスタートからの開始ミリ秒(1s = 1000ms) - int64 date = 3; // ゲームスタートからの経過時間 - float z = 4; -} - -// require MetaData: string roomid, string accountid -message Operation { - Direction Direction = 1; - float strength = 2; // 0 ~ 1 -} - -message ParentOperationRequest { - string RoomId = 1; - string AccountId = 2; -} - -message SendResultRequest { - string RoomId = 1; - string AccountId = 2; - repeated Coordinate ghostRecord = 3; - bool isGameClear = 4; // ゲームクリアならtrue、ゲームオーバーならfalse - int64 date = 5; // 終了時点でのゲームスタートからの経過時間 -} - -message SimpleGameResult { - bool isGameClear = 1; // ゲームクリアならtrue、ゲームオーバーならfalse - int64 date = 2; // 終了時点でのゲームスタートからの経過時間 -} - -message Empty {} - -service RoomService { - - rpc CreateRoom(CreateRoomRequest) returns (stream RoomResponse) {}; - rpc JoinRoom(JoinRoomRequest) returns (stream RoomResponse) {}; - - rpc CoordinateSharing(stream Coordinate) returns (stream Coordinate) {}; - - rpc ChildOperation(stream Operation) returns (stream Empty) {}; - rpc ParentOperation(ParentOperationRequest) returns (stream Operation) {}; - - rpc SendResult(SendResultRequest) returns (Empty) {}; -} - - diff --git a/grpc-code-gen/Dockerfile b/grpc-code-gen/Dockerfile new file mode 100644 index 0000000..930af72 --- /dev/null +++ b/grpc-code-gen/Dockerfile @@ -0,0 +1,27 @@ +FROM golang:1.13 + +ARG APP_HOME=${APP_HOME:-/app} +RUN mkdir -p $APP_HOME + +ARG GRPC_HOME=${GRPC_HOME:-/grpc} +RUN mkdir -p $GRPC_HOME +WORKDIR $GRPC_HOME + +USER root + +RUN apt-get update && apt-get install -y unzip +RUN curl -L -O https://github.com/protocolbuffers/protobuf/releases/download/v3.11.2/protoc-3.11.2-linux-x86_64.zip +RUN curl -L -O https://github.com/grpc/grpc-web/releases/download/1.0.7/protoc-gen-grpc-web-1.0.7-linux-x86_64 +RUN unzip protoc-3.11.2-linux-x86_64.zip && cp ./bin/protoc /usr/local/bin/. && chmod +x /usr/local/bin/protoc && mv ./include/google /usr/local/include/google +RUN cp protoc-gen-grpc-web-1.0.7-linux-x86_64 /usr/local/bin/protoc-gen-grpc-web && chmod +x /usr/local/bin/protoc-gen-grpc-web +RUN go get -u github.com/golang/protobuf/protoc-gen-go && \ + go get -u github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc && \ + go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway && \ + go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger + +RUN curl -L https://www.nuget.org/api/v2/package/Grpc.Tools/2.26.0 -o temp.zip \ + && unzip -p temp.zip tools/linux_x64/grpc_csharp_plugin > /usr/local/bin/grpc_csharp_plugin \ + && chmod +x /usr/local/bin/grpc_csharp_plugin \ + && rm temp.zip + +WORKDIR $APP_HOME diff --git a/grpc-code-gen/code-gen-c#.sh b/grpc-code-gen/code-gen-c#.sh new file mode 100755 index 0000000..4a64206 --- /dev/null +++ b/grpc-code-gen/code-gen-c#.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +docker build `dirname $0` -t protoc-gen-grpc-web:latest + +mkdir -p $2 +for file in `\find $1 -name '*.proto'`; do + docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v "${PWD}:/${PWD}" -w="/${PWD}" protoc-gen-grpc-web \ + protoc \ + -I$1 \ + -I/usr/local/include/google \ + --plugin=protoc-gen-grpc=/usr/local/bin/grpc_csharp_plugin \ + --csharp_out=$2 \ + --grpc_out=$2 \ + $file +done