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.

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