Merge pull request #200 from Fate-JH/decon-repair

Deconstruction Repair
This commit is contained in:
Fate-JH 2018-05-08 10:32:54 -04:00 committed by GitHub
commit 7ddd0214fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 144 additions and 57 deletions

View file

@ -127,9 +127,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
continent.Population ! Zone.Population.Release(avatar) continent.Population ! Zone.Population.Release(avatar)
player.VehicleSeated match { player.VehicleSeated match {
case None => case None =>
continent.Population ! Zone.Corpse.Add(player)
FriskCorpse(player) //TODO eliminate dead letters FriskCorpse(player) //TODO eliminate dead letters
if(!WellLootedCorpse(player)) { if(!WellLootedCorpse(player)) {
continent.Population ! Zone.Corpse.Add(player)
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.Release(player, continent)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.Release(player, continent))
taskResolver ! GUIDTask.UnregisterLocker(player.Locker)(continent.GUID) //rest of player will be cleaned up with corpses taskResolver ! GUIDTask.UnregisterLocker(player.Locker)(continent.GUID) //rest of player will be cleaned up with corpses
} }
@ -304,6 +304,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case AvatarResponse.ObjectDelete(item_guid, unk) => case AvatarResponse.ObjectDelete(item_guid, unk) =>
if(tplayer_guid != guid) { if(tplayer_guid != guid) {
log.info(s"Made to delete item $item_guid")
sendResponse(ObjectDeleteMessage(item_guid, unk)) sendResponse(ObjectDeleteMessage(item_guid, unk))
} }
@ -1601,10 +1602,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
continent.Population ! Zone.Population.Release(avatar) continent.Population ! Zone.Population.Release(avatar)
player.VehicleSeated match { player.VehicleSeated match {
case None => case None =>
continent.Population ! Zone.Corpse.Add(player) //TODO move back out of this match case when changing below issue
FriskCorpse(player) FriskCorpse(player)
if(!WellLootedCorpse(player)) { if(!WellLootedCorpse(player)) {
TurnPlayerIntoCorpse(player) TurnPlayerIntoCorpse(player)
continent.Population ! Zone.Corpse.Add(player) //TODO move back out of this match case when changing below issue
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.Release(player, continent)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.Release(player, continent))
} }
else { //no items in inventory; leave no corpse else { //no items in inventory; leave no corpse

View file

@ -15,17 +15,26 @@ import scala.concurrent.duration._
class CorpseRemovalActor extends Actor { class CorpseRemovalActor extends Actor {
private var burial : Cancellable = DefaultCancellable.obj private var burial : Cancellable = DefaultCancellable.obj
private var corpses : List[CorpseRemovalActor.Entry] = List() private var corpses : List[CorpseRemovalActor.Entry] = List()
private var decomposition : Cancellable = DefaultCancellable.obj
private var buriedCorpses : List[CorpseRemovalActor.Entry] = List()
private var taskResolver : ActorRef = Actor.noSender private var taskResolver : ActorRef = Actor.noSender
private[this] val log = org.log4s.getLogger private[this] val log = org.log4s.getLogger
override def postStop() = { override def postStop() = {
//Cart Master: See you on Thursday. //Cart Master: See you on Thursday.
corpses.foreach { BurialTask } super.postStop()
corpses = Nil burial.cancel
decomposition.cancel
corpses.foreach(corpse => {
BurialTask(corpse)
LastRitesTask(corpse)
})
buriedCorpses.foreach { LastRitesTask }
} }
def receive : Receive = { def receive : Receive = {
@ -42,7 +51,7 @@ class CorpseRemovalActor extends Actor {
def Processing : Receive = { def Processing : Receive = {
case CorpseRemovalActor.AddCorpse(corpse, zone, time) => case CorpseRemovalActor.AddCorpse(corpse, zone, time) =>
if(corpse.isBackpack) { if(corpse.isBackpack && !buriedCorpses.exists(_.corpse == corpse)) {
if(corpses.isEmpty) { if(corpses.isEmpty) {
//we were the only entry so the event must be started from scratch //we were the only entry so the event must be started from scratch
corpses = List(CorpseRemovalActor.Entry(corpse, zone, time)) corpses = List(CorpseRemovalActor.Entry(corpse, zone, time))
@ -51,9 +60,11 @@ class CorpseRemovalActor extends Actor {
else { else {
//unknown number of entries; append, sort, then re-time tasking //unknown number of entries; append, sort, then re-time tasking
val oldHead = corpses.head val oldHead = corpses.head
corpses = (corpses :+ CorpseRemovalActor.Entry(corpse, zone, time)).sortBy(_.timeAlive) if(!corpses.exists(_.corpse == corpse)) {
if(oldHead != corpses.head) { corpses = (corpses :+ CorpseRemovalActor.Entry(corpse, zone, time)).sortBy(_.timeAlive)
RetimeFirstTask() if(oldHead != corpses.head) {
RetimeFirstTask()
}
} }
} }
} }
@ -72,42 +83,66 @@ class CorpseRemovalActor extends Actor {
CorpseRemovalActor.recursiveFindCorpse(corpses.iterator, targets.head) match { CorpseRemovalActor.recursiveFindCorpse(corpses.iterator, targets.head) match {
case None => ; case None => ;
case Some(index) => case Some(index) =>
decomposition.cancel
BurialTask(corpses(index)) BurialTask(corpses(index))
buriedCorpses = buriedCorpses :+ corpses(index)
corpses = corpses.take(index) ++ corpses.drop(index+1) corpses = corpses.take(index) ++ corpses.drop(index+1)
import scala.concurrent.ExecutionContext.Implicits.global
decomposition = context.system.scheduler.scheduleOnce(500 milliseconds, self, CorpseRemovalActor.TryDelete())
} }
} }
else { else {
log.debug(s"multiple target corpses submitted for early cleanup: $targets") log.debug(s"multiple target corpses submitted for early cleanup: $targets")
decomposition.cancel
//cumbersome partition //cumbersome partition
//a - find targets from corpses //a - find targets from corpses
(for { val locatedTargets = for {
a <- targets a <- targets
b <- corpses b <- corpses
if b.corpse == a && if b.corpse == a &&
b.corpse.Continent.equals(a.Continent) && b.corpse.Continent.equals(a.Continent) &&
b.corpse.HasGUID && a.HasGUID && b.corpse.GUID == a.GUID b.corpse.HasGUID && a.HasGUID && b.corpse.GUID == a.GUID
} yield b).foreach { BurialTask } } yield b
//b - corpses after the found targets are locatedTargets.foreach { BurialTask }
//removed (note: cull any non-GUID entries while at it) buriedCorpses = locatedTargets ++ buriedCorpses
//b - corpses, after the found targets are removed (cull any non-GUID entries while at it)
corpses = (for { corpses = (for {
a <- targets a <- locatedTargets.map { _.corpse }
b <- corpses b <- corpses
if b.corpse.HasGUID && a.HasGUID && if b.corpse.HasGUID && a.HasGUID &&
(b.corpse != a || (b.corpse != a ||
!b.corpse.Continent.equals(a.Continent) || !b.corpse.Continent.equals(a.Continent) ||
!b.corpse.HasGUID || !a.HasGUID || b.corpse.GUID != a.GUID) !b.corpse.HasGUID || !a.HasGUID || b.corpse.GUID != a.GUID)
} yield b).sortBy(_.timeAlive) } yield b).sortBy(_.timeAlive)
import scala.concurrent.ExecutionContext.Implicits.global
decomposition = context.system.scheduler.scheduleOnce(500 milliseconds, self, CorpseRemovalActor.TryDelete())
} }
RetimeFirstTask() RetimeFirstTask()
} }
case CorpseRemovalActor.Dispose() => case CorpseRemovalActor.StartDelete() =>
burial.cancel burial.cancel
decomposition.cancel
val now : Long = System.nanoTime val now : Long = System.nanoTime
val (buried, rotting) = corpses.partition(entry => { now - entry.time >= entry.timeAlive }) val (buried, rotting) = corpses.partition(entry => { now - entry.time >= entry.timeAlive })
corpses = rotting corpses = rotting
buriedCorpses = buriedCorpses ++ buried
buried.foreach { BurialTask } buried.foreach { BurialTask }
RetimeFirstTask() RetimeFirstTask()
if(buriedCorpses.nonEmpty) {
import scala.concurrent.ExecutionContext.Implicits.global
burial = context.system.scheduler.scheduleOnce(500 milliseconds, self, CorpseRemovalActor.TryDelete())
}
case CorpseRemovalActor.TryDelete() =>
decomposition.cancel
val (decomposed, rotting) = buriedCorpses.partition(entry => { !entry.zone.Corpses.contains(entry.corpse) })
buriedCorpses = rotting
decomposed.foreach { LastRitesTask }
if(rotting.nonEmpty) {
import scala.concurrent.ExecutionContext.Implicits.global
decomposition = context.system.scheduler.scheduleOnce(500 milliseconds, self, CorpseRemovalActor.TryDelete())
}
case CorpseRemovalActor.FailureToWork(target, zone, ex) => case CorpseRemovalActor.FailureToWork(target, zone, ex) =>
//Cart Master: Oh, I can't take him like that. It's against regulations. //Cart Master: Oh, I can't take him like that. It's against regulations.
@ -122,21 +157,24 @@ class CorpseRemovalActor extends Actor {
if(corpses.nonEmpty) { if(corpses.nonEmpty) {
val short_timeout : FiniteDuration = math.max(1, corpses.head.timeAlive - (now - corpses.head.time)) nanoseconds val short_timeout : FiniteDuration = math.max(1, corpses.head.timeAlive - (now - corpses.head.time)) nanoseconds
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
burial = context.system.scheduler.scheduleOnce(short_timeout, self, CorpseRemovalActor.Dispose()) burial = context.system.scheduler.scheduleOnce(short_timeout, self, CorpseRemovalActor.StartDelete())
} }
} }
def BurialTask(entry : CorpseRemovalActor.Entry) : Unit = { def BurialTask(entry : CorpseRemovalActor.Entry) : Unit = {
//Cart master: Nine pence.
val target = entry.corpse val target = entry.corpse
val zone = entry.zone
target.Position = Vector3.Zero //somewhere it will not disturb anything
entry.zone.Population ! Zone.Corpse.Remove(target) entry.zone.Population ! Zone.Corpse.Remove(target)
context.parent ! AvatarServiceMessage(zone.Id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, target.GUID)) context.parent ! AvatarServiceMessage(entry.zone.Id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, target.GUID))
taskResolver ! BurialTask(target, zone)
} }
def BurialTask(corpse : Player, zone : Zone) : TaskResolver.GiveTask = { def LastRitesTask(entry : CorpseRemovalActor.Entry) : Unit = {
//Cart master: Nine pence.
val target = entry.corpse
target.Position = Vector3.Zero //somewhere it will not disturb anything
taskResolver ! LastRitesTask(target, entry.zone)
}
def LastRitesTask(corpse : Player, zone : Zone) : TaskResolver.GiveTask = {
import net.psforever.objects.guid.{GUIDTask, Task} import net.psforever.objects.guid.{GUIDTask, Task}
TaskResolver.GiveTask ( TaskResolver.GiveTask (
new Task() { new Task() {
@ -170,9 +208,11 @@ object CorpseRemovalActor {
final case class Entry(corpse : Player, zone : Zone, timeAlive : Long = CorpseRemovalActor.time, time : Long = System.nanoTime()) final case class Entry(corpse : Player, zone : Zone, timeAlive : Long = CorpseRemovalActor.time, time : Long = System.nanoTime())
final case class FailureToWork(corpse : Player, zone : Zone, ex : Throwable) private final case class FailureToWork(corpse : Player, zone : Zone, ex : Throwable)
final case class Dispose() private final case class StartDelete()
private final case class TryDelete()
/** /**
* A recursive function that finds and removes a specific player from a list of players. * A recursive function that finds and removes a specific player from a list of players.

View file

@ -30,10 +30,25 @@ class DeconstructionActor extends Actor {
private var scrappingProcess : Cancellable = DefaultCancellable.obj private var scrappingProcess : Cancellable = DefaultCancellable.obj
/** A `List` of currently doomed vehicles */ /** A `List` of currently doomed vehicles */
private var vehicles : List[DeconstructionActor.VehicleEntry] = Nil 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 */ /** The manager that helps unregister the vehicle from its current GUID scope */
private var taskResolver : ActorRef = Actor.noSender private var taskResolver : ActorRef = Actor.noSender
//private[this] val log = org.log4s.getLogger //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 = { def receive : Receive = {
/* /*
@ -52,45 +67,56 @@ class DeconstructionActor extends Actor {
def Processing : Receive = { def Processing : Receive = {
case DeconstructionActor.RequestDeleteVehicle(vehicle, zone, time) => case DeconstructionActor.RequestDeleteVehicle(vehicle, zone, time) =>
vehicles = vehicles :+ DeconstructionActor.VehicleEntry(vehicle, zone, time) if(!vehicles.exists(_.vehicle == vehicle) && !vehicleScrapHeap.exists(_.vehicle == vehicle)) {
vehicle.Actor ! Vehicle.PrepareForDeletion vehicles = vehicles :+ DeconstructionActor.VehicleEntry(vehicle, zone, time)
//kick everyone out; this is a no-blocking manual form of MountableBehavior ! Mountable.TryDismount vehicle.Actor ! Vehicle.PrepareForDeletion
vehicle.Definition.MountPoints.values.foreach(seat_num => { //kick everyone out; this is a no-blocking manual form of MountableBehavior ! Mountable.TryDismount
val zone_id : String = zone.Id vehicle.Definition.MountPoints.values.foreach(seat_num => {
val seat : Seat = vehicle.Seat(seat_num).get val zone_id : String = zone.Id
seat.Occupant match { val seat : Seat = vehicle.Seat(seat_num).get
case Some(tplayer) => seat.Occupant match {
seat.Occupant = None case Some(tplayer) =>
tplayer.VehicleSeated = None seat.Occupant = None
if(tplayer.HasGUID) { tplayer.VehicleSeated = None
context.parent ! VehicleServiceMessage(zone_id, VehicleAction.KickPassenger(tplayer.GUID, 4, false, vehicle.GUID)) if(tplayer.HasGUID) {
} context.parent ! VehicleServiceMessage(zone_id, VehicleAction.KickPassenger(tplayer.GUID, 4, false, vehicle.GUID))
case None => ; }
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())
} }
})
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.TryDeleteVehicle())
} }
case DeconstructionActor.TryDeleteVehicle() => case DeconstructionActor.StartDeleteVehicle() =>
scrappingProcess.cancel scrappingProcess.cancel
heapEmptyProcess.cancel
val now : Long = System.nanoTime val now : Long = System.nanoTime
val (vehiclesToScrap, vehiclesRemain) = PartitionEntries(vehicles, now) val (vehiclesToScrap, vehiclesRemain) = PartitionEntries(vehicles, now)
vehicles = vehiclesRemain vehicles = vehiclesRemain //entries from original list before partition
vehiclesToScrap.foreach(entry => { vehicleScrapHeap = vehicleScrapHeap ++ vehiclesToScrap //may include existing entries
val vehicle = entry.vehicle vehiclesToScrap.foreach { RetirementTask }
val zone = entry.zone
vehicle.Position = Vector3.Zero //somewhere it will not disturb anything
entry.zone.Transport ! Zone.Vehicle.Despawn(vehicle)
context.parent ! DeconstructionActor.DeleteVehicle(vehicle.GUID, zone.Id) //call up to the main event system
taskResolver ! DeconstructionTask(vehicle, zone)
})
if(vehiclesRemain.nonEmpty) { if(vehiclesRemain.nonEmpty) {
val short_timeout : FiniteDuration = math.max(1, DeconstructionActor.timeout_time - (now - vehiclesRemain.head.time)) nanoseconds val short_timeout : FiniteDuration = math.max(1, DeconstructionActor.timeout_time - (now - vehiclesRemain.head.time)) nanoseconds
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
scrappingProcess = context.system.scheduler.scheduleOnce(short_timeout, self, DeconstructionActor.TryDeleteVehicle()) 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) => case DeconstructionActor.FailureToDeleteVehicle(localVehicle, localZone, ex) =>
@ -99,6 +125,20 @@ class DeconstructionActor extends Actor {
case _ => ; case _ => ;
} }
def RetirementTask(entry : DeconstructionActor.VehicleEntry) : Unit = {
val vehicle = entry.vehicle
val zone = entry.zone
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
vehicle.Position = Vector3.Zero //somewhere it will not disturb anything
taskResolver ! DeconstructionTask(vehicle, zone)
}
/** /**
* Construct a middleman `Task` intended to return error messages to the `DeconstructionActor`. * Construct a middleman `Task` intended to return error messages to the `DeconstructionActor`.
* @param vehicle the `Vehicle` object * @param vehicle the `Vehicle` object
@ -195,6 +235,12 @@ object DeconstructionActor {
final case class DeleteVehicle(vehicle_guid : PlanetSideGUID, zone_id : String) final case class DeleteVehicle(vehicle_guid : PlanetSideGUID, zone_id : String)
/** /**
* Internal message used to signal a test of the queued vehicle information. * 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() private final case class TryDeleteVehicle()

View file

@ -329,7 +329,7 @@ class AvatarReleaseTest extends ActorTest {
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete]) assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid) assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
expectNoMsg(200 milliseconds) expectNoMsg(1000 milliseconds)
assert(zone.Corpses.isEmpty) assert(zone.Corpses.isEmpty)
assert(!obj.HasGUID) assert(!obj.HasGUID)
} }
@ -379,7 +379,7 @@ class AvatarReleaseEarly1Test extends ActorTest {
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete]) assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid) assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
expectNoMsg(200 milliseconds) expectNoMsg(600 milliseconds)
assert(zone.Corpses.isEmpty) assert(zone.Corpses.isEmpty)
assert(!obj.HasGUID) assert(!obj.HasGUID)
} }
@ -430,7 +430,7 @@ class AvatarReleaseEarly2Test extends ActorTest {
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete]) assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid) assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
expectNoMsg(200 milliseconds) expectNoMsg(600 milliseconds)
assert(zone.Corpses.isEmpty) assert(zone.Corpses.isEmpty)
assert(!obj.HasGUID) assert(!obj.HasGUID)
} }