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.

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