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.

304 lines
9.6 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
  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.SupervisorStrategy
  8. import akka.actor.typed.scaladsl.ActorContext
  9. import akka.actor.typed.scaladsl.Behaviors
  10. import akka.util.Timeout
  11. import cats.syntax.show._
  12. import monix.execution.CancelableFuture
  13. import monix.execution.CancelablePromise
  14. import wow.doge.mygame.game.subsystems.movement.CanMove
  15. import wow.doge.mygame.implicits._
  16. import wow.doge.mygame.math.ImVector3f
  17. import wow.doge.mygame.subsystems.events.EntityMovementEvent
  18. import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedDown
  19. import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedLeft
  20. import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedRight
  21. import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedUp
  22. import wow.doge.mygame.subsystems.events.Event
  23. import wow.doge.mygame.subsystems.events.EventBus
  24. import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
  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 wow.doge.mygame.utils.GenericTimerActor
  29. object NpcActorSupervisor {
  30. sealed trait Command
  31. final case class Move(pos: ImVector3f) extends Command
  32. private final case class InternalMove(
  33. move: Move,
  34. signal: CancelableFuture[NpcMovementActor.DoneMoving.type]
  35. ) extends Command
  36. private case object DoneMoving extends Command
  37. private case class LogError(err: Throwable) extends Command
  38. private case class MovementFailed(err: Throwable) extends Command
  39. final case class Props(
  40. npcMovementActorBehavior: Behavior[NpcMovementActor.Command],
  41. npcName: String,
  42. initialPos: ImVector3f
  43. ) {
  44. def behavior =
  45. Behaviors.withMdc(Map("actorName" -> npcName))(
  46. Behaviors.setup[Command] { ctx =>
  47. val npcMovementActor = ctx.spawn(
  48. (npcMovementActorBehavior),
  49. s"npc-${npcName}-NpcMovementActor"
  50. )
  51. new NpcActorSupervisor(ctx, this, Children(npcMovementActor))
  52. .idle(State())
  53. }
  54. )
  55. }
  56. final case class State()
  57. final case class Children(
  58. npcMovementActor: ActorRef[NpcMovementActor.Command]
  59. )
  60. }
  61. class NpcActorSupervisor(
  62. ctx: ActorContext[NpcActorSupervisor.Command],
  63. props: NpcActorSupervisor.Props,
  64. children: NpcActorSupervisor.Children
  65. ) {
  66. import NpcActorSupervisor._
  67. implicit val timeout = Timeout(1.second)
  68. val movementTimer = ctx.spawn(
  69. GenericTimerActor
  70. .Props(
  71. children.npcMovementActor,
  72. NpcMovementActor.MovementTick,
  73. 100.millis
  74. )
  75. .behavior,
  76. s"npc-${props.npcName}-NpcActorTimer"
  77. )
  78. def idle(state: State): Behavior[NpcActorSupervisor.Command] =
  79. Behaviors.setup { _ =>
  80. ctx.log.debugP(s"npcActor-${props.npcName}: Entered Idle State")
  81. Behaviors.receiveMessage[Command] {
  82. case m @ Move(pos) =>
  83. ctx.ask(
  84. children.npcMovementActor,
  85. NpcMovementActor.MoveTo(pos, _)
  86. ) {
  87. case Success(signal) => InternalMove(m, signal)
  88. case Failure(exception) => LogError(exception)
  89. }
  90. Behaviors.same
  91. case InternalMove(move, signal) =>
  92. moving(state, move.pos, signal)
  93. case LogError(err) =>
  94. logError(err)
  95. Behaviors.same
  96. case _ => Behaviors.unhandled
  97. }
  98. }
  99. def moving(
  100. state: State,
  101. targetPos: ImVector3f,
  102. signal: CancelableFuture[NpcMovementActor.DoneMoving.type]
  103. ): Behavior[NpcActorSupervisor.Command] =
  104. Behaviors.setup { _ =>
  105. ctx.log.debugP(s"npcActor-${props.npcName}: Entered Moving State")
  106. movementTimer ! GenericTimerActor.Start
  107. ctx.pipeToSelf(signal) {
  108. case Success(value) => DoneMoving
  109. case Failure(exception) => MovementFailed(exception)
  110. }
  111. Behaviors.receiveMessagePartial[Command] {
  112. case LogError(err) =>
  113. logError(err)
  114. Behaviors.same
  115. case MovementFailed(err) =>
  116. ctx.self ! LogError(err)
  117. movementTimer ! GenericTimerActor.Stop
  118. idle(state)
  119. case m @ Move(pos) =>
  120. movementTimer ! GenericTimerActor.Stop
  121. children.npcMovementActor ! NpcMovementActor.StopMoving
  122. // new movement request received, cancel previous request
  123. signal.cancel()
  124. ctx.ask(
  125. children.npcMovementActor,
  126. NpcMovementActor.MoveTo(pos, _)
  127. ) {
  128. case Success(signal) => InternalMove(m, signal)
  129. case Failure(exception) => MovementFailed(exception)
  130. }
  131. Behaviors.same
  132. case InternalMove(move, signal) =>
  133. moving(state, targetPos, signal)
  134. case DoneMoving =>
  135. movementTimer ! GenericTimerActor.Stop
  136. idle(state)
  137. }
  138. }
  139. def logError(err: Throwable) =
  140. ctx.log.errorP(s"npcActor-${props.npcName}: " + err.getMessage)
  141. }
  142. object NpcMovementActor {
  143. case object DoneMoving
  144. sealed trait Command
  145. case class AskPosition(replyTo: ActorRef[ImVector3f]) extends Command
  146. case object MovementTick extends Command
  147. case object StopMoving extends Command
  148. case class MoveTo(
  149. target: ImVector3f,
  150. doneSignal: ActorRef[CancelableFuture[DoneMoving.type]]
  151. ) extends Command
  152. final class Props[T: CanMove](
  153. val enqueueR: Function1[() => Unit, Unit],
  154. val initialPos: ImVector3f,
  155. // val tickEventBus: GameEventBus[TickEvent],
  156. val npcName: String,
  157. val movable: T
  158. ) {
  159. def behavior =
  160. Behaviors.setup[Command] { ctx =>
  161. new NpcMovementActor(ctx, this).receive(State())
  162. }
  163. }
  164. final case class State()
  165. }
  166. class NpcMovementActor[T](
  167. ctx: ActorContext[NpcMovementActor.Command],
  168. props: NpcMovementActor.Props[T]
  169. )(implicit cm: CanMove[T]) {
  170. import NpcMovementActor._
  171. def location = cm.location(props.movable)
  172. def receive(state: State): Behavior[NpcMovementActor.Command] =
  173. Behaviors.receiveMessagePartial {
  174. case AskPosition(replyTo) =>
  175. replyTo ! location
  176. Behaviors.same
  177. case MoveTo(
  178. target: ImVector3f,
  179. replyTo: ActorRef[CancelableFuture[DoneMoving.type]]
  180. ) =>
  181. props.enqueueR(() => cm.move(props.movable, target - location))
  182. val p = CancelablePromise[DoneMoving.type]()
  183. replyTo ! p.future
  184. ticking(p, target, state)
  185. }
  186. def ticking(
  187. reachDestination: CancelablePromise[DoneMoving.type],
  188. targetPos: ImVector3f,
  189. state: State
  190. ): Behavior[NpcMovementActor.Command] =
  191. Behaviors.receiveMessagePartial {
  192. case StopMoving =>
  193. ctx.log.debugP(
  194. show"npcActor-${props.npcName}: Position at Stop = " + location
  195. )
  196. props.enqueueR(() => cm.stop(props.movable))
  197. receive(state)
  198. case MovementTick =>
  199. val dst = ImVector3f.manhattanDst(targetPos, location)
  200. if (dst <= 10f) {
  201. ctx.self ! StopMoving
  202. reachDestination.success(DoneMoving)
  203. } else {
  204. ctx.log.traceP(
  205. show"npcActor-${props.npcName}: Difference = " + dst.formatted(
  206. "%.2f"
  207. )
  208. )
  209. ctx.log.traceP(
  210. show"npcActor-${props.npcName}: Current pos = " + location
  211. )
  212. }
  213. Behaviors.same
  214. }
  215. }
  216. object NpcMovementActorNotUsed {
  217. sealed trait Command
  218. final case class Props(
  219. imMovementActorBehavior: Behavior[ImMovementActor.Command],
  220. npcName: String,
  221. // movementActor: ActorRef[ImMovementActor.Command],
  222. mainEventBus: ActorRef[
  223. EventBus.Command[Event]
  224. ],
  225. tickEventBus: GameEventBus[TickEvent]
  226. ) {
  227. def behavior: Behavior[Command] =
  228. Behaviors.setup { ctx =>
  229. val movementActor = ctx.spawn(
  230. Behaviors
  231. .supervise(imMovementActorBehavior)
  232. .onFailure[Exception](SupervisorStrategy.restart),
  233. s"npc-${npcName}-MovementActorChild"
  234. )
  235. val npcMovementElBehavior = Behaviors.receiveMessagePartial[Event] {
  236. case event: EntityMovementEvent =>
  237. event match {
  238. case MovedLeft(name, pressed) =>
  239. if (name == npcName)
  240. movementActor ! ImMovementActor.MovedLeft(pressed)
  241. Behaviors.same
  242. case MovedUp(name, pressed) =>
  243. if (name == npcName)
  244. movementActor ! ImMovementActor.MovedUp(pressed)
  245. Behaviors.same
  246. case MovedRight(name, pressed) =>
  247. if (name == npcName)
  248. movementActor ! ImMovementActor.MovedRight(pressed)
  249. Behaviors.same
  250. case MovedDown(name, pressed) =>
  251. if (name == npcName)
  252. movementActor ! ImMovementActor.MovedDown(pressed)
  253. Behaviors.same
  254. }
  255. }
  256. val npcMovementEl = ctx.spawn(
  257. Behaviors
  258. .supervise(npcMovementElBehavior)
  259. .onFailure[Exception](SupervisorStrategy.restart),
  260. s"npc-${npcName}-MovementEventHandler"
  261. )
  262. val renderTickElBehavior =
  263. Behaviors.receiveMessage[RenderTick.type] {
  264. case RenderTick =>
  265. movementActor ! ImMovementActor.Tick
  266. Behaviors.same
  267. }
  268. val renderTickEl =
  269. ctx.spawn(
  270. renderTickElBehavior,
  271. s"npc-${npcName}-MovementTickListener"
  272. )
  273. mainEventBus ! EventBus.Subscribe(npcMovementEl)
  274. tickEventBus ! EventBus.Subscribe(renderTickEl)
  275. Behaviors.receiveMessage(msg => Behaviors.same)
  276. }
  277. }
  278. }