package wow.doge.mygame.game.entities import scala.concurrent.duration._ import scala.util.Failure import scala.util.Success import akka.actor.typed.ActorRef import akka.actor.typed.Behavior import akka.actor.typed.SupervisorStrategy import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors import akka.util.Timeout import monix.execution.CancelableFuture import monix.execution.CancelablePromise import wow.doge.mygame.game.subsystems.movement.CanMove import wow.doge.mygame.implicits._ import wow.doge.mygame.math.ImVector3f import wow.doge.mygame.subsystems.events.EntityMovementEvent import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedDown import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedLeft import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedRight import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedUp import wow.doge.mygame.subsystems.events.Event import wow.doge.mygame.subsystems.events.EventBus import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.events.TickEvent.RenderTick import wow.doge.mygame.subsystems.movement.ImMovementActor object NpcActorSupervisor { sealed trait Command final case class Move(pos: ImVector3f) extends Command private final case class InternalMove( move: Move, signal: CancelableFuture[NpcMovementActor2.DoneMoving.type] ) extends Command private case object DoneMoving extends Command // private case class MovementResponse(response: CancelableFuture[_]) extends Command private case class LogError(err: Throwable) extends Command private case object NoOp extends Command final case class Props( npcMovementActorBehavior: Behavior[NpcMovementActor2.Command], npcName: String, initialPos: ImVector3f ) { def create = Behaviors.setup[Command] { ctx => val npcMovementActor = ctx.spawn( (npcMovementActorBehavior), s"npc-${npcName}-NpcMovementActor" ) new NpcActorSupervisor(ctx, this, Children(npcMovementActor)) .idle(State()) } } final case class State( ) final case class Children( npcMovementActor: ActorRef[NpcMovementActor2.Command] ) } class NpcActorSupervisor( ctx: ActorContext[NpcActorSupervisor.Command], props: NpcActorSupervisor.Props, children: NpcActorSupervisor.Children ) { import NpcActorSupervisor._ implicit val timeout = Timeout(1.second) private val movementTimer = ctx.spawn( GenericTimerActor .Props( children.npcMovementActor, NpcMovementActor2.MovementTick, 100.millis ) .create, s"npc-John-NpcActorTimer" ) def idle(state: State): Behavior[NpcActorSupervisor.Command] = Behaviors.setup { _ => ctx.log.info("Inside Idle State") Behaviors.receiveMessage[Command] { case m @ Move(pos) => ctx.ask( children.npcMovementActor, NpcMovementActor2.MoveTo(pos, _) ) { case Success(signal) => InternalMove(m, signal) case Failure(exception) => LogError(exception) } Behaviors.same case InternalMove(move, signal) => moving(state, move.pos, signal) case LogError(err) => ctx.log.warn(err.getMessage()) Behaviors.same case _ => Behaviors.unhandled } } def moving( state: State, targetPos: ImVector3f, signal: CancelableFuture[NpcMovementActor2.DoneMoving.type] ): Behavior[NpcActorSupervisor.Command] = Behaviors.setup { _ => movementTimer ! GenericTimerActor.Start // ctx // .ask(state.npcMovementActor, NpcMovementActor2.MoveTo(targetPos, _))( // _.fold(LogError(_), MovementResponse(_)) // ) ctx.pipeToSelf(signal) { case Success(value) => DoneMoving case Failure(exception) => LogError(exception) } Behaviors.receiveMessagePartial[Command] { case LogError(err) => ctx.log.error(err.getMessage()) Behaviors.same case m @ Move(pos) => movementTimer ! GenericTimerActor.Stop children.npcMovementActor ! NpcMovementActor2.StopMoving signal.cancel() ctx.ask( children.npcMovementActor, NpcMovementActor2.MoveTo(pos, _) ) { case Success(signal) => InternalMove(m, signal) case Failure(exception) => LogError(exception) } Behaviors.same case InternalMove(move, signal) => moving(state, targetPos, signal) case NoOp => Behaviors.same // case MovementResponse(x: CancelableFuture[_]) => // // ctx.pipeToSelf(x)(_.) case DoneMoving => movementTimer ! GenericTimerActor.Stop idle(state) } } } object NpcMovementActor2 { case object DoneMoving sealed trait Command case class AskPosition(replyTo: ActorRef[ImVector3f]) extends Command case object MovementTick extends Command case object StopMoving extends Command case class MoveTo( target: ImVector3f, doneSignal: ActorRef[CancelableFuture[DoneMoving.type]] ) extends Command final class Props[T: CanMove]( val enqueueR: Function1[() => Unit, Unit], val initialPos: ImVector3f, // val tickEventBus: GameEventBus[TickEvent], val movable: T ) { def create = Behaviors.setup[Command] { ctx => new NpcMovementActor2(ctx, this).receive(State()) } } final case class State() } class NpcMovementActor2[T]( ctx: ActorContext[NpcMovementActor2.Command], props: NpcMovementActor2.Props[T] )(implicit cm: CanMove[T]) { import NpcMovementActor2._ def location = cm.location(props.movable) def receive( state: State ): Behavior[NpcMovementActor2.Command] = Behaviors.receiveMessagePartial { case AskPosition(replyTo) => replyTo ! location Behaviors.same case StopMoving => ctx.log.debug( "Position at Stop = " + location.toString ) props.enqueueR(() => cm.stop(props.movable)) receive(state) case MoveTo( target: ImVector3f, replyTo: ActorRef[CancelableFuture[DoneMoving.type]] ) => props.enqueueR(() => cm.move(props.movable, target - location)) val p = CancelablePromise[DoneMoving.type]() replyTo ! p.future ticking(p, target, state) } def ticking( reachDestination: CancelablePromise[DoneMoving.type], targetPos: ImVector3f, state: State ): Behavior[NpcMovementActor2.Command] = Behaviors.receiveMessagePartial { case StopMoving => ctx.log.debug( "Position at Stop = " + location.toString ) props.enqueueR(() => cm.stop(props.movable)) receive(state) case MovementTick => val dst = ImVector3f.dst(targetPos, location) if (dst <= 10f) { ctx.self ! StopMoving reachDestination.success(DoneMoving) receive(state) } else { ctx.log.trace("Difference = " + dst.toString()) ctx.log.trace("Current pos = " + location.toString()) Behaviors.same } } } object NpcMovementActor { sealed trait Command final case class Props( imMovementActorBehavior: Behavior[ImMovementActor.Command], npcName: String, // movementActor: ActorRef[ImMovementActor.Command], mainEventBus: ActorRef[ EventBus.Command[Event] ], tickEventBus: GameEventBus[TickEvent] ) { def create: Behavior[Command] = Behaviors.setup { ctx => val movementActor = ctx.spawn( Behaviors .supervise(imMovementActorBehavior) .onFailure[Exception](SupervisorStrategy.restart), s"npc-${npcName}-MovementActorChild" ) val npcMovementElBehavior = Behaviors.receiveMessagePartial[Event] { case event: EntityMovementEvent => event match { case MovedLeft(name, pressed) => if (name == npcName) movementActor ! ImMovementActor.MovedLeft(pressed) Behaviors.same case MovedUp(name, pressed) => if (name == npcName) movementActor ! ImMovementActor.MovedUp(pressed) Behaviors.same case MovedRight(name, pressed) => if (name == npcName) movementActor ! ImMovementActor.MovedRight(pressed) Behaviors.same case MovedDown(name, pressed) => if (name == npcName) movementActor ! ImMovementActor.MovedDown(pressed) Behaviors.same } } val npcMovementEl = ctx.spawn( Behaviors .supervise(npcMovementElBehavior) .onFailure[Exception](SupervisorStrategy.restart), s"npc-${npcName}-MovementEventHandler" ) val renderTickElBehavior = Behaviors.receiveMessage[RenderTick.type] { case RenderTick => movementActor ! ImMovementActor.Tick Behaviors.same } val renderTickEl = ctx.spawn( renderTickElBehavior, s"npc-${npcName}-MovementTickListener" ) mainEventBus ! EventBus.Subscribe(npcMovementEl) tickEventBus ! EventBus.Subscribe(renderTickEl) Behaviors.receiveMessage { msg => Behaviors.same } } } }