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.5 KiB

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