From ee5d0c666cbc0ca14ed46a2ffd3dfaac4619e95d Mon Sep 17 00:00:00 2001 From: FateJH Date: Sat, 26 May 2018 01:04:38 -0400 Subject: [PATCH] replaced DelayedDeconstructionActor and DeconstructionActor with VehicleRemover, a class that does performs both tasks; all messages to, from, and used by the previous two actors have been removed and replaced with the new messages --- .../src/main/scala/WorldSessionActor.scala | 17 +- .../main/scala/services/RemoverActor.scala | 32 +- .../avatar/support/CorpseRemovalActor.scala | 4 +- .../avatar/support/DroppedItemRemover.scala | 2 +- .../services/vehicle/VehicleService.scala | 40 +-- .../vehicle/VehicleServiceMessage.scala | 6 +- .../vehicle/support/DeconstructionActor.scala | 276 ------------------ .../support/DelayedDeconstructionActor.scala | 114 -------- .../vehicle/support/VehicleRemover.scala | 56 ++++ 9 files changed, 102 insertions(+), 445 deletions(-) delete mode 100644 pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala delete mode 100644 pslogin/src/main/scala/services/vehicle/support/DelayedDeconstructionActor.scala create mode 100644 pslogin/src/main/scala/services/vehicle/support/VehicleRemover.scala diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index fb16f896..9633cac2 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -184,7 +184,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(vehicle : Vehicle) => vehicle.Seat(vehicle.PassengerInSeat(player).get).get.Occupant = None if(vehicle.Seats.values.count(_.isOccupied) == 0) { - vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 600L) //start vehicle decay (10m) + vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent)) //start vehicle decay } vehicleService ! Service.Leave(Some(s"${vehicle.Actor}")) @@ -644,7 +644,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val obj_guid : PlanetSideGUID = obj.GUID val player_guid : PlanetSideGUID = tplayer.GUID log.info(s"MountVehicleMsg: $player_guid mounts $obj_guid @ $seat_num") - vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(obj_guid) //clear all deconstruction timers + vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(obj), continent)) //clear timer PlayerActionsToCancel() if(seat_num == 0) { //simplistic vehicle ownership management obj.Owner match { @@ -698,7 +698,7 @@ class WorldSessionActor extends Actor with MDCContextAware { vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, seat_num, true, obj.GUID)) } if(obj.Seats.values.count(_.isOccupied) == 0) { - vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj, continent, 600L) //start vehicle decay (10m) + vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(obj, continent)) //start vehicle decay } case Mountable.CanDismount(obj : Mountable, _) => @@ -1143,9 +1143,6 @@ class WorldSessionActor extends Actor with MDCContextAware { case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle, pad) => val vehicle_guid = vehicle.GUID - if(player.VehicleSeated.nonEmpty) { - vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) - } sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 0L)) //mount points on? //sendResponse(PlanetsideAttributeMessage(vehicle_guid, 0, 10))//vehicle.Definition.MaxHealth)) sendResponse(PlanetsideAttributeMessage(vehicle_guid, 68, 0L)) //??? @@ -1408,7 +1405,6 @@ class WorldSessionActor extends Actor with MDCContextAware { LivePlayerList.Add(sessionId, avatar) traveler = new Traveler(self, continent.Id) //PropertyOverrideMessage - sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1)) sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list sendResponse(FriendsResponse(FriendAction.InitializeFriendList, 0, true, true, Nil)) @@ -1460,6 +1456,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val guid = tplayer.GUID StartBundlingPackets() sendResponse(SetCurrentAvatarMessage(guid,0,0)) + sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on //TODO once per respawn? sendResponse(PlayerStateShiftMessage(ShiftState(1, tplayer.Position, tplayer.Orientation.z))) if(spectator) { sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, false, "", "on", None)) @@ -2284,8 +2281,8 @@ class WorldSessionActor extends Actor with MDCContextAware { if((player.VehicleOwned.contains(object_guid) && vehicle.Owner.contains(player.GUID)) || (player.Faction == vehicle.Faction && ((vehicle.Owner.isEmpty || continent.GUID(vehicle.Owner.get).isEmpty) || vehicle.Health == 0))) { - vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(object_guid) - vehicleService ! VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent) + vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(vehicle), continent)) + vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent, Some(0 seconds))) log.info(s"RequestDestroy: vehicle $object_guid") } else { @@ -2801,7 +2798,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //todo: implement auto landing procedure if the pilot bails but passengers are still present instead of deconstructing the vehicle //todo: continue flight path until aircraft crashes if no passengers present (or no passenger seats), then deconstruct. if(bailType == BailType.Bailed && seat_num == 0 && GlobalDefinitions.isFlightVehicle(obj.asInstanceOf[Vehicle].Definition)) { - vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj.asInstanceOf[Vehicle], continent, 0L) // Immediately deconstruct vehicle + vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(obj, continent, Some(0 seconds))) // Immediately deconstruct vehicle } case None => diff --git a/pslogin/src/main/scala/services/RemoverActor.scala b/pslogin/src/main/scala/services/RemoverActor.scala index 71af341d..be695d05 100644 --- a/pslogin/src/main/scala/services/RemoverActor.scala +++ b/pslogin/src/main/scala/services/RemoverActor.scala @@ -51,6 +51,8 @@ abstract class RemoverActor extends Actor { private var taskResolver : ActorRef = Actor.noSender private[this] val log = org.log4s.getLogger + def trace(msg : String) : Unit = log.trace(msg) + def debug(msg : String) : Unit = log.debug(msg) /** * Send the initial message that requests a task resolver for assisting in the removal process. @@ -99,6 +101,7 @@ abstract class RemoverActor extends Actor { if(firstHeap.isEmpty) { //we were the only entry so the event must be started from scratch firstHeap = List(entry) + trace(s"a remover task has been added: $entry") RetimeFirstTask() } else { @@ -106,17 +109,18 @@ abstract class RemoverActor extends Actor { val oldHead = firstHeap.head if(!firstHeap.exists(test => RemoverActor.Similarity(test, entry))) { firstHeap = (firstHeap :+ entry).sortBy(_.duration) + trace(s"a remover task has been added: $entry") if(oldHead != firstHeap.head) { RetimeFirstTask() } } else { - log.trace(s"$obj is already queued for removal") + trace(s"$obj is already queued for removal") } } } else { - log.trace(s"$obj either does not qualify for this Remover or is already queued") + trace(s"$obj either does not qualify for this Remover or is already queued") } case RemoverActor.HurrySpecific(targets, zone) => @@ -145,7 +149,7 @@ abstract class RemoverActor extends Actor { import scala.concurrent.ExecutionContext.Implicits.global secondTask = context.system.scheduler.scheduleOnce(SecondStandardDuration, self, RemoverActor.TryDelete()) } - log.trace(s"item removal task has found ${in.size} items to remove") + trace(s"item removal task has found ${in.size} items to remove") case RemoverActor.TryDelete() => secondTask.cancel @@ -156,7 +160,7 @@ abstract class RemoverActor extends Actor { import scala.concurrent.ExecutionContext.Implicits.global secondTask = context.system.scheduler.scheduleOnce(SecondStandardDuration, self, RemoverActor.TryDelete()) } - log.trace(s"item removal task has removed ${in.size} items") + trace(s"item removal task has removed ${in.size} items") case RemoverActor.FailureToWork(entry, ex) => log.error(s"${entry.obj} from ${entry.zone} not properly unregistered - $ex") @@ -172,11 +176,13 @@ abstract class RemoverActor extends Actor { */ def HurrySpecific(targets : List[PlanetSideGameObject], zone : Zone) : Unit = { CullTargetsFromFirstHeap(targets, zone) match { - case Nil => ; + case Nil => + debug(s"no tasks matching the targets $targets have been hurried") case list => + debug(s"the following tasks have been hurried: $list") secondTask.cancel list.foreach { FirstJob } - secondHeap = secondHeap ++ list.map { RepackageEntry } + secondHeap = secondHeap ++ list.map { RepackageEntry } import scala.concurrent.ExecutionContext.Implicits.global secondTask = context.system.scheduler.scheduleOnce(SecondStandardDuration, self, RemoverActor.TryDelete()) } @@ -186,6 +192,7 @@ abstract class RemoverActor extends Actor { * Expedite all entries from the first pool into the second. */ def HurryAll() : Unit = { + trace("all tasks have been hurried") firstTask.cancel firstHeap.foreach { FirstJob } secondHeap = secondHeap ++ firstHeap.map { RepackageEntry } @@ -199,7 +206,12 @@ abstract class RemoverActor extends Actor { * Remove specific entries from the first pool. */ def ClearSpecific(targets : List[PlanetSideGameObject], zone : Zone) : Unit = { - CullTargetsFromFirstHeap(targets, zone) + CullTargetsFromFirstHeap(targets, zone) match { + case Nil => + debug(s"no tasks matching the targets $targets have been cleared") + case list => + debug(s"the following tasks have been cleared: $list") + } } /** @@ -232,7 +244,7 @@ abstract class RemoverActor extends Actor { private def CullTargetsFromFirstHeap(targets : List[PlanetSideGameObject], zone : Zone) : List[RemoverActor.Entry] = { val culledEntries = if(targets.nonEmpty) { if(targets.size == 1) { - log.debug(s"a target submitted: ${targets.head}") + debug(s"a target submitted: ${targets.head}") //simple selection RemoverActor.recursiveFind(firstHeap.iterator, RemoverActor.Entry(targets.head, zone, 0)) match { case None => ; @@ -244,7 +256,7 @@ abstract class RemoverActor extends Actor { } } else { - log.trace(s"multiple targets submitted: $targets") + debug(s"multiple targets submitted: $targets") //cumbersome partition //a - find targets from entries val locatedTargets = for { @@ -267,7 +279,7 @@ abstract class RemoverActor extends Actor { } } else { - log.trace(s"all targets within the specified zone $zone will be submitted") + debug(s"all targets within the specified zone $zone will be submitted") //no specific targets; split on all targets in the given zone instead val (in, out) = firstHeap.partition(entry => entry.zone == zone) firstHeap = out.sortBy(_.duration) diff --git a/pslogin/src/main/scala/services/avatar/support/CorpseRemovalActor.scala b/pslogin/src/main/scala/services/avatar/support/CorpseRemovalActor.scala index 68237ab4..f75da946 100644 --- a/pslogin/src/main/scala/services/avatar/support/CorpseRemovalActor.scala +++ b/pslogin/src/main/scala/services/avatar/support/CorpseRemovalActor.scala @@ -25,9 +25,7 @@ class CorpseRemovalActor extends RemoverActor { context.parent ! AvatarServiceMessage(entry.zone.Id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, entry.obj.GUID)) } - def ClearanceTest(entry : RemoverActor.Entry) : Boolean = { - !entry.zone.Corpses.contains(entry.obj.asInstanceOf[Player]) - } + def ClearanceTest(entry : RemoverActor.Entry) : Boolean = !entry.zone.Corpses.contains(entry.obj) def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = { GUIDTask.UnregisterPlayer(entry.obj.asInstanceOf[Player])(entry.zone.GUID) diff --git a/pslogin/src/main/scala/services/avatar/support/DroppedItemRemover.scala b/pslogin/src/main/scala/services/avatar/support/DroppedItemRemover.scala index ed0fdea7..01bc9d47 100644 --- a/pslogin/src/main/scala/services/avatar/support/DroppedItemRemover.scala +++ b/pslogin/src/main/scala/services/avatar/support/DroppedItemRemover.scala @@ -25,7 +25,7 @@ class DroppedItemRemover extends RemoverActor { context.parent ! AvatarServiceMessage(entry.zone.Id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, entry.obj.GUID)) } - def ClearanceTest(entry : RemoverActor.Entry) : Boolean = true + def ClearanceTest(entry : RemoverActor.Entry) : Boolean = !entry.zone.EquipmentOnGround.contains(entry.obj) def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = { GUIDTask.UnregisterEquipment(entry.obj.asInstanceOf[Equipment])(entry.zone.GUID) diff --git a/pslogin/src/main/scala/services/vehicle/VehicleService.scala b/pslogin/src/main/scala/services/vehicle/VehicleService.scala index 6b16ec2b..e6c548ee 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleService.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleService.scala @@ -4,15 +4,12 @@ package services.vehicle import akka.actor.{Actor, ActorRef, Props} import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.objects.zones.Zone -import services.vehicle.support.{DeconstructionActor, DelayedDeconstructionActor} +import services.vehicle.support.VehicleRemover import net.psforever.types.DriveState - -import services.{GenericEventBus, Service} +import services.{GenericEventBus, RemoverActor, Service} class VehicleService extends Actor { - private val vehicleDecon : ActorRef = context.actorOf(Props[DeconstructionActor], "vehicle-decon-agent") - private val vehicleDelayedDecon : ActorRef = context.actorOf(Props[DelayedDeconstructionActor], "vehicle-delayed-decon-agent") - vehicleDecon ! DeconstructionActor.RequestTaskResolver + private val vehicleDecon : ActorRef = context.actorOf(Props[VehicleRemover], "vehicle-decon-agent") private [this] val log = org.log4s.getLogger override def preStart = { @@ -87,6 +84,11 @@ class VehicleService extends Actor { VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.StowEquipment(vehicle_guid, slot, definition.ObjectId, item.GUID, definition.Packet.DetailedConstructorData(item).get)) ) + case VehicleAction.UnloadVehicle(player_guid, continent, vehicle) => + vehicleDecon ! RemoverActor.ClearSpecific(List(vehicle), continent) //precaution + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.UnloadVehicle(vehicle.GUID)) + ) case VehicleAction.UnstowEquipment(player_guid, item_guid) => VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.UnstowEquipment(item_guid)) @@ -102,23 +104,9 @@ class VehicleService extends Actor { case _ => ; } - //message to DeconstructionActor - case VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent) => - vehicleDecon ! DeconstructionActor.RequestDeleteVehicle(vehicle, continent) - - //message to DelayedDeconstructionActor - case VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, zone, timeAlive) => - vehicleDelayedDecon ! DelayedDeconstructionActor.ScheduleDeconstruction(vehicle, zone, timeAlive) - - //message to DelayedDeconstructionActor - case VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) => - vehicleDelayedDecon ! DelayedDeconstructionActor.UnscheduleDeconstruction(vehicle_guid) - - //response from DeconstructionActor - case DeconstructionActor.DeleteVehicle(vehicle_guid, zone_id) => - VehicleEvents.publish( - VehicleServiceResponse(s"/$zone_id/Vehicle", Service.defaultPlayerGUID, VehicleResponse.UnloadVehicle(vehicle_guid)) - ) + //message to VehicleRemover + case VehicleServiceMessage.Decon(msg) => + vehicleDecon forward msg //from VehicleSpawnControl case VehicleSpawnPad.ConcealPlayer(player_guid, zone_id) => @@ -159,13 +147,11 @@ class VehicleService extends Actor { VehicleEvents.publish( VehicleServiceResponse(s"/${zone.Id}/Vehicle", Service.defaultPlayerGUID, VehicleResponse.LoadVehicle(vehicle, vtype, vguid, vdata)) ) - vehicleDelayedDecon ! DelayedDeconstructionActor.UnscheduleDeconstruction(vguid) - vehicleDelayedDecon ! DelayedDeconstructionActor.ScheduleDeconstruction(vehicle, zone, 600L) //10min + vehicleDecon forward RemoverActor.AddTask(vehicle, zone) //from VehicleSpawnControl case VehicleSpawnPad.DisposeVehicle(vehicle, zone) => - vehicleDelayedDecon ! DelayedDeconstructionActor.UnscheduleDeconstruction(vehicle.GUID) - vehicleDecon ! DeconstructionActor.RequestDeleteVehicle(vehicle, zone) + vehicleDecon forward RemoverActor.HurrySpecific(List(vehicle), zone) //correspondence from WorldSessionActor case VehicleServiceMessage.AMSDeploymentChange(zone) => diff --git a/pslogin/src/main/scala/services/vehicle/VehicleServiceMessage.scala b/pslogin/src/main/scala/services/vehicle/VehicleServiceMessage.scala index 61ec6854..8ed071e0 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleServiceMessage.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleServiceMessage.scala @@ -3,16 +3,14 @@ package services.vehicle import net.psforever.objects.Vehicle import net.psforever.objects.zones.Zone -import net.psforever.packet.game.PlanetSideGUID final case class VehicleServiceMessage(forChannel : String, actionMessage : VehicleAction.Action) object VehicleServiceMessage { - final case class DelayedVehicleDeconstruction(vehicle : Vehicle, continent : Zone, timeAlive : Long) final case class GiveActorControl(vehicle : Vehicle, actorName : String) final case class RevokeActorControl(vehicle : Vehicle) - final case class RequestDeleteVehicle(vehicle : Vehicle, continent : Zone) - final case class UnscheduleDeconstruction(vehicle_guid : PlanetSideGUID) + + final case class Decon(msg : Any) final case class AMSDeploymentChange(zone : Zone) } diff --git a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala deleted file mode 100644 index 2a95b8a5..00000000 --- a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright (c) 2017 PSForever -package services.vehicle.support - -import akka.actor.{Actor, ActorRef, Cancellable} -import net.psforever.objects.{DefaultCancellable, GlobalDefinitions, Vehicle} -import net.psforever.objects.guid.TaskResolver -import net.psforever.objects.vehicles.Seat -import net.psforever.objects.zones.Zone -import net.psforever.packet.game.PlanetSideGUID -import net.psforever.types.Vector3 -import services.ServiceManager -import services.ServiceManager.Lookup -import services.vehicle.{VehicleAction, VehicleServiceMessage} - -import scala.annotation.tailrec -import scala.concurrent.duration._ - -/** - * Manage a previously-functioning vehicle as it is being deconstructed.
- *
- * A reference to a vehicle should be passed to this object as soon as it is going to be cleaned-up from the game world. - * Once accepted, only a few seconds will remain before the vehicle is deleted. - * To ensure that no players are lost in the deletion, all occupants of the vehicle are kicked out. - * Furthermore, the vehicle is rendered "dead" and inaccessible right up to the point where it is removed.
- *
- * This `Actor` is intended to sit on top of the event system that handles broadcast messaging. - */ -class DeconstructionActor extends Actor { - /** The periodic `Executor` that scraps the next vehicle on the list */ - private var scrappingProcess : Cancellable = DefaultCancellable.obj - /** A `List` of currently doomed vehicles */ - private var vehicles : List[DeconstructionActor.VehicleEntry] = Nil - /** The periodic `Executor` that cleans up the next vehicle on the list */ - private var heapEmptyProcess : Cancellable = DefaultCancellable.obj - /** A `List` of vehicles that have been removed from the game world and are awaiting deconstruction. */ - private var vehicleScrapHeap : List[DeconstructionActor.VehicleEntry] = Nil - /** The manager that helps unregister the vehicle from its current GUID scope */ - private var taskResolver : ActorRef = Actor.noSender - //private[this] val log = org.log4s.getLogger - - override def postStop() : Unit = { - super.postStop() - scrappingProcess.cancel - heapEmptyProcess.cancel - - vehicles.foreach(entry => { - RetirementTask(entry) - DestructionTask(entry) - }) - vehicleScrapHeap.foreach { DestructionTask } - } - - def receive : Receive = { - /* - ask for a resolver to deal with the GUID system - when the TaskResolver is finally delivered, switch over to a behavior that actually deals with submitted vehicles - */ - case DeconstructionActor.RequestTaskResolver => - ServiceManager.serviceManager ! Lookup("taskResolver") - - case ServiceManager.LookupResult("taskResolver", endpoint) => - taskResolver = endpoint - context.become(Processing) - - case _ => ; - } - - def Processing : Receive = { - case DeconstructionActor.RequestDeleteVehicle(vehicle, zone, time) => - if(!vehicles.exists(_.vehicle == vehicle) && !vehicleScrapHeap.exists(_.vehicle == vehicle)) { - vehicles = vehicles :+ DeconstructionActor.VehicleEntry(vehicle, zone, time) - vehicle.Actor ! Vehicle.PrepareForDeletion - //kick everyone out; this is a no-blocking manual form of MountableBehavior ! Mountable.TryDismount - vehicle.Definition.MountPoints.values.foreach(seat_num => { - val zone_id : String = zone.Id - val seat : Seat = vehicle.Seat(seat_num).get - seat.Occupant match { - case Some(tplayer) => - seat.Occupant = None - tplayer.VehicleSeated = None - if(tplayer.HasGUID) { - context.parent ! VehicleServiceMessage(zone_id, VehicleAction.KickPassenger(tplayer.GUID, 4, false, vehicle.GUID)) - } - case None => ; - } - }) - if(vehicles.size == 1) { - //we were the only entry so the event must be started from scratch - import scala.concurrent.ExecutionContext.Implicits.global - scrappingProcess = context.system.scheduler.scheduleOnce(DeconstructionActor.timeout, self, DeconstructionActor.StartDeleteVehicle()) - } - } - - case DeconstructionActor.StartDeleteVehicle() => - scrappingProcess.cancel - heapEmptyProcess.cancel - val now : Long = System.nanoTime - val (vehiclesToScrap, vehiclesRemain) = PartitionEntries(vehicles, now) - vehicles = vehiclesRemain - vehicleScrapHeap = vehicleScrapHeap ++ vehiclesToScrap //may include existing entries - vehiclesToScrap.foreach(entry => { - val vehicle = entry.vehicle - val zone = entry.zone - RetirementTask(entry) - if(vehicle.Definition == GlobalDefinitions.ams) { - import net.psforever.types.DriveState - vehicle.DeploymentState = DriveState.Mobile //internally undeployed //TODO this should be temporary? - context.parent ! VehicleServiceMessage.AMSDeploymentChange(zone) - } - taskResolver ! DeconstructionTask(vehicle, zone) - }) - - if(vehiclesRemain.nonEmpty) { - val short_timeout : FiniteDuration = math.max(1, DeconstructionActor.timeout_time - (now - vehiclesRemain.head.time)) nanoseconds - import scala.concurrent.ExecutionContext.Implicits.global - scrappingProcess = context.system.scheduler.scheduleOnce(short_timeout, self, DeconstructionActor.StartDeleteVehicle()) - } - if(vehicleScrapHeap.nonEmpty) { - import scala.concurrent.ExecutionContext.Implicits.global - heapEmptyProcess = context.system.scheduler.scheduleOnce(500 milliseconds, self, DeconstructionActor.TryDeleteVehicle()) - } - - case DeconstructionActor.TryDeleteVehicle() => - heapEmptyProcess.cancel - val (vehiclesToScrap, vehiclesRemain) = vehicleScrapHeap.partition(entry => !entry.zone.Vehicles.contains(entry.vehicle)) - vehicleScrapHeap = vehiclesRemain - vehiclesToScrap.foreach { DestructionTask } - if(vehiclesRemain.nonEmpty) { - import scala.concurrent.ExecutionContext.Implicits.global - heapEmptyProcess = context.system.scheduler.scheduleOnce(500 milliseconds, self, DeconstructionActor.TryDeleteVehicle()) - } - - case DeconstructionActor.FailureToDeleteVehicle(localVehicle, localZone, ex) => - org.log4s.getLogger.error(s"vehicle deconstruction: $localVehicle failed to be properly cleaned up from zone $localZone - $ex") - - case _ => ; - } - - def RetirementTask(entry : DeconstructionActor.VehicleEntry) : Unit = { - val vehicle = entry.vehicle - val zone = entry.zone - vehicle.Position = Vector3.Zero //somewhere it will not disturb anything - zone.Transport ! Zone.Vehicle.Despawn(vehicle) - context.parent ! DeconstructionActor.DeleteVehicle(vehicle.GUID, zone.Id) //call up to the main event system - } - - def DestructionTask(entry : DeconstructionActor.VehicleEntry) : Unit = { - val vehicle = entry.vehicle - val zone = entry.zone - taskResolver ! DeconstructionTask(vehicle, zone) - } - - /** - * Construct a middleman `Task` intended to return error messages to the `DeconstructionActor`. - * @param vehicle the `Vehicle` object - * @param zone the `Zone` in which the vehicle resides - * @return a `TaskResolver.GiveTask` message - */ - def DeconstructionTask(vehicle : Vehicle, zone : Zone) : TaskResolver.GiveTask = { - import net.psforever.objects.guid.{GUIDTask, Task} - TaskResolver.GiveTask ( - new Task() { - private val localVehicle = vehicle - private val localZone = zone - private val localAnnounce = self - - override def isComplete : Task.Resolution.Value = Task.Resolution.Success - - def Execute(resolver : ActorRef) : Unit = { - resolver ! scala.util.Success(this) - } - - override def onFailure(ex : Throwable): Unit = { - localAnnounce ! DeconstructionActor.FailureToDeleteVehicle(localVehicle, localZone, ex) - } - }, List(GUIDTask.UnregisterVehicle(vehicle)(zone.GUID)) - ) - } - - /** - * Iterate over entries in a `List` until an entry that does not exceed the time limit is discovered. - * Separate the original `List` into two: - * a `List` of elements that have exceeded the time limit, - * and a `List` of elements that still satisfy the time limit. - * As newer entries to the `List` will always resolve later than old ones, - * and newer entries are always added to the end of the main `List`, - * processing in order is always correct. - * @param list the `List` of entries to divide - * @param now the time right now (in nanoseconds) - * @see `List.partition` - * @return a `Tuple` of two `Lists`, whose qualifications are explained above - */ - private def PartitionEntries(list : List[DeconstructionActor.VehicleEntry], now : Long) : (List[DeconstructionActor.VehicleEntry], List[DeconstructionActor.VehicleEntry]) = { - val n : Int = recursivePartitionEntries(list.iterator, now) - (list.take(n), list.drop(n)) //take and drop so to always return new lists - } - - /** - * Mark the index where the `List` of elements can be divided into two: - * a `List` of elements that have exceeded the time limit, - * and a `List` of elements that still satisfy the time limit. - * @param iter the `Iterator` of entries to divide - * @param now the time right now (in nanoseconds) - * @param index a persistent record of the index where list division should occur; - * defaults to 0 - * @return the index where division will occur - */ - @tailrec private def recursivePartitionEntries(iter : Iterator[DeconstructionActor.VehicleEntry], now : Long, index : Int = 0) : Int = { - if(!iter.hasNext) { - index - } - else { - val entry = iter.next() - if(now - entry.time >= DeconstructionActor.timeout_time) { - recursivePartitionEntries(iter, now, index + 1) - } - else { - index - } - } - } -} - -object DeconstructionActor { - /** The wait before completely deleting a vehicle; as a Long for calculation simplicity */ - private final val timeout_time : Long = 5000000000L //nanoseconds (5s) - /** The wait before completely deleting a vehicle; as a `FiniteDuration` for `Executor` simplicity */ - private final val timeout : FiniteDuration = timeout_time nanoseconds - - final case class RequestTaskResolver() - - /** - * Message that carries information about a vehicle to be deconstructed. - * @param vehicle the `Vehicle` object - * @param zone the `Zone` in which the vehicle resides - * @param time when the vehicle was doomed - * @see `VehicleEntry` - */ - final case class RequestDeleteVehicle(vehicle : Vehicle, zone : Zone, time : Long = System.nanoTime()) - /** - * Message that carries information about a vehicle to be deconstructed. - * Prompting, as compared to `RequestDeleteVehicle` which is reactionary. - * @param vehicle_guid the vehicle - * @param zone_id the `Zone` in which the vehicle resides - */ - final case class DeleteVehicle(vehicle_guid : PlanetSideGUID, zone_id : String) - /** - * Internal message used to signal a test of the queued vehicle information. - * Remove all deconstructing vehicles from the game world. - */ - private final case class StartDeleteVehicle() - /** - * Internal message used to signal a test of the queued vehicle information. - * Remove all deconstructing vehicles from the zone's globally unique identifier system. - */ - private final case class TryDeleteVehicle() - - /** - * Error-passing message carrying information out of the final deconstruction GUID unregistering task. - * @param vehicle the `Vehicle` object - * @param zone the `Zone` in which the vehicle may or may not reside - * @param ex information regarding what happened - */ - private final case class FailureToDeleteVehicle(vehicle : Vehicle, zone : Zone, ex : Throwable) - - /** - * Entry of vehicle information. - * The `zone` is maintained separately as a necessity, required to complete the deletion of the vehicle - * via unregistering of the vehicle and all related, registered objects. - * @param vehicle the `Vehicle` object - * @param zone the `Zone` in which the vehicle resides - * @param time when the vehicle was doomed - * @see `RequestDeleteVehicle` - */ - private final case class VehicleEntry(vehicle : Vehicle, zone : Zone, time : Long) -} diff --git a/pslogin/src/main/scala/services/vehicle/support/DelayedDeconstructionActor.scala b/pslogin/src/main/scala/services/vehicle/support/DelayedDeconstructionActor.scala deleted file mode 100644 index 7756b415..00000000 --- a/pslogin/src/main/scala/services/vehicle/support/DelayedDeconstructionActor.scala +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2017 PSForever -package services.vehicle.support - -import akka.actor.{Actor, Cancellable} -import net.psforever.objects.{DefaultCancellable, Vehicle} -import net.psforever.objects.zones.Zone -import net.psforever.packet.game.PlanetSideGUID -import services.vehicle.VehicleServiceMessage - -import scala.concurrent.duration._ - -/** - * Maintain and curate a list of timed `vehicle` object deconstruction tasks.
- *
- * These tasks are queued or dismissed by player activity but they are executed independent of player activity. - * A common disconnected cause of deconstruction is neglect for an extended period of time. - * At that point, the original owner of the vehicle no longer matters. - * Deconstruction neglect, however, is averted by having someone become seated. - * A realized deconstruction is entirely based on a fixed interval after an unresolved request has been received. - * The actual process of deconstructing the vehicle and cleaning up its resources is performed by an external agent.
- *
- * This `Actor` is intended to sit on top of the event system that handles broadcast messaging. - */ -class DelayedDeconstructionActor extends Actor { - /** The periodic `Executor` that scraps the next vehicle on the list */ - private var monitor : Cancellable = DefaultCancellable.obj - /** A `List` of currently doomed vehicles */ - private var vehicles : List[DelayedDeconstructionActor.VehicleEntry] = Nil - private[this] val log = org.log4s.getLogger - private[this] def trace(msg : String) : Unit = log.trace(msg) - - - def receive : Receive = { - case DelayedDeconstructionActor.ScheduleDeconstruction(vehicle, zone, timeAlive) => - trace(s"delayed deconstruction order for $vehicle in $timeAlive") - val oldHead = vehicles.headOption - val now : Long = System.nanoTime - vehicles = (vehicles :+ DelayedDeconstructionActor.VehicleEntry(vehicle, zone, timeAlive * 1000000000L)) - .sortBy(entry => entry.survivalTime - (now - entry.logTime)) - if(vehicles.size == 1 || oldHead != vehicles.headOption) { //we were the only entry so the event must be started from scratch - RetimePeriodicTest() - } - - case DelayedDeconstructionActor.UnscheduleDeconstruction(vehicle_guid) => - //all tasks for this vehicle are cleared from the queue - //clear any task that is no longer valid by determination of unregistered GUID - val before = vehicles.length - val now : Long = System.nanoTime - vehicles = vehicles.filter(entry => { entry.vehicle.HasGUID && entry.vehicle.GUID != vehicle_guid }) - .sortBy(entry => entry.survivalTime - (now - entry.logTime)) - trace(s"attempting to clear deconstruction order for vehicle $vehicle_guid, found ${before - vehicles.length}") - RetimePeriodicTest() - - case DelayedDeconstructionActor.PeriodicTaskCulling => - //filter the list of deconstruction tasks for any that are need to be triggered - monitor.cancel - val now : Long = System.nanoTime - val (vehiclesToDecon, vehiclesRemain) = vehicles.partition(entry => { now - entry.logTime >= entry.survivalTime }) - vehicles = vehiclesRemain.sortBy(_.survivalTime) - trace(s"vehicle culling - ${vehiclesToDecon.length} deconstruction tasks found; ${vehiclesRemain.length} tasks remain") - vehiclesToDecon.foreach(entry => { context.parent ! VehicleServiceMessage.RequestDeleteVehicle(entry.vehicle, entry.zone) }) - RetimePeriodicTest() - - case _ => ; - } - - def RetimePeriodicTest() : Unit = { - monitor.cancel - vehicles.headOption match { - case None => ; - case Some(entry) => - val retime = math.max(1, entry.survivalTime - (System.nanoTime - entry.logTime)) nanoseconds - import scala.concurrent.ExecutionContext.Implicits.global - monitor = context.system.scheduler.scheduleOnce(retime, self, DelayedDeconstructionActor.PeriodicTaskCulling) - } - } -} - -object DelayedDeconstructionActor { - /** - * Timer for the repeating executor. - */ - private final val periodicTest : FiniteDuration = 5000000000L nanoseconds //5s - - /** - * Queue a future vehicle deconstruction action. - * @param vehicle the `Vehicle` object - * @param zone the `Zone` that the vehicle currently occupies - * @param survivalTime how long until the vehicle will be deconstructed in seconds - */ - final case class ScheduleDeconstruction(vehicle : Vehicle, zone : Zone, survivalTime : Long) - - /** - * Dequeue a vehicle from being deconstructed. - * @param vehicle_guid the vehicle - */ - final case class UnscheduleDeconstruction(vehicle_guid : PlanetSideGUID) - - /** - * A message the `Actor` sends to itself. - * The trigger for the periodic deconstruction task. - */ - private final case class PeriodicTaskCulling() - - /** - * An entry that stores vehicle deconstruction tasks. - * @param vehicle the `Vehicle` object - * @param zone the `Zone` that the vehicle currently occupies - * @param survivalTime how long until the vehicle will be deconstructed in nanoseconds - * @param logTime when this deconstruction request was initially created in nanoseconds; - * initialized by default to a "now" - */ - private final case class VehicleEntry(vehicle : Vehicle, zone : Zone, survivalTime : Long, logTime : Long = System.nanoTime()) -} diff --git a/pslogin/src/main/scala/services/vehicle/support/VehicleRemover.scala b/pslogin/src/main/scala/services/vehicle/support/VehicleRemover.scala new file mode 100644 index 00000000..cec2e65e --- /dev/null +++ b/pslogin/src/main/scala/services/vehicle/support/VehicleRemover.scala @@ -0,0 +1,56 @@ +// Copyright (c) 2017 PSForever +package services.vehicle.support + +import net.psforever.objects.Vehicle +import net.psforever.objects.guid.{GUIDTask, TaskResolver} +import net.psforever.objects.zones.Zone +import services.vehicle.{VehicleAction, VehicleServiceMessage} +import services.{RemoverActor, Service} + +import scala.concurrent.duration._ + +class VehicleRemover extends RemoverActor { + final val FirstStandardDuration : FiniteDuration = 5 minutes + + final val SecondStandardDuration : FiniteDuration = 5 seconds + + def InclusionTest(entry : RemoverActor.Entry) : Boolean = { + entry.obj.isInstanceOf[Vehicle] + } + + def InitialJob(entry : RemoverActor.Entry) : Unit = { } + + def FirstJob(entry : RemoverActor.Entry) : Unit = { + val vehicle = entry.obj.asInstanceOf[Vehicle] + val vehicleGUID = vehicle.GUID + val zoneId = entry.zone.Id + vehicle.Actor ! Vehicle.PrepareForDeletion + //kick out all passengers + vehicle.Definition.MountPoints.values.foreach(mount => { + val seat = vehicle.Seat(mount).get + seat.Occupant match { + case Some(tplayer) => + seat.Occupant = None + tplayer.VehicleSeated = None + if(tplayer.HasGUID) { + context.parent ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(tplayer.GUID, 4, false, vehicleGUID)) + } + case None => ; + } + }) + } + + override def SecondJob(entry : RemoverActor.Entry) : Unit = { + val vehicle = entry.obj.asInstanceOf[Vehicle] + val zone = entry.zone + zone.Transport ! Zone.Vehicle.Despawn(vehicle) + context.parent ! VehicleServiceMessage(zone.Id, VehicleAction.UnloadVehicle(Service.defaultPlayerGUID, zone, vehicle)) + super.SecondJob(entry) + } + + def ClearanceTest(entry : RemoverActor.Entry) : Boolean = entry.obj.asInstanceOf[Vehicle].Seats.values.count(_.isOccupied) == 0 + + def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = { + GUIDTask.UnregisterVehicle(entry.obj.asInstanceOf[Vehicle])(entry.zone.GUID) + } +}