From 74b718c536474b6ea6c3220c84ef97e6296f5aee Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 11 Oct 2017 19:50:50 -0400 Subject: [PATCH] doors open and async close now via LocalEnvironment and DoorCloseActor --- .../objects/serverobject/doors/Door.scala | 1 + .../serverobject/doors/DoorControl.scala | 1 + .../objects/zones/DoorCloseActor.scala | 87 +++++++++++++++++++ .../net/psforever/objects/zones/Zone.scala | 16 ---- .../objects/zones/ZoneDoorActor.scala | 69 --------------- pslogin/src/main/scala/LocalService.scala | 27 ++++-- .../src/main/scala/WorldSessionActor.scala | 31 ++++++- 7 files changed, 138 insertions(+), 94 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/zones/DoorCloseActor.scala delete mode 100644 common/src/main/scala/net/psforever/objects/zones/ZoneDoorActor.scala diff --git a/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala b/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala index 25c50e856..5971fbaa2 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala @@ -33,6 +33,7 @@ class Door(ddef : DoorDefinition) extends PlanetSideServerObject { Door.OpenEvent() } else if(openState) { + openState = false Door.CloseEvent() } else { diff --git a/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala index 158752918..ac3832f03 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala @@ -11,6 +11,7 @@ class DoorControl(door : Door) extends Actor { def receive : Receive = { case Door.Use(player, msg) => sender ! Door.DoorMessage(player, msg, door.Use(player, msg)) + case _ => sender ! Door.NoEvent() } diff --git a/common/src/main/scala/net/psforever/objects/zones/DoorCloseActor.scala b/common/src/main/scala/net/psforever/objects/zones/DoorCloseActor.scala new file mode 100644 index 000000000..b1604b75f --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/zones/DoorCloseActor.scala @@ -0,0 +1,87 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.zones + +import akka.actor.{Actor, Cancellable} +import net.psforever.objects.serverobject.doors.Door +import net.psforever.packet.game.PlanetSideGUID + +import scala.annotation.tailrec +import scala.concurrent.duration._ + +class DoorCloseActor() extends Actor { + import DoorCloseActor._ + private var doorCloserTrigger : Cancellable = DefaultCloser + private var openDoors : List[DoorEntry] = Nil + //private[this] val log = org.log4s.getLogger + + def receive : Receive = { + case DoorIsOpen(door, zone, time) => + openDoors = openDoors :+ DoorEntry(door, zone, time) + if(openDoors.size == 1) { + import scala.concurrent.ExecutionContext.Implicits.global + doorCloserTrigger = context.system.scheduler.scheduleOnce(timeout, self, DoorCloseActor.TryCloseDoors()) + } + + case TryCloseDoors() => + doorCloserTrigger.cancel + val now : Long = System.nanoTime + //TODO we can just walk across the list of doors and extract only the first few entries + val (doorsToClose, doorsLeftOpen) = recursivePartitionDoors(openDoors.iterator, now) + openDoors = doorsLeftOpen + doorsToClose.foreach(entry => { + entry.door.Open = false //permissible + context.parent ! DoorCloseActor.CloseTheDoor(entry.door.GUID, entry.zone.Id) + }) + + if(doorsLeftOpen.nonEmpty) { + val short_timeout : FiniteDuration = math.max(1, timeout_time - (now - doorsLeftOpen.head.opened_at_time)) nanoseconds + import scala.concurrent.ExecutionContext.Implicits.global + doorCloserTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, DoorCloseActor.TryCloseDoors()) + } + + case _ => ; + } + + /** + * na + * @param iter na + * @param now na + * @param list na + * @see `List.partition` + * @return a `Tuple` of two `Lists`: + * the entries for all `Door`s that are closing, + * and the entries for all doors that are staying open + */ + @tailrec private def recursivePartitionDoors(iter : Iterator[DoorEntry], now : Long, list : List[DoorEntry] = Nil) : (List[DoorEntry], List[DoorEntry]) = { + if(!iter.hasNext) { + (list, iter.toList) + } + else { + val entry = iter.next() + if(now - entry.opened_at_time >= timeout_time) { + recursivePartitionDoors(iter, now, list :+ entry) + } + else { + (list, entry +: iter.toList) + } + } + } +} + +object DoorCloseActor { + private final val timeout_time : Long = 5000000000L //nanoseconds + private final val timeout : FiniteDuration = timeout_time nanoseconds + + private final val DefaultCloser : Cancellable = new Cancellable() { + override def cancel : Boolean = true + override def isCancelled : Boolean = true + } + + final case class DoorIsOpen(door : Door, zone : Zone, opened_at_time : Long = System.nanoTime()) + + final case class CloseTheDoor(door_guid : PlanetSideGUID, zone_id : String) + + private final case class DoorEntry(door : Door, zone : Zone, opened_at_time : Long) + + private final case class TryCloseDoors() +} diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala index bdc25b9dc..68415c50a 100644 --- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -48,10 +48,6 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { private val equipmentOnGround : ListBuffer[Equipment] = ListBuffer[Equipment]() /** Used by the `Zone` to coordinate `Equipment` dropping and collection requests. */ private var ground : ActorRef = ActorRef.noSender - /** */ - private var doors : ActorRef = ActorRef.noSender - /** */ - private var events : ActorRef = ActorRef.noSender private var bases : List[Base] = List() @@ -72,7 +68,6 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { implicit val guid : NumberPoolHub = this.guid //passed into builderObject.Build implicitly accessor = context.actorOf(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystem.AllocateNumberPoolActors(guid)), s"$Id-uns") ground = context.actorOf(Props(classOf[ZoneGroundActor], equipmentOnGround), s"$Id-ground") - doors = context.actorOf(Props(classOf[ZoneDoorActor], this), s"$Id-doors") Map.LocalObjects.foreach({ builderObject => builderObject.Build @@ -179,17 +174,6 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { */ def Ground : ActorRef = ground - def Doors : ActorRef = doors - - def Events : ActorRef = events - - def Events_=(zoneActor : ActorRef) : ActorRef = { - if(events == ActorRef.noSender) { - events = zoneActor - } - Events - } - def MakeBases(num : Int) : List[Base] = { bases = (0 to num).map(id => new Base(id)).toList bases diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneDoorActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneDoorActor.scala deleted file mode 100644 index b5d4ae0c5..000000000 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneDoorActor.scala +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.zones - -import akka.actor.{Actor, Cancellable} -import net.psforever.packet.game.PlanetSideGUID - -import scala.annotation.tailrec -import scala.concurrent.duration._ - -class ZoneDoorActor(implicit val zone : Zone) extends Actor { - import ZoneDoorActor._ - private var doorCloserTrigger : Cancellable = DefaultCloser - private var openDoors : List[DoorEntry] = Nil - - def receive : Receive = { - case DoorIsOpen(guid, time) => - openDoors = openDoors :+ DoorEntry(guid, time) - if(doorCloserTrigger.isCancelled) { - import scala.concurrent.ExecutionContext.Implicits.global - doorCloserTrigger = context.system.scheduler.scheduleOnce(timeout, self, ZoneDoorActor.CloseTheDoor()) - } - - case CloseTheDoor() => - doorCloserTrigger.cancel - val now : Long = System.nanoTime - recursiveCloseDoors(openDoors.iterator, now) match { - case entry :: rest => - openDoors = rest - import scala.concurrent.ExecutionContext.Implicits.global - doorCloserTrigger = context.system.scheduler.scheduleOnce((now - entry.opened_at_time + timeout_time)*1000 milliseconds, self, ZoneDoorActor.CloseTheDoor()) - case Nil => - openDoors = Nil - } - - case _ => ; - } - - @tailrec private def recursiveCloseDoors(iter : Iterator[DoorEntry], now : Long) : List[DoorEntry] = { - if(!iter.hasNext) { - Nil - } - else { - val entry = iter.next - if(now - entry.opened_at_time < timeout_time) { - entry +: iter.toList - } - else { - //TODO close this door entry - recursiveCloseDoors(iter, now) - } - } - } -} - -object ZoneDoorActor { - private final val timeout_time = 5000 - private final val timeout : FiniteDuration = timeout_time milliseconds - - private final val DefaultCloser : Cancellable = new Cancellable() { - override def cancel : Boolean = true - override def isCancelled : Boolean = true - } - - final case class DoorIsOpen(door_guid : PlanetSideGUID, opened_at_time : Long = System.nanoTime()) - - private final case class DoorEntry(door_guid : PlanetSideGUID, opened_at_time : Long) - - private final case class CloseTheDoor() -} diff --git a/pslogin/src/main/scala/LocalService.scala b/pslogin/src/main/scala/LocalService.scala index 5337b074a..ae014ab25 100644 --- a/pslogin/src/main/scala/LocalService.scala +++ b/pslogin/src/main/scala/LocalService.scala @@ -1,17 +1,21 @@ // Copyright (c) 2017 PSForever -import akka.actor.Actor +import akka.actor.{Actor, Props} +import net.psforever.objects.serverobject.doors.Door +import net.psforever.objects.zones.{DoorCloseActor, Zone} import net.psforever.packet.game.PlanetSideGUID object LocalAction { trait Action - final case class Door(player_guid : PlanetSideGUID) extends Action + final case class DoorOpens(player_guid : PlanetSideGUID, continent : Zone, door : Door) extends Action + final case class DoorCloses(player_guid : PlanetSideGUID, door_guid : PlanetSideGUID) extends Action } object LocalServiceResponse { trait Response - final case class Door(player_guid : PlanetSideGUID) extends Response + final case class DoorOpens(door_guid : PlanetSideGUID) extends Response + final case class DoorCloses(door_guid : PlanetSideGUID) extends Response } final case class LocalServiceMessage(forChannel : String, actionMessage : LocalAction.Action) @@ -24,6 +28,7 @@ final case class LocalServiceResponse(toChannel : String, avatar_guid : PlanetSi class LocalService extends Actor { //import LocalService._ + private val doorCloser = context.actorOf(Props[DoorCloseActor], "local-door-closer") private [this] val log = org.log4s.getLogger override def preStart = { @@ -45,13 +50,25 @@ class LocalService extends Actor { case LocalServiceMessage(forChannel, action) => action match { - case LocalAction.Door(player_guid) => + case LocalAction.DoorOpens(player_guid, zone, door) => + doorCloser ! DoorCloseActor.DoorIsOpen(door, zone) LocalEvents.publish( - LocalServiceResponse(s"/$forChannel/LocalEnvironment" + forChannel, player_guid, LocalServiceResponse.Door(player_guid)) + LocalServiceResponse(s"/$forChannel/LocalEnvironment", player_guid, LocalServiceResponse.DoorOpens(door.GUID)) + ) + + case LocalAction.DoorCloses(player_guid, door_guid) => + LocalEvents.publish( + LocalServiceResponse(s"/$forChannel/LocalEnvironment", player_guid, LocalServiceResponse.DoorCloses(door_guid)) ) case _ => ; } + //response from DoorCloseActor + case DoorCloseActor.CloseTheDoor(door_guid, zone_id) => + LocalEvents.publish( + LocalServiceResponse(s"/$zone_id/LocalEnvironment", PlanetSideGUID(0), LocalServiceResponse.DoorCloses(door_guid)) + ) + case msg => log.info(s"Unhandled message $msg from $sender") } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index a78e611ae..559ea213e 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -49,6 +49,7 @@ class WorldSessionActor extends Actor with MDCContextAware { clientKeepAlive.cancel() avatarService ! Service.Leave() + localService ! Service.Leave() LivePlayerList.Remove(sessionId) match { case Some(tplayer) => if(tplayer.HasGUID) { @@ -70,7 +71,8 @@ class WorldSessionActor extends Actor with MDCContextAware { if(pipe.hasNext) { rightRef = pipe.next rightRef !> HelloFriend(sessionId, pipe) - } else { + } + else { rightRef = sender() } context.become(Started) @@ -221,13 +223,33 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ => ; } - case Door.DoorMessage(_, msg, order) => + case LocalServiceResponse(_, guid, reply) => + reply match { + case LocalServiceResponse.DoorOpens(door_guid) => + if(player.GUID != guid) { + sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(door_guid, 16))) + } + + case LocalServiceResponse.DoorCloses(door_guid) => //door closes for everyone + sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(door_guid, 17))) + } + + case Door.DoorMessage(tplayer, msg, order) => + val door_guid = msg.object_guid order match { case Door.OpenEvent() => - sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(msg.object_guid, 16))) + continent.GUID(door_guid) match { + case Some(door : Door) => + sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(door_guid, 16))) + localService ! LocalServiceMessage (continent.Id, LocalAction.DoorOpens (tplayer.GUID, continent, door) ) + + case _ => + log.warn(s"door $door_guid wanted to be opened but could not be found") + } case Door.CloseEvent() => - sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(msg.object_guid, 17))) + sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(door_guid, 17))) + localService ! LocalServiceMessage(continent.Id, LocalAction.DoorCloses(tplayer.GUID, door_guid)) case Door.NoEvent() => ; } @@ -769,6 +791,7 @@ class WorldSessionActor extends Actor with MDCContextAware { }) avatarService ! Service.Join(player.Continent) + localService ! Service.Join(player.Continent) self ! SetCurrentAvatar(player) case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) =>