doors open and async close now via LocalEnvironment and DoorCloseActor

This commit is contained in:
FateJH 2017-10-11 19:50:50 -04:00
parent 1c41972d69
commit 74b718c536
7 changed files with 138 additions and 94 deletions

View file

@ -33,6 +33,7 @@ class Door(ddef : DoorDefinition) extends PlanetSideServerObject {
Door.OpenEvent()
}
else if(openState) {
openState = false
Door.CloseEvent()
}
else {

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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

View file

@ -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()
}

View file

@ -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")
}

View file

@ -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) =>