Testing out JmonkeyEngine to make a game in Scala with Akka Actors within a pure FP layer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

268 lines
9.4 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. package wow.doge.mygame.game.entities
  2. import scala.concurrent.duration._
  3. import scala.util.Failure
  4. import scala.util.Success
  5. import akka.actor.typed.ActorRef
  6. import akka.actor.typed.Behavior
  7. import akka.actor.typed.LogOptions
  8. import akka.actor.typed.PostStop
  9. import akka.actor.typed.SupervisorStrategy
  10. import akka.actor.typed.scaladsl.ActorContext
  11. import akka.actor.typed.scaladsl.Behaviors
  12. import akka.util.Timeout
  13. import com.typesafe.scalalogging.Logger
  14. import monix.reactive.Observable
  15. import monix.reactive.OverflowStrategy
  16. import monix.reactive.subjects.ConcurrentSubject
  17. import org.slf4j.event.Level
  18. import wow.doge.mygame.Dispatchers
  19. import wow.doge.mygame.executors.Schedulers.AsyncScheduler
  20. import wow.doge.mygame.game.entities.StatsActor
  21. import wow.doge.mygame.implicits._
  22. import wow.doge.mygame.subsystems.events.EventBus
  23. import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
  24. import wow.doge.mygame.subsystems.events.PlayerEvent
  25. import wow.doge.mygame.subsystems.events.TickEvent
  26. import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
  27. import wow.doge.mygame.subsystems.movement.ImMovementActor
  28. import monix.eval.Coeval
  29. import monix.execution.AsyncQueue
  30. object PlayerActorSupervisor {
  31. type Ref = ActorRef[PlayerActorSupervisor.Command]
  32. sealed trait Status
  33. object Status {
  34. case object Alive extends Status
  35. case object Dead extends Status
  36. }
  37. sealed trait Command
  38. case class TakeDamage(value: CharacterStats.DamageHealth) extends Command
  39. case class TakeDamage2(
  40. value: CharacterStats.DamageHealth,
  41. replyTo: ActorRef[Unit]
  42. ) extends Command
  43. case class Heal(value: CharacterStats.HealHealth) extends Command
  44. case class CurrentStats(replyTo: ActorRef[CharacterStats]) extends Command
  45. case class GetStatus(replyTo: ActorRef[Status]) extends Command
  46. case class GetStatsObservable(replyTo: ActorRef[Observable[CharacterStats]])
  47. extends Command
  48. case class GetStatsObservable2(replyTo: ActorRef[Observable[CharacterStats]])
  49. extends Command
  50. private case object Die extends Command
  51. private case class DamageResponse(response: (Boolean, CharacterStats))
  52. extends Command
  53. private case class DamageResponse2(
  54. response: (Boolean, CharacterStats),
  55. replyTo: ActorRef[Unit]
  56. ) extends Command
  57. // private case class InternalTakeDamage(old: Int, value: Int) extends Command
  58. private case class LogError(ex: Throwable) extends Command
  59. class Props(
  60. val playerEventBus: GameEventBus[PlayerEvent],
  61. val tickEventBus: GameEventBus[TickEvent],
  62. val imMovementActorBehavior: Behavior[ImMovementActor.Command],
  63. val scheduler: AsyncScheduler
  64. ) {
  65. def behavior =
  66. Behaviors.logMessages(
  67. LogOptions()
  68. .withLevel(Level.DEBUG)
  69. .withLogger(
  70. Logger[PlayerActorSupervisor].underlying
  71. ),
  72. Behaviors
  73. .setup[Command] { ctx =>
  74. ctx.log.infoP("Starting PlayerActor")
  75. // spawn children actors
  76. val playerMovementActor =
  77. ctx.spawnN(
  78. Behaviors
  79. .supervise(imMovementActorBehavior)
  80. .onFailure[Exception](
  81. SupervisorStrategy.restart.withLimit(2, 100.millis)
  82. ),
  83. Dispatchers.jmeDispatcher
  84. )
  85. val playerStatsActor =
  86. ctx.spawnN(
  87. new StatsActor.Props(
  88. CharacterStats.Health(100),
  89. CharacterStats.Stamina(100)
  90. ).behavior
  91. )
  92. val playerMovementEl = ctx.spawnN(
  93. Behaviors
  94. .supervise(PlayerMovementEventListener(playerMovementActor))
  95. .onFailure[Exception](
  96. SupervisorStrategy.restart.withLimit(2, 100.millis)
  97. )
  98. )
  99. val renderTickEl = {
  100. val behavior: Behavior[RenderTick.type] =
  101. Behaviors.setup(ctx =>
  102. Behaviors
  103. .receiveMessage[RenderTick.type] {
  104. case RenderTick =>
  105. playerMovementActor ! ImMovementActor.Tick
  106. // playerCameraActor ! PlayerCameraActor.Tick
  107. Behaviors.same
  108. }
  109. .receiveSignal {
  110. case (_, PostStop) =>
  111. ctx.log.infoP("stopped")
  112. Behaviors.same
  113. }
  114. )
  115. ctx.spawn(behavior, "playerMovementTickListener")
  116. }
  117. //init listeners
  118. playerEventBus ! EventBus.Subscribe(playerMovementEl)
  119. tickEventBus ! EventBus.Subscribe(renderTickEl)
  120. new PlayerActorSupervisor(
  121. ctx,
  122. this,
  123. Children(playerMovementActor, playerStatsActor),
  124. ConcurrentSubject.publish(
  125. OverflowStrategy.DropOldAndSignal(
  126. 50,
  127. dropped => Coeval.pure(None)
  128. )
  129. )(scheduler.value),
  130. AsyncQueue.bounded(10)(scheduler.value)
  131. ).aliveState
  132. }
  133. )
  134. }
  135. case class Children(
  136. movementActor: ActorRef[ImMovementActor.Command],
  137. statsActor: ActorRef[StatsActor.Command]
  138. )
  139. }
  140. class PlayerActorSupervisor(
  141. ctx: ActorContext[PlayerActorSupervisor.Command],
  142. props: PlayerActorSupervisor.Props,
  143. children: PlayerActorSupervisor.Children,
  144. statsSubject: ConcurrentSubject[CharacterStats, CharacterStats],
  145. statsQueue: AsyncQueue[CharacterStats]
  146. ) {
  147. import PlayerActorSupervisor._
  148. implicit val timeout = Timeout(1.second)
  149. val aliveState =
  150. Behaviors
  151. .receiveMessage[Command] {
  152. case TakeDamage(value) =>
  153. // children.movementActor ! ImMovementActor.MovedDown(true)
  154. // ctx.ask(children.statsActor, StatsActor.CurrentStats(_)) {
  155. // case Success(status) => InternalTakeDamage(status.hp, value)
  156. // case Failure(ex) => LogError(ex)
  157. // }
  158. ctx.ask(children.statsActor, StatsActor.TakeDamageResult(value, _)) {
  159. case Success(response) => DamageResponse(response)
  160. case Failure(ex) => LogError(ex)
  161. }
  162. Behaviors.same
  163. case TakeDamage2(value, replyTo) =>
  164. // children.movementActor ! ImMovementActor.MovedDown(true)
  165. // ctx.ask(children.statsActor, StatsActor.CurrentStats(_)) {
  166. // case Success(status) => InternalTakeDamage(status.hp, value)
  167. // case Failure(ex) => LogError(ex)
  168. // }
  169. ctx.ask(children.statsActor, StatsActor.TakeDamageResult(value, _)) {
  170. case Success(response) => DamageResponse2(response, replyTo)
  171. case Failure(ex) => LogError(ex)
  172. }
  173. Behaviors.same
  174. case CurrentStats(replyTo) =>
  175. // ctx.ask(children.statsActor, StatsActor.CurrentStats())
  176. children.statsActor ! StatsActor.CurrentStats(replyTo)
  177. Behaviors.same
  178. case Heal(value) =>
  179. children.statsActor ! StatsActor.HealResult(value)
  180. Behaviors.same
  181. case GetStatus(replyTo) =>
  182. replyTo ! Status.Alive
  183. Behaviors.same
  184. // case _ => Behaviors.unhandled
  185. // case InternalTakeDamage(hp, damage) =>
  186. // if (hp - damage <= 0) dead
  187. // else {
  188. // children.statsActor ! StatsActor.TakeDamage(damage)
  189. // Behaviors.same
  190. // }
  191. case GetStatsObservable(replyTo) =>
  192. replyTo ! statsSubject
  193. Behaviors.same
  194. case GetStatsObservable2(replyTo) =>
  195. import monix.{eval => me}
  196. replyTo ! Observable.repeatEvalF(
  197. me.Task.deferFuture(statsQueue.poll())
  198. )
  199. Behaviors.same
  200. case DamageResponse(response) =>
  201. response match {
  202. case (dead, state) =>
  203. if (dead) ctx.self ! Die
  204. statsSubject.onNext(state)
  205. }
  206. Behaviors.same
  207. case DamageResponse2(response, replyTo) =>
  208. response match {
  209. case (dead, stats) =>
  210. if (dead) ctx.self ! Die
  211. statsQueue
  212. .offer(stats)
  213. .foreach(_ => replyTo ! ())(props.scheduler.value)
  214. }
  215. Behaviors.same
  216. case Die => deadState
  217. case LogError(ex) =>
  218. ctx.log.error(ex.getMessage)
  219. Behaviors.same
  220. }
  221. .receiveSignal {
  222. case (_, PostStop) =>
  223. ctx.log.infoP("stopped")
  224. statsSubject.onComplete()
  225. Behaviors.same
  226. }
  227. val deadState = Behaviors
  228. .receiveMessage[Command] {
  229. // case TakeDamage(value) =>
  230. // children.statsActor ! StatsActor.TakeDamage(value)
  231. // // children.movementActor ! ImMovementActor.MovedDown(true)
  232. // Behaviors.same
  233. // case CurrentStats(replyTo) =>
  234. // // ctx.ask(children.statsActor, StatsActor.CurrentStats())
  235. // children.statsActor ! StatsActor.CurrentStats(replyTo)
  236. // Behaviors.same
  237. // case Heal(_) =>
  238. // Behaviors.same
  239. case CurrentStats(replyTo) =>
  240. // ctx.ask(children.statsActor, StatsActor.CurrentStats())
  241. children.statsActor ! StatsActor.CurrentStats(replyTo)
  242. Behaviors.same
  243. case GetStatus(replyTo) =>
  244. replyTo ! Status.Dead
  245. Behaviors.same
  246. case _ => Behaviors.unhandled
  247. }
  248. .receiveSignal {
  249. case (_, PostStop) =>
  250. ctx.log.infoP("stopped")
  251. statsSubject.onComplete()
  252. Behaviors.same
  253. }
  254. }