Unique Number System Operations (#906)

* propagation of the ask pattern into the unique number actor

* TaskWorkflow as a replacement for TaskResolver; includes working tests

* AvailabilityPolicy has been changed slightly; number source restriction mechanic has been completely removed

* TaskResolver is gone and done and TaskWorkflow replaces it

* number pool variety

* every zone gets a custom tailored 'environment' number pool, as well as all other number pools; uns involves many more static functions and hard-defined variables

* repairs to uns and guidtask tests; worked uns into unops, an actorless version, but did not integrate into server

* shuffled around files in the guid package, causing import threshing; wrote extensive comments; repaired tests; classes related to the old unique number system have been removed

* created straightforward tasks; simplified number pool actor calls; repaired tests due to modifications to generic pool

* bad merge recovery
This commit is contained in:
Fate-JH 2021-08-15 21:27:45 -04:00 committed by GitHub
parent ce2a3f5422
commit 9841b7e97d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
114 changed files with 2323 additions and 3374 deletions

View file

@ -491,11 +491,7 @@ class AvatarStowEquipmentTest extends ActorTest {
/* /*
Preparation for these three Release tests is involved. Preparation for these three Release tests is involved.
The ServiceManager must not only be set up correctly, but must be given a TaskResolver. The ServiceManager must be set up correctly.
The AvatarService is started and that starts CorpseRemovalActor, an essential part of this test.
The CorpseRemovalActor needs that TaskResolver created by the ServiceManager;
but, another independent TaskResolver will be needed for manual parts of the test.
(The ServiceManager's TaskResolver can be "borrowed" but that requires writing code to intercept it.)
The Zone needs to be set up and initialized properly with a ZoneActor. The Zone needs to be set up and initialized properly with a ZoneActor.
The ZoneActor builds the GUID Actor and the ZonePopulationActor. The ZoneActor builds the GUID Actor and the ZonePopulationActor.

View file

@ -46,11 +46,6 @@ akka.actor.deployment {
dispatcher = galaxy-service dispatcher = galaxy-service
} }
# Isolate tasks
"/service/taskResolver*" {
dispatcher = task-dispatcher
}
# Bottleneck (dedicated thread) # Bottleneck (dedicated thread)
"/service/cluster" { "/service/cluster" {
dispatcher = interstellar-cluster-service dispatcher = interstellar-cluster-service

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 937,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 1615,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 1128,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 904,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 635,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 908,
"Selector": "specific"
}
]

View file

@ -0,0 +1,74 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 3000,
"Selector": "specific"
},
{
"Name": "players",
"Start": 3001,
"Max": 4500,
"Selector": "random"
},
{
"Name": "lockers",
"Start": 4501,
"Max": 5000,
"Selector": "random"
},
{
"Name": "tools",
"Start": 5001,
"Max": 9500,
"Selector": "random"
},
{
"Name": "ammo",
"Start": 9501,
"Max": 23000,
"Selector": "random"
},
{
"Name": "kits",
"Start": 23001,
"Max": 36500,
"Selector": "random"
},
{
"Name": "items",
"Start": 36501,
"Max": 39500,
"Selector": "random"
},
{
"Name": "projectiles",
"Start": 40100,
"Max": 40149,
"Selector": "specific"
},
{
"Name": "locker-contents",
"Start": 40150,
"Max": 40450,
"Selector": "specific"
},
{
"Name": "vehicles",
"Start": 45001,
"Max": 47000,
"Selector": "random"
},
{
"Name": "terminals",
"Start": 47001,
"Max": 48000,
"Selector": "random"
},
{
"Name": "deployables",
"Start": 48001,
"Max": 64000,
"Selector": "random"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 1161,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 1096,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 1074,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 778,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 918,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 699,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 318,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 2088,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 2657,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 2515,
"Selector": "specific"
}
]

View file

@ -0,0 +1,44 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 3732,
"Selector": "specific"
},
{
"Name": "players",
"Start": 4001,
"Max": 5500,
"Selector": "random"
},
{
"Name": "lockers",
"Start": 5501,
"Max": 6000,
"Selector": "random"
},
{
"Name": "tools",
"Start": 6001,
"Max": 10500,
"Selector": "random"
},
{
"Name": "ammo",
"Start": 10501,
"Max": 24000,
"Selector": "random"
},
{
"Name": "kits",
"Start": 24001,
"Max": 37500,
"Selector": "random"
},
{
"Name": "items",
"Start": 37501,
"Max": 40099,
"Selector": "random"
}
]

View file

@ -0,0 +1,44 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 3076,
"Selector": "specific"
},
{
"Name": "players",
"Start": 3101,
"Max": 4600,
"Selector": "random"
},
{
"Name": "lockers",
"Start": 4601,
"Max": 5100,
"Selector": "random"
},
{
"Name": "tools",
"Start": 5101,
"Max": 9600,
"Selector": "random"
},
{
"Name": "ammo",
"Start": 9601,
"Max": 23100,
"Selector": "random"
},
{
"Name": "kits",
"Start": 23101,
"Max": 36600,
"Selector": "random"
},
{
"Name": "items",
"Start": 36601,
"Max": 39600,
"Selector": "random"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 2112,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 2422,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 2816,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 2139,
"Selector": "specific"
}
]

View file

@ -0,0 +1,8 @@
[
{
"Name": "environment",
"Start": 0,
"Max": 2921,
"Selector": "specific"
}
]

View file

@ -16,7 +16,7 @@ import net.psforever.objects.definition._
import net.psforever.objects.definition.converter.{CorpseConverter, DestroyedVehicleConverter} import net.psforever.objects.definition.converter.{CorpseConverter, DestroyedVehicleConverter}
import net.psforever.objects.entity.{SimpleWorldEntity, WorldEntity} import net.psforever.objects.entity.{SimpleWorldEntity, WorldEntity}
import net.psforever.objects.equipment._ import net.psforever.objects.equipment._
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} import net.psforever.objects.guid._
import net.psforever.objects.inventory.{Container, InventoryItem} import net.psforever.objects.inventory.{Container, InventoryItem}
import net.psforever.objects.locker.LockerContainer import net.psforever.objects.locker.LockerContainer
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
@ -1049,11 +1049,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
//!!only dispatched to SessionActor as cleanup if the target deployable was never fully introduced //!!only dispatched to SessionActor as cleanup if the target deployable was never fully introduced
case Zone.Deployable.IsDismissed(obj: TurretDeployable) => case Zone.Deployable.IsDismissed(obj: TurretDeployable) =>
continent.tasks ! GUIDTask.UnregisterDeployableTurret(obj)(continent.GUID) TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(continent.GUID, obj))
//!!only dispatched to SessionActor as cleanup if the target deployable was never fully introduced //!!only dispatched to SessionActor as cleanup if the target deployable was never fully introduced
case Zone.Deployable.IsDismissed(obj) => case Zone.Deployable.IsDismissed(obj) =>
continent.tasks ! GUIDTask.UnregisterObjectTask(obj)(continent.GUID) TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, obj))
case ICS.ZonesResponse(zones) => case ICS.ZonesResponse(zones) =>
zones.foreach { zone => zones.foreach { zone =>
@ -1156,9 +1156,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
player.avatar = avatar player.avatar = avatar
interstellarFerry match { interstellarFerry match {
case Some(vehicle) if vehicle.PassengerInSeat(player).contains(0) => case Some(vehicle) if vehicle.PassengerInSeat(player).contains(0) =>
continent.tasks ! RegisterDrivenVehicle(vehicle, player) TaskWorkflow.execute(registerDrivenVehicle(vehicle, player))
case _ => case _ =>
continent.tasks ! RegisterNewAvatar(player) TaskWorkflow.execute(registerNewAvatar(player))
} }
case NewPlayerLoaded(tplayer) => case NewPlayerLoaded(tplayer) =>
@ -1997,13 +1997,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case (_, guid) => sendResponse(ObjectDeleteMessage(guid, 0)) case (_, guid) => sendResponse(ObjectDeleteMessage(guid, 0))
} }
//functionally delete //functionally delete
delete.foreach { case (obj, _) => continent.tasks ! GUIDTask.UnregisterEquipment(obj)(continent.GUID) } delete.foreach { case (obj, _) => TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) }
//redraw //redraw
if (maxhand) { if (maxhand) {
continent.tasks ! HoldNewEquipmentUp(player)( TaskWorkflow.execute(HoldNewEquipmentUp(player)(
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)), Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
0 0
) ))
} }
//draw free hand //draw free hand
player.FreeHand.Equipment match { player.FreeHand.Equipment match {
@ -2075,14 +2075,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
(old_holsters ++ old_inventory).foreach { (old_holsters ++ old_inventory).foreach {
case (obj, guid) => case (obj, guid) =>
sendResponse(ObjectDeleteMessage(guid, 0)) sendResponse(ObjectDeleteMessage(guid, 0))
continent.tasks ! GUIDTask.UnregisterEquipment(obj)(continent.GUID) TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj))
} }
//redraw //redraw
if (maxhand) { if (maxhand) {
continent.tasks ! HoldNewEquipmentUp(player)( TaskWorkflow.execute(HoldNewEquipmentUp(player)(
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)), Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
0 0
) ))
} }
ApplyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory) ApplyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory)
DropLeftovers(player)(drops) DropLeftovers(player)(drops)
@ -2166,7 +2166,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
if (Avatar.purchaseCooldowns.contains(item.obj.Definition)) { if (Avatar.purchaseCooldowns.contains(item.obj.Definition)) {
avatarActor ! AvatarActor.UpdatePurchaseTime(item.obj.Definition) avatarActor ! AvatarActor.UpdatePurchaseTime(item.obj.Definition)
} }
continent.tasks ! PutLoadoutEquipmentInInventory(target)(item.obj, item.start) TaskWorkflow.execute(PutLoadoutEquipmentInInventory(target)(item.obj, item.start))
} }
} }
} }
@ -2574,11 +2574,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, false)) sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, false))
case None => case None =>
avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition) avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition)
continent.tasks ! BuyNewEquipmentPutInInventory( TaskWorkflow.execute(BuyNewEquipmentPutInInventory(
continent.GUID(tplayer.VehicleSeated) match { case Some(v : Vehicle) => v; case _ => player }, continent.GUID(tplayer.VehicleSeated) match { case Some(v : Vehicle) => v; case _ => player },
tplayer, tplayer,
msg.terminal_guid msg.terminal_guid
)(item) )(item))
} }
case Terminal.SellEquipment() => case Terminal.SellEquipment() =>
@ -2637,7 +2637,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
entry.obj.Faction = tplayer.Faction entry.obj.Faction = tplayer.Faction
vTrunk.InsertQuickly(entry.start, entry.obj) vTrunk.InsertQuickly(entry.start, entry.obj)
}) })
continent.tasks ! RegisterVehicleFromSpawnPad(vehicle, pad, term) TaskWorkflow.execute(registerVehicleFromSpawnPad(vehicle, pad, term))
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true)) sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
case _ => case _ =>
log.error( log.error(
@ -2921,7 +2921,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
(old_weapons ++ old_inventory).foreach { (old_weapons ++ old_inventory).foreach {
case (obj, eguid) => case (obj, eguid) =>
sendResponse(ObjectDeleteMessage(eguid, 0)) sendResponse(ObjectDeleteMessage(eguid, 0))
continent.tasks ! GUIDTask.UnregisterEquipment(obj)(continent.GUID) TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj))
} }
ApplyPurchaseTimersBeforePackingLoadout(player, vehicle, added_weapons ++ new_inventory) ApplyPurchaseTimersBeforePackingLoadout(player, vehicle, added_weapons ++ new_inventory)
} else if (accessedContainer.contains(target)) { } else if (accessedContainer.contains(target)) {
@ -4406,7 +4406,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
continent.id, continent.id,
AvatarAction.ProjectileExplodes(player.GUID, obj.GUID, obj) AvatarAction.ProjectileExplodes(player.GUID, obj.GUID, obj)
) )
continent.tasks ! UnregisterProjectile(obj) TaskWorkflow.execute(unregisterProjectile(obj))
} }
} }
@ -4965,13 +4965,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
dObj.Orientation = orient dObj.Orientation = orient
dObj.Faction = player.Faction dObj.Faction = player.Faction
dObj.AssignOwnership(player) dObj.AssignOwnership(player)
val tasking: TaskResolver.GiveTask = dObj match { val tasking: TaskBundle = dObj match {
case turret: TurretDeployable => case turret: TurretDeployable =>
GUIDTask.RegisterDeployableTurret(turret)(continent.GUID) GUIDTask.registerDeployableTurret(continent.GUID, turret)
case _ => case _ =>
GUIDTask.RegisterObjectTask(dObj)(continent.GUID) GUIDTask.registerObject(continent.GUID, dObj)
} }
continent.tasks ! CallBackForTask(tasking, continent.Deployables, Zone.Deployable.BuildByOwner(dObj, player, obj)) TaskWorkflow.execute(CallBackForTask(tasking, continent.Deployables, Zone.Deployable.BuildByOwner(dObj, player, obj)))
case Some(obj) => case Some(obj) =>
log.warn(s"DeployObject: what is $obj, ${player.Name}? It's not a construction tool!") log.warn(s"DeployObject: what is $obj, ${player.Name}? It's not a construction tool!")
@ -5724,35 +5724,22 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* as if that player is only just being introduced. * as if that player is only just being introduced.
* `Players` are complex objects that contain a variety of other register-able objects and each of these objects much be handled. * `Players` are complex objects that contain a variety of other register-able objects and each of these objects much be handled.
* @param tplayer the avatar `Player` * @param tplayer the avatar `Player`
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
private def RegisterNewAvatar(tplayer: Player): TaskResolver.GiveTask = { private def registerNewAvatar(tplayer: Player): TaskBundle = {
TaskResolver.GiveTask( TaskBundle(
new Task() { new StraightforwardTask() {
private val localPlayer = tplayer private val localPlayer = tplayer
private val localAnnounce = self private val localAnnounce = self
override def Description: String = s"register new player avatar ${localPlayer.Name}" override def description(): String = s"register new player avatar ${localPlayer.Name}"
override def isComplete: Task.Resolution.Value = { def action(): Future[Any] = {
if (localPlayer.HasGUID) { localAnnounce ! NewPlayerLoaded(localPlayer)
Task.Resolution.Success Future(true)
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
log.trace(s"Player ${localPlayer.Name} is newly registered")
resolver ! Success(this)
localAnnounce ! NewPlayerLoaded(localPlayer) //alerts WorldSessionActor
}
override def onFailure(ex: Throwable): Unit = {
localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts SessionActor
} }
}, },
List(GUIDTask.RegisterAvatar(tplayer)(continent.GUID)) List(GUIDTask.registerAvatar(continent.GUID, tplayer))
) )
} }
@ -5761,35 +5748,22 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* as if that player was already introduced and is just being renewed. * as if that player was already introduced and is just being renewed.
* `Players` are complex objects that contain a variety of other register-able objects and each of these objects much be handled. * `Players` are complex objects that contain a variety of other register-able objects and each of these objects much be handled.
* @param tplayer the avatar `Player` * @param tplayer the avatar `Player`
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
private def RegisterAvatar(tplayer: Player): TaskResolver.GiveTask = { private def registerAvatar(tplayer: Player): TaskBundle = {
TaskResolver.GiveTask( TaskBundle(
new Task() { new StraightforwardTask() {
private val localPlayer = tplayer private val localPlayer = tplayer
private val localAnnounce = self private val localAnnounce = self
override def Description: String = s"register player avatar ${localPlayer.Name}" override def description(): String = s"register player avatar ${localPlayer.Name}"
override def isComplete: Task.Resolution.Value = { def action(): Future[Any] = {
if (localPlayer.HasGUID) { localAnnounce ! PlayerLoaded(localPlayer)
Task.Resolution.Success Future(true)
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
log.trace(s"Player $localPlayer is registered")
resolver ! Success(this)
localAnnounce ! PlayerLoaded(localPlayer) //alerts WorldSessionActor
}
override def onFailure(ex: Throwable): Unit = {
localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WorldSessionActor
} }
}, },
List(GUIDTask.RegisterPlayer(tplayer)(continent.GUID)) List(GUIDTask.registerPlayer(continent.GUID, tplayer))
) )
} }
@ -5798,29 +5772,21 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* Use this function to renew the globally unique identifiers on a vehicle that has already been added to the scene once. * Use this function to renew the globally unique identifiers on a vehicle that has already been added to the scene once.
* @param vehicle the `Vehicle` object * @param vehicle the `Vehicle` object
* @see `RegisterVehicleFromSpawnPad` * @see `RegisterVehicleFromSpawnPad`
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
def RegisterVehicle(vehicle: Vehicle): TaskResolver.GiveTask = { private def registerVehicle(vehicle: Vehicle): TaskBundle = {
TaskResolver.GiveTask( TaskBundle(
new Task() { new StraightforwardTask() {
private val localVehicle = vehicle private val localVehicle = vehicle
private val localAnnounce = self
override def Description: String = s"register a ${localVehicle.Definition.Name}" override def description(): String = s"register a ${localVehicle.Definition.Name}"
override def isComplete: Task.Resolution.Value = { def action(): Future[Any] = {
if (localVehicle.HasGUID) { Future(true)
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
log.trace(s"Vehicle $localVehicle is registered")
resolver ! Success(this)
} }
}, },
List(GUIDTask.RegisterVehicle(vehicle)(continent.GUID)) List(GUIDTask.registerVehicle(continent.GUID, vehicle))
) )
} }
@ -5839,43 +5805,34 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* and the user who is the driver (second param) is properly seated * and the user who is the driver (second param) is properly seated
* but the said driver does not know about the vehicle through his usual convention - `VehicleSeated` - yet. * but the said driver does not know about the vehicle through his usual convention - `VehicleSeated` - yet.
* @see `GlobalDefinitions.droppod` * @see `GlobalDefinitions.droppod`
* @see `GUIDTask.RegisterObjectTask` * @see `GUIDTask.registerObject`
* @see `interstellarFerry` * @see `interstellarFerry`
* @see `Player.VehicleSeated` * @see `Player.VehicleSeated`
* @see `PlayerLoaded` * @see `PlayerLoaded`
* @see `TaskResolver.GiveTask` * @see `TaskBundle`
* @see `Vehicles.Own` * @see `Vehicles.Own`
* @param vehicle the unregistered droppod * @param vehicle the unregistered droppod
* @param tplayer the player using the droppod for instant action; * @param tplayer the player using the droppod for instant action;
* should already be the driver of the droppod * should already be the driver of the droppod
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
def RegisterDroppod(vehicle: Vehicle, tplayer: Player): TaskResolver.GiveTask = { private def registerDroppod(vehicle: Vehicle, tplayer: Player): TaskBundle = {
TaskResolver.GiveTask( TaskBundle(
new Task() { new StraightforwardTask() {
private val localDriver = tplayer private val localDriver = tplayer
private val localVehicle = vehicle private val localVehicle = vehicle
private val localAnnounce = self private val localAnnounce = self
override def Description: String = s"register a ${localVehicle.Definition.Name} manned by ${localDriver.Name}" override def description(): String = s"register a ${localVehicle.Definition.Name} manned by ${localDriver.Name}"
override def isComplete: Task.Resolution.Value = { def action(): Future[Any] = {
if (localVehicle.HasGUID) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
log.trace(s"Vehicle $localVehicle is registered")
localDriver.VehicleSeated = localVehicle.GUID localDriver.VehicleSeated = localVehicle.GUID
Vehicles.Own(localVehicle, localDriver) Vehicles.Own(localVehicle, localDriver)
localAnnounce ! PlayerLoaded(localDriver) localAnnounce ! PlayerLoaded(localDriver)
resolver ! Success(this) Future(true)
} }
}, },
List(GUIDTask.RegisterObjectTask(vehicle)(continent.GUID)) List(GUIDTask.registerObject(continent.GUID, vehicle))
) )
} }
@ -5887,65 +5844,44 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* the vehicle is being brought into existence from scratch and was never a member of any `Zone`. * the vehicle is being brought into existence from scratch and was never a member of any `Zone`.
* @param obj the `Vehicle` object * @param obj the `Vehicle` object
* @see `RegisterVehicle` * @see `RegisterVehicle`
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
def RegisterVehicleFromSpawnPad(obj: Vehicle, pad: VehicleSpawnPad, terminal: Terminal): TaskResolver.GiveTask = { private def registerVehicleFromSpawnPad(vehicle: Vehicle, pad: VehicleSpawnPad, terminal: Terminal): TaskBundle = {
TaskResolver.GiveTask( TaskBundle(
new Task() { new StraightforwardTask() {
private val localVehicle = obj private val localVehicle = vehicle
private val localPad = pad.Actor private val localPad = pad.Actor
private val localTerminal = terminal private val localTerminal = terminal
private val localPlayer = player private val localPlayer = player
override def Description: String = s"register a ${localVehicle.Definition.Name} for spawn pad" override def description(): String = s"register a ${localVehicle.Definition.Name} for spawn pad"
override def isComplete: Task.Resolution.Value = { def action(): Future[Any] = {
if (localVehicle.HasGUID) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
localPad ! VehicleSpawnPad.VehicleOrder(localPlayer, localVehicle, localTerminal) localPad ! VehicleSpawnPad.VehicleOrder(localPlayer, localVehicle, localTerminal)
resolver ! Success(this) Future(true)
} }
}, },
List(RegisterVehicle(obj)) List(registerVehicle(vehicle))
) )
} }
def RegisterDrivenVehicle(obj: Vehicle, driver: Player): TaskResolver.GiveTask = { private def registerDrivenVehicle(vehicle: Vehicle, driver: Player): TaskBundle = {
TaskResolver.GiveTask( TaskBundle(
new Task() { new StraightforwardTask() {
private val localVehicle = obj private val localVehicle = vehicle
private val localDriver = driver private val localDriver = driver
private val localAnnounce = self private val localAnnounce = self
override def Description: String = s"register a ${localVehicle.Definition.Name} driven by ${localDriver.Name}" override def description(): String = s"register a ${localVehicle.Definition.Name} driven by ${localDriver.Name}"
override def isComplete: Task.Resolution.Value = { def action(): Future[Any] = {
if (localVehicle.HasGUID && localDriver.HasGUID) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
log.trace(s"${localDriver.Name} 's vehicle ${localVehicle.Definition.Name} is registered")
localDriver.VehicleSeated = localVehicle.GUID localDriver.VehicleSeated = localVehicle.GUID
Vehicles.Own(localVehicle, localDriver) Vehicles.Own(localVehicle, localDriver)
localAnnounce ! NewPlayerLoaded(localDriver) //alerts WorldSessionActor localAnnounce ! NewPlayerLoaded(localDriver)
resolver ! Success(this) Future(true)
}
override def onFailure(ex: Throwable): Unit = {
localAnnounce ! PlayerFailedToLoad(localDriver) //alerts SessionActor
} }
}, },
List(GUIDTask.RegisterAvatar(driver)(continent.GUID), GUIDTask.RegisterVehicle(obj)(continent.GUID)) List(GUIDTask.registerAvatar(continent.GUID, driver), GUIDTask.registerVehicle(continent.GUID, vehicle))
) )
} }
@ -5954,55 +5890,39 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* After the projectile is registered to the curent zone's global unique identifier system, * After the projectile is registered to the curent zone's global unique identifier system,
* all connected clients save for the one that registered it will be informed about the projectile's "creation." * all connected clients save for the one that registered it will be informed about the projectile's "creation."
* @param obj the projectile to be registered * @param obj the projectile to be registered
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
def RegisterProjectile(obj: Projectile): TaskResolver.GiveTask = { private def registerProjectile(obj: Projectile): TaskBundle = {
val definition = obj.Definition TaskBundle(
TaskResolver.GiveTask( new StraightforwardTask() {
new Task() {
private val globalProjectile = obj private val globalProjectile = obj
private val localAnnounce = self private val localAnnounce = self
override def Description: String = s"register a ${globalProjectile.profile.Name}" override def description(): String = s"register a ${globalProjectile.profile.Name}"
override def isComplete: Task.Resolution.Value = { def action(): Future[Any] = {
if (globalProjectile.HasGUID) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
localAnnounce ! LoadedRemoteProjectile(globalProjectile.GUID, Some(globalProjectile)) localAnnounce ! LoadedRemoteProjectile(globalProjectile.GUID, Some(globalProjectile))
resolver ! Success(this) Future(true)
} }
}, },
List(GUIDTask.RegisterObjectTask(obj)(continent.GUID)) List(GUIDTask.registerObject(continent.GUID, obj))
) )
} }
def UnregisterDrivenVehicle(obj: Vehicle, driver: Player): TaskResolver.GiveTask = { private def unregisterDrivenVehicle(vehicle: Vehicle, driver: Player): TaskBundle = {
TaskResolver.GiveTask( TaskBundle(
new Task() { new StraightforwardTask() {
private val localVehicle = obj private val localVehicle = vehicle
private val localDriver = driver private val localDriver = driver
private val localAnnounce = self
override def Description: String = s"unregister a ${localVehicle.Definition.Name} driven by ${localDriver.Name}" override def description(): String = s"unregister a ${localVehicle.Definition.Name} driven by ${localDriver.Name}"
override def isComplete: Task.Resolution.Value = { def action(): Future[Any] = {
if (!localVehicle.HasGUID && !localDriver.HasGUID) { Future(true)
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
resolver ! Success(this)
} }
}, },
List(GUIDTask.UnregisterAvatar(driver)(continent.GUID), GUIDTask.UnregisterVehicle(obj)(continent.GUID)) List(GUIDTask.unregisterAvatar(continent.GUID, driver), GUIDTask.unregisterVehicle(continent.GUID, vehicle))
) )
} }
@ -6011,52 +5931,42 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* After the projectile is unregistered from the curent zone's global unique identifier system, * After the projectile is unregistered from the curent zone's global unique identifier system,
* all connected clients save for the one that registered it will be informed about the projectile's "destruction." * all connected clients save for the one that registered it will be informed about the projectile's "destruction."
* @param obj the projectile to be unregistered * @param obj the projectile to be unregistered
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
def UnregisterProjectile(obj: Projectile): TaskResolver.GiveTask = { private def unregisterProjectile(obj: Projectile): TaskBundle = {
TaskResolver.GiveTask( TaskBundle(
new Task() { new StraightforwardTask() {
private val globalProjectile = obj private val globalProjectile = obj
private val localAnnounce = continent.AvatarEvents private val localAnnounce = self
private val localMsg = AvatarServiceMessage(continent.id, AvatarAction.ObjectDelete(player.GUID, obj.GUID, 2)) private val localMsg = AvatarServiceMessage(continent.id, AvatarAction.ObjectDelete(player.GUID, obj.GUID, 2))
override def Description: String = s"unregister a ${globalProjectile.profile.Name}" override def description(): String = s"unregister a ${globalProjectile.profile.Name}"
override def isComplete: Task.Resolution.Value = { def action(): Future[Any] = {
if (!globalProjectile.HasGUID) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
localAnnounce ! localMsg localAnnounce ! localMsg
resolver ! Success(this) Future(true)
} }
}, },
List(GUIDTask.UnregisterObjectTask(obj)(continent.GUID)) List(GUIDTask.unregisterObject(continent.GUID, obj))
) )
} }
/** /**
* If the projectile object is unregistered, register it. * If the projectile object is unregistered, register it.
* If the projectile object is already registered, unregister it and then register it again. * If the projectile object is already registered, unregister it and then register it again.
* @see `RegisterProjectile(Projectile)` * @see `registerProjectile(Projectile)`
* @see `UnregisterProjectile(Projectile)` * @see `unregisterProjectile(Projectile)`
* @param obj the projectile to be registered (a second time?) * @param obj the projectile to be registered (a second time?)
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
def ReregisterProjectile(obj: Projectile): TaskResolver.GiveTask = { def reregisterProjectile(obj: Projectile): TaskBundle = {
val reg = RegisterProjectile(obj) val reg = registerProjectile(obj)
if (obj.HasGUID) { if (obj.HasGUID) {
TaskResolver.GiveTask( TaskBundle(
reg.task, reg.mainTask,
List( TaskBundle(
TaskResolver.GiveTask( reg.subTasks(0).mainTask,
reg.subs(0).task, unregisterProjectile(obj)
List(UnregisterProjectile(obj))
)
) )
) )
} else { } else {
@ -6371,13 +6281,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case veh: Vehicle => ModifyAmmunitionInVehicle(veh) case veh: Vehicle => ModifyAmmunitionInVehicle(veh)
case _ => ModifyAmmunition(obj) case _ => ModifyAmmunition(obj)
} }
val stowNewFunc: Equipment => TaskResolver.GiveTask = PutNewEquipmentInInventoryOrDrop(obj) val stowNewFunc: Equipment => TaskBundle = PutNewEquipmentInInventoryOrDrop(obj)
val stowFunc: Equipment => Future[Any] = PutEquipmentInInventoryOrDrop(obj) val stowFunc: Equipment => Future[Any] = PutEquipmentInInventoryOrDrop(obj)
xs.foreach(item => { xs.foreach(item => {
obj.Inventory -= item.start obj.Inventory -= item.start
sendResponse(ObjectDeleteMessage(item.obj.GUID, 0)) sendResponse(ObjectDeleteMessage(item.obj.GUID, 0))
continent.tasks ! GUIDTask.UnregisterObjectTask(item.obj)(continent.GUID) TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, item.obj))
}) })
//box will be the replacement ammo; give it the discovered magazine and load it into the weapon //box will be the replacement ammo; give it the discovered magazine and load it into the weapon
@ -6426,7 +6336,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
s"PerformToolAmmoChange: ${player.Name} takes ${originalBoxCapacity - splitReloadAmmo} from a box of $originalBoxCapacity $requestedAmmoType ammo" s"PerformToolAmmoChange: ${player.Name} takes ${originalBoxCapacity - splitReloadAmmo} from a box of $originalBoxCapacity $requestedAmmoType ammo"
) )
val boxForInventory = AmmoBox(box.Definition, splitReloadAmmo) val boxForInventory = AmmoBox(box.Definition, splitReloadAmmo)
continent.tasks ! stowNewFunc(boxForInventory) TaskWorkflow.execute(stowNewFunc(boxForInventory))
fullMagazine fullMagazine
} }
sendResponse( sendResponse(
@ -6471,10 +6381,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case Nil | List(_) => ; //done (the former case is technically not possible) case Nil | List(_) => ; //done (the former case is technically not possible)
case _ :: toUpdate => case _ :: toUpdate =>
modifyFunc(previousBox, 0) //update to changed capacity value modifyFunc(previousBox, 0) //update to changed capacity value
toUpdate.foreach(box => { continent.tasks ! stowNewFunc(box) }) toUpdate.foreach(box => { TaskWorkflow.execute(stowNewFunc(box)) })
} }
} else { } else {
continent.tasks ! GUIDTask.UnregisterObjectTask(previousBox)(continent.GUID) TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, previousBox))
} }
} }
} }
@ -6496,32 +6406,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
zone.Ground.tell(Zone.Ground.DropItem(item, obj.Position, Vector3.z(obj.Orientation.z)), obj.Actor) zone.Ground.tell(Zone.Ground.DropItem(item, obj.Position, Vector3.z(obj.Orientation.z)), obj.Actor)
} }
/**
* Register an `Equipment` item and then drop it on the ground.
* @see `NormalItemDrop`
* @param obj a `Container` object that represents where the item will be dropped;
* curried for callback
* @param zone the continent in which the item is being dropped;
* curried for callback
* @param item the item
*/
def NewItemDrop(obj: PlanetSideServerObject with Container, zone: Zone)(item: Equipment): TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private val localItem = item
private val localFunc: Equipment => Unit = NormalItemDrop(obj, zone)
override def Description: String = s"dropping a new ${localItem.Definition.Name} on the ground"
def Execute(resolver: ActorRef): Unit = {
localFunc(localItem)
resolver ! Success(this)
}
},
List(GUIDTask.RegisterEquipment(item)(zone.GUID))
)
}
/** /**
* After a weapon has finished shooting, determine if it needs to be sorted in a special way. * After a weapon has finished shooting, determine if it needs to be sorted in a special way.
* @param tool a weapon * @param tool a weapon
@ -7274,7 +7158,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* @see `AvatarAction.Release` * @see `AvatarAction.Release`
* @see `AvatarServiceMessage` * @see `AvatarServiceMessage`
* @see `FriskDeadBody` * @see `FriskDeadBody`
* @see `GUIDTask.UnregisterPlayer` * @see `GUIDTask.unregisterPlayer`
* @see `ObjectDeleteMessage` * @see `ObjectDeleteMessage`
* @see `WellLootedDeadBody` * @see `WellLootedDeadBody`
* @see `Zone.Corpse.Add` * @see `Zone.Corpse.Add`
@ -7291,7 +7175,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
zone.Population ! Zone.Population.Release(avatar) zone.Population ! Zone.Population.Release(avatar)
sendResponse(ObjectDeleteMessage(pguid, 0)) sendResponse(ObjectDeleteMessage(pguid, 0))
zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.ObjectDelete(pguid, pguid, 0)) zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.ObjectDelete(pguid, pguid, 0))
zone.tasks ! GUIDTask.UnregisterPlayer(tplayer)(zone.GUID) TaskWorkflow.execute(GUIDTask.unregisterPlayer(zone.GUID, tplayer))
} }
} }
@ -7788,7 +7672,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
projectile.quality(quality) projectile.quality(quality)
} else if (projectile.tool_def.Size == EquipmentSize.Melee) { } else if (projectile.tool_def.Size == EquipmentSize.Melee) {
//melee //melee
val quality = player.avatar.implants.flatten.find { entry => entry.definition == GlobalDefinitions.melee_booster } match { val quality = player.avatar.implants.flatten.find { entry => entry.definition.implantType == ImplantType.MeleeBooster } match {
case Some(booster) if booster.active && player.avatar.stamina > 9 => case Some(booster) if booster.active && player.avatar.stamina > 9 =>
avatarActor ! AvatarActor.ConsumeStamina(10) avatarActor ! AvatarActor.ConsumeStamina(10)
ProjectileQuality.Modified(25f) ProjectileQuality.Modified(25f)
@ -8210,7 +8094,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
if (!zoneReload && zoneId == continent.id) { if (!zoneReload && zoneId == continent.id) {
if (player.isBackpack) { // important! test the actor-wide player ref, not the parameter if (player.isBackpack) { // important! test the actor-wide player ref, not the parameter
// respawning from unregistered player // respawning from unregistered player
continent.tasks ! RegisterAvatar(targetPlayer) TaskWorkflow.execute(registerAvatar(targetPlayer))
} else { } else {
// move existing player; this is the one case where the original GUID is retained by the player // move existing player; this is the one case where the original GUID is retained by the player
self ! PlayerLoaded(targetPlayer) self ! PlayerLoaded(targetPlayer)
@ -8220,15 +8104,15 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
val original = player val original = player
if (player.isBackpack) { if (player.isBackpack) {
session = session.copy(player = targetPlayer) session = session.copy(player = targetPlayer)
taskThenZoneChange( TaskWorkflow.execute(taskThenZoneChange(
GUIDTask.UnregisterObjectTask(original.avatar.locker)(continent.GUID), GUIDTask.unregisterObject(continent.GUID, original.avatar.locker),
ICS.FindZone(_.id == zoneId, context.self) ICS.FindZone(_.id == zoneId, context.self)
) ))
} else if (player.HasGUID) { } else if (player.HasGUID) {
taskThenZoneChange( TaskWorkflow.execute(taskThenZoneChange(
GUIDTask.UnregisterAvatar(original)(continent.GUID), GUIDTask.unregisterAvatar(continent.GUID, original),
ICS.FindZone(_.id == zoneId, context.self) ICS.FindZone(_.id == zoneId, context.self)
) ))
} else { } else {
cluster ! ICS.FindZone(_.id == zoneId, context.self) cluster ! ICS.FindZone(_.id == zoneId, context.self)
} }
@ -8317,7 +8201,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
if (!zoneReload && zoneId == continent.id) { if (!zoneReload && zoneId == continent.id) {
if (vehicle.Definition == GlobalDefinitions.droppod) { if (vehicle.Definition == GlobalDefinitions.droppod) {
//instant action droppod in the same zone //instant action droppod in the same zone
continent.tasks ! RegisterDroppod(vehicle, player) TaskWorkflow.execute(registerDroppod(vehicle, player))
} else { } else {
//transferring a vehicle between spawn points (warp gates) in the same zone //transferring a vehicle between spawn points (warp gates) in the same zone
self ! PlayerLoaded(player) self ! PlayerLoaded(player)
@ -8325,10 +8209,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
} else if (vehicle.Definition == GlobalDefinitions.droppod) { } else if (vehicle.Definition == GlobalDefinitions.droppod) {
LoadZoneCommonTransferActivity() LoadZoneCommonTransferActivity()
player.Continent = zoneId //forward-set the continent id to perform a test player.Continent = zoneId //forward-set the continent id to perform a test
taskThenZoneChange( TaskWorkflow.execute(taskThenZoneChange(
GUIDTask.UnregisterAvatar(player)(continent.GUID), GUIDTask.unregisterAvatar(continent.GUID, player),
ICS.FindZone(_.id == zoneId, context.self) ICS.FindZone(_.id == zoneId, context.self)
) ))
} else { } else {
UnaccessContainer(vehicle) UnaccessContainer(vehicle)
LoadZoneCommonTransferActivity() LoadZoneCommonTransferActivity()
@ -8347,10 +8231,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
} }
//unregister vehicle and driver whole + GiveWorld //unregister vehicle and driver whole + GiveWorld
continent.Transport ! Zone.Vehicle.Despawn(vehicle) continent.Transport ! Zone.Vehicle.Despawn(vehicle)
taskThenZoneChange( TaskWorkflow.execute(taskThenZoneChange(
UnregisterDrivenVehicle(vehicle, player), unregisterDrivenVehicle(vehicle, player),
ICS.FindZone(_.id == zoneId, context.self) ICS.FindZone(_.id == zoneId, context.self)
) ))
} }
} }
@ -8369,7 +8253,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* A reference to the top-level ferrying vehicle's former globally unique identifier has been retained for this purpose. * A reference to the top-level ferrying vehicle's former globally unique identifier has been retained for this purpose.
* This vehicle can be deleted for everyone if no more work can be detected. * This vehicle can be deleted for everyone if no more work can be detected.
* *
* @see `GUIDTask.UnregisterPlayer` * @see `GUIDTask.unregisterPlayer`
* @see `LoadZoneCommonTransferActivity` * @see `LoadZoneCommonTransferActivity`
* @see `Vehicles.AllGatedOccupantsInSameZone` * @see `Vehicles.AllGatedOccupantsInSameZone`
* @see `PlayerLoaded` * @see `PlayerLoaded`
@ -8393,10 +8277,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
val continentId = continent.id val continentId = continent.id
interstellarFerryTopLevelGUID = None interstellarFerryTopLevelGUID = None
taskThenZoneChange( TaskWorkflow.execute(taskThenZoneChange(
GUIDTask.UnregisterAvatar(player)(continent.GUID), GUIDTask.unregisterAvatar(continent.GUID, player),
ICS.FindZone(_.id == zoneId, context.self) ICS.FindZone(_.id == zoneId, context.self)
) ))
} }
} }
@ -8439,20 +8323,24 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
/** Before changing zones, perform the following task (which can be a nesting of subtasks). */ /** Before changing zones, perform the following task (which can be a nesting of subtasks). */
def taskThenZoneChange( def taskThenZoneChange(
task: TaskResolver.GiveTask, task: TaskBundle,
zoneMessage: ICS.FindZone zoneMessage: ICS.FindZone
): Unit = { ): TaskBundle = {
continent.tasks ! TaskResolver.GiveTask( TaskBundle(
new Task() { new StraightforwardTask() {
override def isComplete: Task.Resolution.Value = task.task.isComplete val localAvatar = avatar
val localZone = continent
val localCluster = cluster
def Execute(resolver: ActorRef): Unit = { override def description() : String = s"doing ${task.description()} before transferring zones"
continent.Population ! Zone.Population.Leave(avatar)
def action(): Future[Any] = {
continent.Population ! Zone.Population.Leave(localAvatar)
cluster ! zoneMessage cluster ! zoneMessage
resolver ! Success(this) Future(true)
} }
}, },
List(task) task
) )
} }
@ -8474,7 +8362,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
avatarActor ! AvatarActor.SetVehicle(None) avatarActor ! AvatarActor.SetVehicle(None)
} }
RemoveBoomerTriggersFromInventory().foreach(obj => { RemoveBoomerTriggersFromInventory().foreach(obj => {
continent.tasks ! GUIDTask.UnregisterObjectTask(obj)(continent.GUID) TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, obj))
}) })
Deployables.Disown(continent, avatar, self) Deployables.Disown(continent, avatar, self)
drawDeloyableIcon = RedrawDeployableIcons //important for when SetCurrentAvatar initializes the UI next zone drawDeloyableIcon = RedrawDeployableIcons //important for when SetCurrentAvatar initializes the UI next zone
@ -8896,7 +8784,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
continent.id, continent.id,
AvatarAction.ProjectileExplodes(player.GUID, projectile_guid, projectile) AvatarAction.ProjectileExplodes(player.GUID, projectile_guid, projectile)
) )
continent.tasks ! UnregisterProjectile(projectile) TaskWorkflow.execute(unregisterProjectile(projectile))
projectiles(local_index) match { projectiles(local_index) match {
case Some(obj) if !obj.isResolved => obj.Miss() case Some(obj) if !obj.isResolved => obj.Miss()
case _ => ; case _ => ;
@ -8933,7 +8821,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
tplayer.VehicleSeated = None tplayer.VehicleSeated = None
zone.Population ! Zone.Population.Release(avatar) zone.Population ! Zone.Population.Release(avatar)
sendResponse(ObjectDeleteMessage(tplayer.GUID, 0)) sendResponse(ObjectDeleteMessage(tplayer.GUID, 0))
zone.tasks ! GUIDTask.UnregisterPlayer(tplayer)(zone.GUID) TaskWorkflow.execute(GUIDTask.unregisterPlayer(zone.GUID, tplayer))
} }
} }
@ -9144,15 +9032,15 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
log.trace( log.trace(
s"WeaponFireMessage: ${player.Name}'s ${projectile_info.Name} is a remote projectile" s"WeaponFireMessage: ${player.Name}'s ${projectile_info.Name} is a remote projectile"
) )
continent.tasks ! (if (projectile.HasGUID) { if (projectile.HasGUID) {
continent.AvatarEvents ! AvatarServiceMessage( continent.AvatarEvents ! AvatarServiceMessage(
continent.id, continent.id,
AvatarAction.ProjectileExplodes(player.GUID, projectile.GUID, projectile) AvatarAction.ProjectileExplodes(player.GUID, projectile.GUID, projectile)
) )
ReregisterProjectile(projectile) TaskWorkflow.execute(reregisterProjectile(projectile))
} else { } else {
RegisterProjectile(projectile) TaskWorkflow.execute(registerProjectile(projectile))
}) }
} }
projectilesToCleanUp(projectileIndex) = false projectilesToCleanUp(projectileIndex) = false

View file

@ -4,7 +4,7 @@ import akka.actor.ActorRef
import akka.pattern.{AskTimeoutException, ask} import akka.pattern.{AskTimeoutException, ask}
import akka.util.Timeout import akka.util.Timeout
import net.psforever.objects.equipment.{Ammo, Equipment} import net.psforever.objects.equipment.{Ammo, Equipment}
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} import net.psforever.objects.guid._
import net.psforever.objects.inventory.{Container, InventoryItem} import net.psforever.objects.inventory.{Container, InventoryItem}
import net.psforever.objects.locker.LockerContainer import net.psforever.objects.locker.LockerContainer
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
@ -73,32 +73,29 @@ object WorldSession {
* Equipment will go wherever it fits in containing object, or be dropped if it fits nowhere. * Equipment will go wherever it fits in containing object, or be dropped if it fits nowhere.
* Item swapping during the placement is not allowed. * Item swapping during the placement is not allowed.
* @see `ChangeAmmoMessage` * @see `ChangeAmmoMessage`
* @see `GUIDTask.RegisterEquipment` * @see `GUIDTask.registerEquipment`
* @see `PutEquipmentInInventoryOrDrop` * @see `PutEquipmentInInventoryOrDrop`
* @see `Task` * @see `Task`
* @see `TaskResolver.GiveTask` * @see `TaskBundle`
* @param obj the container * @param obj the container
* @param item the item being manipulated * @param item the item being manipulated
* @return a `TaskResolver` object * @return a `TaskBundle` object
*/ */
def PutNewEquipmentInInventorySlot( def PutNewEquipmentInInventorySlot(
obj: PlanetSideServerObject with Container obj: PlanetSideServerObject with Container
)(item: Equipment, slot: Int): TaskResolver.GiveTask = { )(item: Equipment, slot: Int): TaskBundle = {
val localZone = obj.Zone val localZone = obj.Zone
TaskResolver.GiveTask( TaskBundle(
new Task() { new StraightforwardTask() {
private val localContainer = obj private val localContainer = obj
private val localItem = item private val localItem = item
private val localSlot = slot private val localSlot = slot
override def isComplete: Task.Resolution.Value = Task.Resolution.Success def action(): Future[Any] = {
def Execute(resolver: ActorRef): Unit = {
PutEquipmentInInventorySlot(localContainer)(localItem, localSlot) PutEquipmentInInventorySlot(localContainer)(localItem, localSlot)
resolver ! Success(this)
} }
}, },
List(GUIDTask.RegisterEquipment(item)(localZone.GUID)) GUIDTask.registerEquipment(localZone.GUID, item)
) )
} }
@ -108,31 +105,28 @@ object WorldSession {
* Equipment will go wherever it fits in containing object, or be dropped if it fits nowhere. * Equipment will go wherever it fits in containing object, or be dropped if it fits nowhere.
* Item swapping during the placement is not allowed. * Item swapping during the placement is not allowed.
* @see `ChangeAmmoMessage` * @see `ChangeAmmoMessage`
* @see `GUIDTask.RegisterEquipment` * @see `GUIDTask.registerEquipment`
* @see `PutEquipmentInInventoryOrDrop` * @see `PutEquipmentInInventoryOrDrop`
* @see `Task` * @see `Task`
* @see `TaskResolver.GiveTask` * @see `TaskBundle`
* @param obj the container * @param obj the container
* @param item the item being manipulated * @param item the item being manipulated
* @return a `TaskResolver` object * @return a `TaskBundle` object
*/ */
def PutNewEquipmentInInventoryOrDrop( def PutNewEquipmentInInventoryOrDrop(
obj: PlanetSideServerObject with Container obj: PlanetSideServerObject with Container
)(item: Equipment): TaskResolver.GiveTask = { )(item: Equipment): TaskBundle = {
val localZone = obj.Zone val localZone = obj.Zone
TaskResolver.GiveTask( TaskBundle(
new Task() { new StraightforwardTask() {
private val localContainer = obj private val localContainer = obj
private val localItem = item private val localItem = item
override def isComplete: Task.Resolution.Value = Task.Resolution.Success def action(): Future[Any] = {
def Execute(resolver: ActorRef): Unit = {
PutEquipmentInInventoryOrDrop(localContainer)(localItem) PutEquipmentInInventoryOrDrop(localContainer)(localItem)
resolver ! Success(this)
} }
}, },
List(GUIDTask.RegisterEquipment(item)(localZone.GUID)) GUIDTask.registerEquipment(localZone.GUID, item)
) )
} }
@ -149,7 +143,7 @@ object WorldSession {
* @see `Containable.PutItemAway` * @see `Containable.PutItemAway`
* @see `Future.onComplete` * @see `Future.onComplete`
* @see `Future.recover` * @see `Future.recover`
* @see `GUIDTask.UnregisterEquipment` * @see `GUIDTask.unregisterEquipment`
* @see `tell` * @see `tell`
* @see `Zone.AvatarEvents` * @see `Zone.AvatarEvents`
* @param obj the container * @param obj the container
@ -165,7 +159,7 @@ object WorldSession {
val result = ask(localContainer.Actor, Containable.PutItemInSlotOnly(localItem, slot)) val result = ask(localContainer.Actor, Containable.PutItemInSlotOnly(localItem, slot))
result.onComplete { result.onComplete {
case Failure(_) | Success(_: Containable.CanNotPutItemInSlot) => case Failure(_) | Success(_: Containable.CanNotPutItemInSlot) =>
localContainer.Zone.tasks ! GUIDTask.UnregisterEquipment(localItem)(localContainer.Zone.GUID) TaskWorkflow.execute(GUIDTask.unregisterEquipment(localContainer.Zone.GUID, localItem))
case _ => ; case _ => ;
} }
result result
@ -177,43 +171,33 @@ object WorldSession {
* This request will (probably) be coincidental with a number of other such requests based on that loadout * This request will (probably) be coincidental with a number of other such requests based on that loadout
* so items must be rigidly placed else cascade into a chaostic order. * so items must be rigidly placed else cascade into a chaostic order.
* Item swapping during the placement is not allowed. * Item swapping during the placement is not allowed.
* @see `GUIDTask.RegisterEquipment` * @see `GUIDTask.registerEquipment`
* @see `PutEquipmentInInventorySlot` * @see `PutEquipmentInInventorySlot`
* @see `Task` * @see `Task`
* @see `TaskResolver.GiveTask` * @see `TaskBundle`
* @param obj the container * @param obj the container
* @param item the item being manipulated * @param item the item being manipulated
* @param slot where the item will be placed in the container * @param slot where the item will be placed in the container
* @return a `TaskResolver` object * @return a `TaskBundle` object
*/ */
def PutLoadoutEquipmentInInventory( def PutLoadoutEquipmentInInventory(
obj: PlanetSideServerObject with Container obj: PlanetSideServerObject with Container
)(item: Equipment, slot: Int): TaskResolver.GiveTask = { )(item: Equipment, slot: Int): TaskBundle = {
val localZone = obj.Zone val localZone = obj.Zone
TaskResolver.GiveTask( TaskBundle(
new Task() { new StraightforwardTask() {
private val localContainer = obj private val localContainer = obj
private val localItem = item private val localItem = item
private val localSlot = slot private val localSlot = slot
private val localFunc: (Equipment, Int) => Future[Any] = PutEquipmentInInventorySlot(obj) private val localFunc: (Equipment, Int) => Future[Any] = PutEquipmentInInventorySlot(obj)
override def Timeout: Long = 1000 override def description(): String = s"PutEquipmentInInventorySlot - ${localItem.Definition.Name}"
override def isComplete: Task.Resolution.Value = { def action(): Future[Any] = {
if (localItem.HasGUID && localContainer.Find(localItem).nonEmpty)
Task.Resolution.Success
else
Task.Resolution.Incomplete
}
override def Description: String = s"PutEquipmentInInventorySlot - ${localItem.Definition.Name}"
def Execute(resolver: ActorRef): Unit = {
localFunc(localItem, localSlot) localFunc(localItem, localSlot)
resolver ! Success(this)
} }
}, },
List(GUIDTask.RegisterEquipment(item)(localZone.GUID)) GUIDTask.registerEquipment(localZone.GUID, item)
) )
} }
@ -227,8 +211,8 @@ object WorldSession {
* @see `ask` * @see `ask`
* @see `Containable.CanNotPutItemInSlot` * @see `Containable.CanNotPutItemInSlot`
* @see `Containable.PutItemInSlotOnly` * @see `Containable.PutItemInSlotOnly`
* @see `GUIDTask.RegisterEquipment` * @see `GUIDTask.registerEquipment`
* @see `GUIDTask.UnregisterEquipment` * @see `GUIDTask.unregisterEquipment`
* @see `Future.onComplete` * @see `Future.onComplete`
* @see `PutEquipmentInInventorySlot` * @see `PutEquipmentInInventorySlot`
* @see `TerminalMessageOnTimeout` * @see `TerminalMessageOnTimeout`
@ -236,31 +220,22 @@ object WorldSession {
* @param player na * @param player na
* @param term na * @param term na
* @param item the item being manipulated * @param item the item being manipulated
* @return a `TaskResolver` object * @return a `TaskBundle` object
*/ */
def BuyNewEquipmentPutInInventory( def BuyNewEquipmentPutInInventory(
obj: PlanetSideServerObject with Container, obj: PlanetSideServerObject with Container,
player: Player, player: Player,
term: PlanetSideGUID term: PlanetSideGUID
)(item: Equipment): TaskResolver.GiveTask = { )(item: Equipment): TaskBundle = {
val localZone = obj.Zone val localZone = obj.Zone
TaskResolver.GiveTask( TaskBundle(
new Task() { new StraightforwardTask() {
private val localContainer = obj private val localContainer = obj
private val localItem = item private val localItem = item
private val localPlayer = player private val localPlayer = player
private val localTermMsg: Boolean => Unit = TerminalResult(term, localPlayer, TransactionType.Buy) private val localTermMsg: Boolean => Unit = TerminalResult(term, localPlayer, TransactionType.Buy)
override def Timeout: Long = 1000 def action(): Future[Any] = {
override def isComplete: Task.Resolution.Value = {
if (localItem.HasGUID && localContainer.Find(localItem).nonEmpty)
Task.Resolution.Success
else
Task.Resolution.Incomplete
}
def Execute(resolver: ActorRef): Unit = {
TerminalMessageOnTimeout( TerminalMessageOnTimeout(
ask(localContainer.Actor, Containable.PutItemAway(localItem)), ask(localContainer.Actor, Containable.PutItemAway(localItem)),
localTermMsg localTermMsg
@ -279,16 +254,16 @@ object WorldSession {
localTermMsg(true) localTermMsg(true)
} }
} else { } else {
localContainer.Zone.tasks ! GUIDTask.UnregisterEquipment(localItem)(localContainer.Zone.GUID) TaskWorkflow.execute(GUIDTask.unregisterEquipment(localContainer.Zone.GUID, localItem))
localTermMsg(false) localTermMsg(false)
} }
case _ => case _ =>
localTermMsg(true) localTermMsg(true)
} }
resolver ! Success(this) Future(true)
} }
}, },
List(GUIDTask.RegisterEquipment(item)(localZone.GUID)) GUIDTask.registerEquipment(localZone.GUID, item)
) )
} }
@ -306,44 +281,35 @@ object WorldSession {
* @see `AvatarAction.SendResponse` * @see `AvatarAction.SendResponse`
* @see `Containable.CanNotPutItemInSlot` * @see `Containable.CanNotPutItemInSlot`
* @see `Containable.PutItemInSlotOnly` * @see `Containable.PutItemInSlotOnly`
* @see `GUIDTask.RegisterEquipment` * @see `GUIDTask.registerEquipment`
* @see `GUIDTask.UnregisterEquipment` * @see `GUIDTask.unregisterEquipment`
* @see `Future.onComplete` * @see `Future.onComplete`
* @see `ObjectHeldMessage` * @see `ObjectHeldMessage`
* @see `Player.DrawnSlot` * @see `Player.DrawnSlot`
* @see `Player.LastDrawnSlot` * @see `Player.LastDrawnSlot`
* @see `Service.defaultPlayerGUID` * @see `Service.defaultPlayerGUID`
* @see `TaskResolver.GiveTask` * @see `TaskBundle`
* @see `Zone.AvatarEvents` * @see `Zone.AvatarEvents`
* @param player the player whose visible slot will be equipped and drawn * @param player the player whose visible slot will be equipped and drawn
* @param item the item to equip * @param item the item to equip
* @param slot the slot in which the item will be equipped * @param slot the slot in which the item will be equipped
* @return a `TaskResolver` object * @return a `TaskBundle` object
*/ */
def HoldNewEquipmentUp(player: Player)(item: Equipment, slot: Int): TaskResolver.GiveTask = { def HoldNewEquipmentUp(player: Player)(item: Equipment, slot: Int): TaskBundle = {
if (player.VisibleSlots.contains(slot)) { if (player.VisibleSlots.contains(slot)) {
val localZone = player.Zone val localZone = player.Zone
TaskResolver.GiveTask( TaskBundle(
new Task() { new StraightforwardTask() {
private val localPlayer = player private val localPlayer = player
private val localGUID = player.GUID private val localGUID = player.GUID
private val localItem = item private val localItem = item
private val localSlot = slot private val localSlot = slot
override def Timeout: Long = 1000 def action(): Future[Any] = {
override def isComplete: Task.Resolution.Value = {
if (localPlayer.DrawnSlot == localSlot)
Task.Resolution.Success
else
Task.Resolution.Incomplete
}
def Execute(resolver: ActorRef): Unit = {
ask(localPlayer.Actor, Containable.PutItemInSlotOnly(localItem, localSlot)) ask(localPlayer.Actor, Containable.PutItemInSlotOnly(localItem, localSlot))
.onComplete { .onComplete {
case Failure(_) | Success(_: Containable.CanNotPutItemInSlot) => case Failure(_) | Success(_: Containable.CanNotPutItemInSlot) =>
localPlayer.Zone.tasks ! GUIDTask.UnregisterEquipment(localItem)(localZone.GUID) TaskWorkflow.execute(GUIDTask.unregisterEquipment(localZone.GUID, localItem))
case _ => case _ =>
if (localPlayer.DrawnSlot != Player.HandsDownSlot) { if (localPlayer.DrawnSlot != Player.HandsDownSlot) {
localPlayer.DrawnSlot = Player.HandsDownSlot localPlayer.DrawnSlot = Player.HandsDownSlot
@ -365,10 +331,10 @@ object WorldSession {
AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectHeldMessage(localGUID, localSlot, false)) AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectHeldMessage(localGUID, localSlot, false))
) )
} }
resolver ! Success(this) Future(this)
} }
}, },
List(GUIDTask.RegisterEquipment(item)(localZone.GUID)) GUIDTask.registerEquipment(localZone.GUID, item)
) )
} else { } else {
//TODO log.error //TODO log.error
@ -460,7 +426,7 @@ object WorldSession {
* @see `Containable.RemoveItemFromSlot` * @see `Containable.RemoveItemFromSlot`
* @see `Future.onComplete` * @see `Future.onComplete`
* @see `Future.recover` * @see `Future.recover`
* @see `GUIDTask.UnregisterEquipment` * @see `GUIDTask.unregisterEquipment`
* @see `Zone.AvatarEvents` * @see `Zone.AvatarEvents`
* @param obj the container to search * @param obj the container to search
* @param item the item to find and remove from the container * @param item the item to find and remove from the container
@ -474,7 +440,7 @@ object WorldSession {
val result = ask(localContainer.Actor, Containable.RemoveItemFromSlot(localItem)) val result = ask(localContainer.Actor, Containable.RemoveItemFromSlot(localItem))
result.onComplete { result.onComplete {
case Success(Containable.ItemFromSlot(_, Some(_), Some(_))) => case Success(Containable.ItemFromSlot(_, Some(_), Some(_))) =>
localContainer.Zone.tasks ! GUIDTask.UnregisterEquipment(localItem)(localContainer.Zone.GUID) TaskWorkflow.execute(GUIDTask.unregisterEquipment(localContainer.Zone.GUID, localItem))
case _ => case _ =>
} }
result result
@ -492,7 +458,7 @@ object WorldSession {
* @see `Containable.RemoveItemFromSlot` * @see `Containable.RemoveItemFromSlot`
* @see `Future.onComplete` * @see `Future.onComplete`
* @see `Future.recover` * @see `Future.recover`
* @see `GUIDTask.UnregisterEquipment` * @see `GUIDTask.unregisterEquipment`
* @see `RemoveOldEquipmentFromInventory` * @see `RemoveOldEquipmentFromInventory`
* @see `TerminalMessageOnTimeout` * @see `TerminalMessageOnTimeout`
* @see `TerminalResult` * @see `TerminalResult`
@ -517,7 +483,7 @@ object WorldSession {
) )
result.onComplete { result.onComplete {
case Success(Containable.ItemFromSlot(_, Some(item), Some(_))) => case Success(Containable.ItemFromSlot(_, Some(item), Some(_))) =>
localContainer.Zone.tasks ! GUIDTask.UnregisterEquipment(item)(localContainer.Zone.GUID) TaskWorkflow.execute(GUIDTask.unregisterEquipment(localContainer.Zone.GUID, item))
localTermMsg(true) localTermMsg(true)
case _ => case _ =>
localTermMsg(false) localTermMsg(false)
@ -538,7 +504,7 @@ object WorldSession {
* @see `LockerContainer` * @see `LockerContainer`
* @see `RemoveEquipmentFromLockerContainer` * @see `RemoveEquipmentFromLockerContainer`
* @see `StowEquipmentInLockerContainer` * @see `StowEquipmentInLockerContainer`
* @see `TaskResolver` * @see `TaskBundle`
* @param toChannel broadcast channel name for a manual packet callback * @param toChannel broadcast channel name for a manual packet callback
* @param source the container in which the item is to be removed * @param source the container in which the item is to be removed
* @param destination the container into which the item is to be placed * @param destination the container into which the item is to be placed
@ -573,14 +539,14 @@ object WorldSession {
* @see `Container` * @see `Container`
* @see `Equipment` * @see `Equipment`
* @see `GridInventory.CheckCollisionsVar` * @see `GridInventory.CheckCollisionsVar`
* @see `GUIDTask.RegisterEquipment` * @see `GUIDTask.registerEquipment`
* @see `GUIDTask.UnregisterEquipment` * @see `GUIDTask.unregisterEquipment`
* @see `IdentifiableEntity.Invalidate` * @see `IdentifiableEntity.Invalidate`
* @see `LockerContainer` * @see `LockerContainer`
* @see `Service` * @see `Service`
* @see `Task` * @see `Task`
* @see `TaskResolver` * @see `TaskBundle`
* @see `TaskResolver.GiveTask` * @see `TaskBundle`
* @see `Zone.AvatarEvents` * @see `Zone.AvatarEvents`
* @param toChannel broadcast channel name for a manual packet callback * @param toChannel broadcast channel name for a manual packet callback
* @param source the container in which the item is to be removed * @param source the container in which the item is to be removed
@ -610,7 +576,7 @@ object WorldSession {
(false, None) (false, None)
} }
if (performSwap) { if (performSwap) {
def moveItemTaskFunc(toSlot: Int): Task = new Task() { def moveItemTaskFunc(toSlot: Int): Task = new StraightforwardTask() {
val localGUID = swapItemGUID //the swap item's original GUID, if any swap item val localGUID = swapItemGUID //the swap item's original GUID, if any swap item
val localChannel = toChannel val localChannel = toChannel
val localSource = source val localSource = source
@ -621,21 +587,13 @@ object WorldSession {
val localMoveOnComplete: Try[Any] => Unit = { val localMoveOnComplete: Try[Any] => Unit = {
case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) => case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) =>
//swapItem is not registered right now, we can not drop the item without re-registering it //swapItem is not registered right now, we can not drop the item without re-registering it
localSource.Zone.tasks ! PutNewEquipmentInInventorySlot(localSource)(swapItem, localSrcSlot) TaskWorkflow.execute(PutNewEquipmentInInventorySlot(localSource)(swapItem, localSrcSlot))
case _ => ; case _ => ;
} }
override def Description: String = s"unregistering $localItem before stowing in $localDestination" override def description(): String = s"unregistering $localItem before stowing in $localDestination"
override def isComplete: Task.Resolution.Value = { def action(): Future[Any] = {
if (localItem.HasGUID && localDestination.Find(localItem).contains(localDestSlot)) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
localGUID match { localGUID match {
case Some(guid) => case Some(guid) =>
//see LockerContainerControl.RemoveItemFromSlotCallback //see LockerContainerControl.RemoveItemFromSlotCallback
@ -647,15 +605,15 @@ object WorldSession {
} }
val moveResult = ask(localDestination.Actor, Containable.PutItemInSlotOrAway(localItem, Some(localDestSlot))) val moveResult = ask(localDestination.Actor, Containable.PutItemInSlotOrAway(localItem, Some(localDestSlot)))
moveResult.onComplete(localMoveOnComplete) moveResult.onComplete(localMoveOnComplete)
resolver ! Success(this) moveResult
} }
} }
val resultOnComplete: Try[Any] => Unit = { val resultOnComplete: Try[Any] => Unit = {
case Success(Containable.ItemFromSlot(fromSource, Some(itemToMove), Some(fromSlot))) => case Success(Containable.ItemFromSlot(fromSource, Some(itemToMove), Some(fromSlot))) =>
destination.Zone.tasks ! TaskResolver.GiveTask( TaskWorkflow.execute(TaskBundle(
moveItemTaskFunc(fromSlot), moveItemTaskFunc(fromSlot),
List(GUIDTask.UnregisterEquipment(itemToMove)(fromSource.Zone.GUID)) GUIDTask.unregisterEquipment(fromSource.Zone.GUID, itemToMove)
) ))
case _ => ; case _ => ;
} }
val result = ask(source.Actor, Containable.RemoveItemFromSlot(item)) val result = ask(source.Actor, Containable.RemoveItemFromSlot(item))
@ -673,14 +631,14 @@ object WorldSession {
* @see `Container` * @see `Container`
* @see `Equipment` * @see `Equipment`
* @see `GridInventory.CheckCollisionsVar` * @see `GridInventory.CheckCollisionsVar`
* @see `GUIDTask.RegisterEquipment` * @see `GUIDTask.registerEquipment`
* @see `GUIDTask.UnregisterEquipment` * @see `GUIDTask.unregisterEquipment`
* @see `IdentifiableEntity.Invalidate` * @see `IdentifiableEntity.Invalidate`
* @see `LockerContainer` * @see `LockerContainer`
* @see `Service` * @see `Service`
* @see `Task` * @see `Task`
* @see `TaskResolver` * @see `TaskBundle`
* @see `TaskResolver.GiveTask` * @see `TaskBundle`
* @see `Zone.AvatarEvents` * @see `Zone.AvatarEvents`
* @param toChannel broadcast channel name for a manual packet callback * @param toChannel broadcast channel name for a manual packet callback
* @param source the container in which the item is to be removed * @param source the container in which the item is to be removed
@ -695,8 +653,8 @@ object WorldSession {
item: Equipment, item: Equipment,
dest: Int dest: Int
): Unit = { ): Unit = {
destination.Zone.tasks ! TaskResolver.GiveTask( TaskWorkflow.execute(TaskBundle(
new Task() { new StraightforwardTask() {
val localGUID = item.GUID //original GUID val localGUID = item.GUID //original GUID
val localChannel = toChannel val localChannel = toChannel
val localSource = source val localSource = source
@ -717,25 +675,16 @@ object WorldSession {
case _ => ; case _ => ;
} }
override def Description: String = s"registering $localItem in ${localDestination.Zone.id} before removing from $localSource" override def description(): String = s"registering $localItem in ${localDestination.Zone.id} before removing from $localSource"
override def isComplete: Task.Resolution.Value = { def action(): Future[Any] = {
if (localItem.HasGUID && localDestination.Find(localItem).isEmpty) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
val zone = localSource.Zone val zone = localSource.Zone
//see LockerContainerControl.RemoveItemFromSlotCallback //see LockerContainerControl.RemoveItemFromSlotCallback
zone.AvatarEvents ! AvatarServiceMessage(localChannel, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, localGUID)) zone.AvatarEvents ! AvatarServiceMessage(localChannel, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, localGUID))
localSource.Actor ! Containable.MoveItem(localDestination, localItem, localSlot) ask(localSource.Actor, Containable.MoveItem(localDestination, localItem, localSlot))
resolver ! Success(this)
} }
}, },
List(GUIDTask.RegisterEquipment(item)(destination.Zone.GUID)) GUIDTask.registerEquipment(destination.Zone.GUID, item))
) )
} }
@ -884,21 +833,21 @@ object WorldSession {
} }
} }
def CallBackForTask(task: TaskResolver.GiveTask, sendTo: ActorRef, pass: Any): TaskResolver.GiveTask = { def CallBackForTask(task: TaskBundle, sendTo: ActorRef, pass: Any): TaskBundle = {
TaskResolver.GiveTask( TaskBundle(
new Task() { new StraightforwardTask() {
private val localDesc = task.task.Description private val localDesc = task.description()
private val destination = sendTo private val destination = sendTo
private val passMsg = pass private val passMsg = pass
override def Description: String = s"callback for tasking $localDesc" override def description(): String = s"callback for tasking $localDesc"
def Execute(resolver: ActorRef): Unit = { def action() : Future[Any] = {
destination ! passMsg destination ! passMsg
resolver ! Success(this) Future(this)
} }
}, },
List(task) task
) )
} }
} }

View file

@ -4,7 +4,7 @@ package net.psforever.objects
import akka.actor.{ActorContext, Props} import akka.actor.{ActorContext, Props}
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry} import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
import net.psforever.objects.ce.{Deployable, DeployedItem} import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.guid.GUIDTask import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.vital.etc.TriggerUsedReason import net.psforever.objects.vital.etc.TriggerUsedReason
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
@ -99,7 +99,7 @@ class BoomerDeployableControl(mine: BoomerDeployable)
zone.id, zone.id,
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, trigger.GUID) AvatarAction.ObjectDelete(Service.defaultPlayerGUID, trigger.GUID)
) )
zone.tasks ! GUIDTask.UnregisterObjectTask(trigger)(zone.GUID) TaskWorkflow.execute(GUIDTask.unregisterObject(zone.GUID, trigger))
case None => ; case None => ;
} }
} }

View file

@ -9,6 +9,7 @@ import net.psforever.objects.definition.converter._
import net.psforever.objects.equipment._ import net.psforever.objects.equipment._
import net.psforever.objects.geometry.GeometryForm import net.psforever.objects.geometry.GeometryForm
import net.psforever.objects.inventory.InventoryTile import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.locker.LockerContainerDefinition
import net.psforever.objects.serverobject.aura.Aura import net.psforever.objects.serverobject.aura.Aura
import net.psforever.objects.serverobject.doors.DoorDefinition import net.psforever.objects.serverobject.doors.DoorDefinition
import net.psforever.objects.serverobject.generator.GeneratorDefinition import net.psforever.objects.serverobject.generator.GeneratorDefinition
@ -426,11 +427,7 @@ object GlobalDefinitions {
Equipment (locker_container, kits, ammunition, weapons) Equipment (locker_container, kits, ammunition, weapons)
*/ */
import net.psforever.packet.game.objectcreate.ObjectClass import net.psforever.packet.game.objectcreate.ObjectClass
val locker_container = new EquipmentDefinition(456) { val locker_container = new LockerContainerDefinition()
Name = "locker_container"
Size = EquipmentSize.Inventory
Packet = new LockerContainerConverter()
}
val medkit = KitDefinition(Kits.medkit) val medkit = KitDefinition(Kits.medkit)

View file

@ -7,7 +7,7 @@ import net.psforever.objects.avatar.PlayerControl
import net.psforever.objects.ce.Deployable import net.psforever.objects.ce.Deployable
import net.psforever.objects.definition.ExoSuitDefinition import net.psforever.objects.definition.ExoSuitDefinition
import net.psforever.objects.equipment.EquipmentSlot import net.psforever.objects.equipment.EquipmentSlot
import net.psforever.objects.guid.GUIDTask import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.inventory.InventoryItem import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.loadouts.InfantryLoadout import net.psforever.objects.loadouts.InfantryLoadout
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
@ -341,7 +341,7 @@ object Players {
def commonDestroyConstructionItem(player: Player, tool: ConstructionItem, index: Int): Unit = { def commonDestroyConstructionItem(player: Player, tool: ConstructionItem, index: Int): Unit = {
val zone = player.Zone val zone = player.Zone
if (safelyRemoveConstructionItemFromSlot(player, tool, index, "CommonDestroyConstructionItem")) { if (safelyRemoveConstructionItemFromSlot(player, tool, index, "CommonDestroyConstructionItem")) {
zone.tasks ! GUIDTask.UnregisterEquipment(tool)(zone.GUID) TaskWorkflow.execute(GUIDTask.unregisterEquipment(zone.GUID, tool))
} }
} }

View file

@ -6,7 +6,7 @@ import net.psforever.objects.ce.{Deployable, DeployableBehavior, DeployedItem}
import net.psforever.objects.definition.DeployableDefinition import net.psforever.objects.definition.DeployableDefinition
import net.psforever.objects.definition.converter.SmallTurretConverter import net.psforever.objects.definition.converter.SmallTurretConverter
import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit} import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit}
import net.psforever.objects.guid.GUIDTask import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.serverobject.damage.Damageable.Target
@ -132,6 +132,6 @@ class TurretControl(turret: TurretDeployable)
override def unregisterDeployable(obj: Deployable): Unit = { override def unregisterDeployable(obj: Deployable): Unit = {
val zone = obj.Zone val zone = obj.Zone
zone.tasks ! GUIDTask.UnregisterDeployableTurret(turret)(zone.GUID) TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(zone.GUID, turret))
} }
} }

View file

@ -9,7 +9,7 @@ import net.psforever.objects.ballistics.PlayerSource
import net.psforever.objects.ce.Deployable import net.psforever.objects.ce.Deployable
import net.psforever.objects.definition.DeployAnimation import net.psforever.objects.definition.DeployAnimation
import net.psforever.objects.equipment._ import net.psforever.objects.equipment._
import net.psforever.objects.guid.GUIDTask import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.inventory.{GridInventory, InventoryItem} import net.psforever.objects.inventory.{GridInventory, InventoryItem}
import net.psforever.objects.loadouts.Loadout import net.psforever.objects.loadouts.Loadout
import net.psforever.objects.serverobject.aura.{Aura, AuraEffectBehavior} import net.psforever.objects.serverobject.aura.{Aura, AuraEffectBehavior}
@ -284,7 +284,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
val zone = player.Zone val zone = player.Zone
avatarActor ! AvatarActor.UpdateUseTime(kdef) avatarActor ! AvatarActor.UpdateUseTime(kdef)
player.Slot(slot).Equipment = None //remove from slot immediately; must exist on client for now player.Slot(slot).Equipment = None //remove from slot immediately; must exist on client for now
zone.tasks ! GUIDTask.UnregisterEquipment(kit)(zone.GUID) TaskWorkflow.execute(GUIDTask.unregisterEquipment(zone.GUID, kit))
zone.AvatarEvents ! AvatarServiceMessage( zone.AvatarEvents ! AvatarServiceMessage(
zone.id, zone.id,
AvatarAction.PlanetsideAttributeToAll(player.GUID, attribute, value) AvatarAction.PlanetsideAttributeToAll(player.GUID, attribute, value)
@ -482,17 +482,17 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
obj.Trigger = trigger obj.Trigger = trigger
//TODO sufficiently delete the tool //TODO sufficiently delete the tool
zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.ObjectDelete(player.GUID, tool.GUID)) zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.ObjectDelete(player.GUID, tool.GUID))
zone.tasks ! GUIDTask.UnregisterEquipment(tool)(zone.GUID) TaskWorkflow.execute(GUIDTask.unregisterEquipment(zone.GUID, tool))
player.Find(tool) match { player.Find(tool) match {
case Some(index) if player.VisibleSlots.contains(index) => case Some(index) if player.VisibleSlots.contains(index) =>
player.Slot(index).Equipment = None player.Slot(index).Equipment = None
zone.tasks ! HoldNewEquipmentUp(player)(trigger, index) TaskWorkflow.execute(HoldNewEquipmentUp(player)(trigger, index))
case Some(index) => case Some(index) =>
player.Slot(index).Equipment = None player.Slot(index).Equipment = None
zone.tasks ! PutNewEquipmentInInventoryOrDrop(player)(trigger) TaskWorkflow.execute(PutNewEquipmentInInventoryOrDrop(player)(trigger))
case None => case None =>
//don't know where boomer trigger "should" go //don't know where boomer trigger "should" go
zone.tasks ! PutNewEquipmentInInventoryOrDrop(player)(trigger) TaskWorkflow.execute(PutNewEquipmentInInventoryOrDrop(player)(trigger))
} }
Players.buildCooldownReset(zone, player.Name, obj) Players.buildCooldownReset(zone, player.Name, obj)
case _ => ; case _ => ;

View file

@ -2,7 +2,7 @@
package net.psforever.objects.ce package net.psforever.objects.ce
import akka.actor.{Actor, ActorRef, Cancellable} import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects.guid.GUIDTask import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.definition.DeployAnimation import net.psforever.objects.definition.DeployAnimation
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
@ -280,7 +280,7 @@ trait DeployableBehavior {
*/ */
def unregisterDeployable(obj: Deployable): Unit = { def unregisterDeployable(obj: Deployable): Unit = {
val zone = obj.Zone val zone = obj.Zone
zone.tasks ! GUIDTask.UnregisterObjectTask(obj)(zone.GUID) TaskWorkflow.execute(GUIDTask.unregisterObject(zone.GUID, obj))
} }
/** /**

View file

@ -2,17 +2,17 @@
package net.psforever.objects.definition package net.psforever.objects.definition
import net.psforever.objects.definition.converter.AmmoBoxConverter import net.psforever.objects.definition.converter.AmmoBoxConverter
import net.psforever.objects.equipment.Ammo import net.psforever.objects.equipment.{Ammo, EquipmentSize}
class AmmoBoxDefinition(objectId: Int) extends EquipmentDefinition(objectId) { class AmmoBoxDefinition(objectId: Int) extends EquipmentDefinition(objectId) {
import net.psforever.objects.equipment.EquipmentSize Name = "ammo_box"
Size = EquipmentSize.Inventory
Packet = AmmoBoxDefinition.converter
private val ammoType: Ammo.Value = Ammo(objectId) //let throw NoSuchElementException private val ammoType: Ammo.Value = Ammo(objectId) //let throw NoSuchElementException
private var capacity: Int = 1 private var capacity: Int = 1
var repairAmount: Float = 0 var repairAmount: Float = 0
registerAs = "ammo"
Name = "ammo box"
Size = EquipmentSize.Inventory
Packet = AmmoBoxDefinition.converter
def AmmoType: Ammo.Value = ammoType def AmmoType: Ammo.Value = ammoType

View file

@ -14,6 +14,7 @@ class AvatarDefinition(objectId: Int) extends ObjectDefinition(objectId) with Vi
Avatars(objectId) //let throw NoSuchElementException Avatars(objectId) //let throw NoSuchElementException
Packet = AvatarDefinition.converter Packet = AvatarDefinition.converter
Geometry = GeometryForm.representPlayerByCylinder(radius = 1.6f) Geometry = GeometryForm.representPlayerByCylinder(radius = 1.6f)
registerAs = "players"
} }
object AvatarDefinition { object AvatarDefinition {

View file

@ -12,6 +12,7 @@ class ConstructionItemDefinition(objectId: Int) extends EquipmentDefinition(obje
CItem(objectId) //let throw NoSuchElementException CItem(objectId) //let throw NoSuchElementException
private val modes: ListBuffer[ConstructionFireMode] = ListBuffer() private val modes: ListBuffer[ConstructionFireMode] = ListBuffer()
Packet = new ACEConverter Packet = new ACEConverter
registerAs = "items"
def Modes: ListBuffer[ConstructionFireMode] = modes def Modes: ListBuffer[ConstructionFireMode] = modes
} }

View file

@ -59,6 +59,7 @@ abstract class DeployableDefinition(objectId: Int)
DamageUsing = DamageCalculations.AgainstVehicle DamageUsing = DamageCalculations.AgainstVehicle
ResistUsing = NoResistanceSelection ResistUsing = NoResistanceSelection
Packet = new SmallDeployableConverter Packet = new SmallDeployableConverter
registerAs = "deployables"
def Item: DeployedItem.Value = item def Item: DeployedItem.Value = item
} }

View file

@ -16,6 +16,7 @@ class KitDefinition(objectId: Int) extends EquipmentDefinition(objectId) {
Tile = InventoryTile.Tile42 Tile = InventoryTile.Tile42
Name = "kit" Name = "kit"
Packet = KitDefinition.converter Packet = KitDefinition.converter
registerAs = "kits"
} }
object KitDefinition { object KitDefinition {

View file

@ -21,10 +21,11 @@ import net.psforever.types.OxygenState
* @param objectId the object's identifier number * @param objectId the object's identifier number
*/ */
abstract class ObjectDefinition(private val objectId: Int) extends BasicDefinition { abstract class ObjectDefinition(private val objectId: Int) extends BasicDefinition {
var registerAs: String = "generic"
/** a data converter for this type of object */ /** a data converter for this type of object */
protected var packet: PacketConverter = new ObjectCreateConverter[PlanetSideGameObject]() {} protected var packet: PacketConverter = new ObjectCreateConverter[PlanetSideGameObject]() {}
Name = "object definition" Name = "object_definition"
/** /**
* Get the conversion object. * Get the conversion object.

View file

@ -60,6 +60,7 @@ class ProjectileDefinition(objectId: Int)
private var finalVelocity: Float = 0f private var finalVelocity: Float = 0f
Name = "projectile" Name = "projectile"
Modifiers = DistanceDegrade Modifiers = DistanceDegrade
registerAs = "projectiles"
def ProjectileType: Projectiles.Value = projectileType def ProjectileType: Projectiles.Value = projectileType

View file

@ -6,8 +6,9 @@ import net.psforever.objects.equipment.SItem
class SimpleItemDefinition(objectId: Int) extends EquipmentDefinition(objectId) { class SimpleItemDefinition(objectId: Int) extends EquipmentDefinition(objectId) {
import net.psforever.objects.equipment.EquipmentSize import net.psforever.objects.equipment.EquipmentSize
SItem(objectId) //let throw NoSuchElementException SItem(objectId) //let throw NoSuchElementException
Name = "tool" Name = "simple_item"
Size = EquipmentSize.Pistol //all items Size = EquipmentSize.Pistol //all items
registerAs = "items"
} }
object SimpleItemDefinition { object SimpleItemDefinition {

View file

@ -18,6 +18,7 @@ class ToolDefinition(objectId: Int) extends EquipmentDefinition(objectId) {
private var defaultFireModeIndex: Option[Int] = None private var defaultFireModeIndex: Option[Int] = None
Name = "tool" Name = "tool"
Packet = ToolDefinition.converter Packet = ToolDefinition.converter
registerAs = "tools"
def AmmoTypes: mutable.ListBuffer[AmmoBoxDefinition] = ammoTypes def AmmoTypes: mutable.ListBuffer[AmmoBoxDefinition] = ammoTypes

View file

@ -61,6 +61,7 @@ class VehicleDefinition(objectId: Int)
Model = VehicleResolutions.calculate Model = VehicleResolutions.calculate
RepairDistance = 10 RepairDistance = 10
RepairRestoresAt = 1 RepairRestoresAt = 1
registerAs = "vehicles"
def MaxShields: Int = maxShields def MaxShields: Int = maxShields

View file

@ -1,16 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.guid
/**
* The availability of individual GUIDs is maintained by the given policy.
*/
object AvailabilityPolicy extends Enumeration {
type Type = Value
/**
* An `AVAILABLE` GUID is ready and waiting to be `LEASED` for use.
* A `LEASED` GUID has been issued and is currently being used.
* A `RESTRICTED` GUID can never be freed. It is allowed, however, to be assigned once as if it were `LEASED`.
*/
val Available, Leased, Restricted = Value
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.guid package net.psforever.objects.guid
import akka.actor.ActorRef import akka.util.Timeout
import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.equipment.{Equipment, EquipmentSlot} import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.objects._ import net.psforever.objects._
@ -10,6 +10,8 @@ import net.psforever.objects.locker.{LockerContainer, LockerEquipment}
import net.psforever.objects.serverobject.turret.WeaponTurret import net.psforever.objects.serverobject.turret.WeaponTurret
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.duration._
import scala.concurrent.Future
/** /**
* The basic compiled tasks for assigning (registering) and revoking (unregistering) globally unique identifiers.<br> * The basic compiled tasks for assigning (registering) and revoking (unregistering) globally unique identifiers.<br>
@ -22,43 +24,63 @@ import scala.annotation.tailrec
* It will get passed from the more complicated functions down into the less complicated functions, * It will get passed from the more complicated functions down into the less complicated functions,
* until it has found the basic number assignment functionality.<br> * until it has found the basic number assignment functionality.<br>
* <br> * <br>
* All functions produce a `TaskResolver.GiveTask` container object * All functions produce a `TaskBundle` container object
* or a list of `TaskResolver.GiveTask` container objects that is expected to be used by a `TaskResolver` `Actor`. * or a list of `TaskBundle` container objects that is expected to be used by a `TaskBundle` container.
* These "task containers" can also be unpackaged into their component tasks, sorted into other containers, * These "task containers" can also be unpackaged into their component tasks, sorted into other containers,
* and combined with other tasks to enact more complicated sequences of operations. * and combined with other tasks to enact more complicated sequences of operations.
* Almost all tasks have an explicit registering and an unregistering activity defined for it. * Almost all tasks have an explicit registering and an unregistering activity defined for it.
*/ */
object GUIDTask { object GUIDTask {
private implicit val timeout = Timeout(2.seconds)
//registration tasking
protected case class RegisterObjectTask(
guid: UniqueNumberOps,
obj: IdentifiableEntity,
pool: String
) extends Task {
def action(): Future[Any] = {
guid.Register(obj, pool)
}
def undo(): Unit = {
guid.Unregister(obj)
}
def isSuccessful() : Boolean = obj.HasGUID
override def description(): String = s"register $obj to $pool"
}
def RegisterObjectTask(guid: UniqueNumberOps, obj: IdentifiableEntity): RegisterObjectTask = obj match {
case o: PlanetSideGameObject => RegisterObjectTask(guid, o)
case _ => RegisterObjectTask(guid, obj, "generic")
}
def RegisterObjectTask(guid: UniqueNumberOps, obj: PlanetSideGameObject): RegisterObjectTask =
RegisterObjectTask(guid, obj, obj.Definition.registerAs)
/** /**
* Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers.<br> * Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers.
* <br>
* Regardless of the complexity of the object provided to this function, only the current depth will be assigned a GUID. * Regardless of the complexity of the object provided to this function, only the current depth will be assigned a GUID.
* This is the most basic operation that all objects that can be assigned a GUID must perform. * This is the most basic operation that all objects that can be assigned a GUID must perform.
* @param obj the object being registered * @param obj the object being registered
* @param guid implicit reference to a unique number system * @param guid implicit reference to a unique number system
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
def RegisterObjectTask(obj: IdentifiableEntity)(implicit guid: ActorRef): TaskResolver.GiveTask = { def registerObject(guid: UniqueNumberOps, obj: IdentifiableEntity): TaskBundle =
TaskResolver.GiveTask(new Task() { TaskBundle(RegisterObjectTask(guid, obj, "generic"))
private val localObject = obj
private val localAccessor = guid
override def Description: String = s"register $localObject" /**
* Construct tasking that registers an object with a globally unique identifier selected from a specific pool of numbers.
override def isComplete: Task.Resolution.Value = * Regardless of the complexity of the object provided to this function, only the current depth will be assigned a GUID.
if (localObject.HasGUID) { * @param obj the object being registered
Task.Resolution.Success * @param guid implicit reference to a unique number system
} else { * @return a `TaskBundle` message
Task.Resolution.Incomplete */
} def registerObject(guid: UniqueNumberOps, obj: PlanetSideGameObject): TaskBundle =
TaskBundle(RegisterObjectTask(guid, obj, obj.Definition.registerAs))
def Execute(resolver: ActorRef): Unit = {
import net.psforever.objects.guid.actor.Register
localAccessor ! Register(localObject, "dynamic", resolver) //TODO pool should not be hardcoded
}
})
}
/** /**
* Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, as a `Tool`.<br> * Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, as a `Tool`.<br>
@ -74,41 +96,14 @@ object GUIDTask {
* else use a more general function to differentiate between simple and complex objects. * else use a more general function to differentiate between simple and complex objects.
* @param obj the `Tool` object being registered * @param obj the `Tool` object being registered
* @param guid implicit reference to a unique number system * @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterEquipment` * @see `GUIDTask.registerEquipment`
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
def RegisterTool(obj: Tool)(implicit guid: ActorRef): TaskResolver.GiveTask = { def registerTool(guid: UniqueNumberOps, obj: Tool): TaskBundle = {
val ammoTasks: List[TaskResolver.GiveTask] = TaskBundle(
(0 until obj.MaxAmmoSlot).map(ammoIndex => RegisterObjectTask(obj.AmmoSlots(ammoIndex).Box)).toList RegisterObjectTask(guid, obj),
TaskResolver.GiveTask(RegisterObjectTask(obj).task, ammoTasks) (0 until obj.MaxAmmoSlot).map(ammoIndex => registerObject(guid, obj.AmmoSlots(ammoIndex).Box))
} )
/**
* Construct tasking that registers a `LockerContainer` object
* with a globally unique identifier selected from a pool of numbers.
* @param obj the object being registered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.UnregisterLocker`
* @return a `TaskResolver.GiveTask` message
*/
def RegisterLocker(obj: LockerContainer)(implicit guid: ActorRef): TaskResolver.GiveTask = {
TaskResolver.GiveTask(RegisterObjectTask(obj).task, RegisterInventory(obj))
}
def RegisterLocker(obj: LockerEquipment)(implicit guid: ActorRef): TaskResolver.GiveTask = {
TaskResolver.GiveTask(RegisterObjectTask(obj).task, RegisterInventory(obj))
}
/**
* Construct tasking that registers the objects that are within the given container's inventory
* with a globally unique identifier selected from a pool of numbers for each object.
* @param container the storage unit in which objects can be found
* @param guid implicit reference to a unique number system
* @see `GUID.UnregisterInventory`<br>
* `Container`
* @return a list of `TaskResolver.GiveTask` messages
*/
def RegisterInventory(container: Container)(implicit guid: ActorRef): List[TaskResolver.GiveTask] = {
container.Inventory.Items.map(entry => { RegisterEquipment(entry.obj) })
} }
/** /**
@ -125,17 +120,52 @@ object GUIDTask {
* The type will be sorted and the object will be handled according to its complexity level. * The type will be sorted and the object will be handled according to its complexity level.
* @param obj the `Equipment` object being registered * @param obj the `Equipment` object being registered
* @param guid implicit reference to a unique number system * @param guid implicit reference to a unique number system
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
def RegisterEquipment(obj: Equipment)(implicit guid: ActorRef): TaskResolver.GiveTask = { def registerEquipment(guid: UniqueNumberOps, obj: Equipment): TaskBundle = {
obj match { obj match {
case tool: Tool => case tool: Tool => registerTool(guid, tool)
RegisterTool(tool) case _ => registerObject(guid, obj)
case _ =>
RegisterObjectTask(obj)
} }
} }
/**
* Construct tasking that registers the objects that are within the given container's inventory
* with a globally unique identifier selected from a pool of numbers for each object.
* @param container the storage unit in which objects can be found
* @param guid implicit reference to a unique number system
* @see `GUIDTask.unregisterInventory`<br>
* `Container`
* @return a list of `TaskBundle` messages
*/
def registerInventory(guid: UniqueNumberOps, container: Container): List[TaskBundle] = {
container.Inventory.Items.map{ entry => registerEquipment(guid, entry.obj) }
}
/**
* Construct tasking that registers a `LockerContainer` object
* with a globally unique identifier selected from a pool of numbers.
* @param obj the object being registered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.unregisterLocker`
* @return a `TaskBundle` message
*/
def registerLocker(guid: UniqueNumberOps, obj: LockerContainer): TaskBundle = {
TaskBundle(RegisterObjectTask(guid, obj), registerInventory(guid, obj))
}
/**
* Construct tasking that registers a `LockerContainer` object
* with a globally unique identifier selected from a pool of numbers.
* @param obj the object being registered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.unregisterLocker`
* @return a `TaskBundle` message
*/
def registerLocker(guid: UniqueNumberOps, obj: LockerEquipment): TaskBundle = {
TaskBundle(RegisterObjectTask(guid, obj), registerInventory(guid, obj))
}
/** /**
* Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, as a `Player`.<br> * Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, as a `Player`.<br>
* <br> * <br>
@ -150,13 +180,13 @@ object GUIDTask {
* a task built of lesser registration tasks and supporting tasks should be written instead. * a task built of lesser registration tasks and supporting tasks should be written instead.
* @param tplayer the `Player` object being registered * @param tplayer the `Player` object being registered
* @param guid implicit reference to a unique number system * @param guid implicit reference to a unique number system
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
def RegisterAvatar(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = { def registerAvatar(guid: UniqueNumberOps, tplayer: Player): TaskBundle = {
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), RegisterEquipment) val holsterTasks = visibleSlotTaskBuilding(guid, tplayer.Holsters(), registerEquipment)
val lockerTask = List(RegisterObjectTask(tplayer.avatar.locker)) val lockerTask = List(registerObject(guid, tplayer.avatar.locker))
val inventoryTasks = RegisterInventory(tplayer) val inventoryTasks = registerInventory(guid, tplayer)
TaskResolver.GiveTask(RegisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks) TaskBundle(RegisterObjectTask(guid, tplayer), holsterTasks ++ lockerTask ++ inventoryTasks)
} }
/** /**
@ -165,12 +195,12 @@ object GUIDTask {
* Similar to `RegisterAvatar` but the locker components are skipped. * Similar to `RegisterAvatar` but the locker components are skipped.
* @param tplayer the `Player` object being registered * @param tplayer the `Player` object being registered
* @param guid implicit reference to a unique number system * @param guid implicit reference to a unique number system
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
def RegisterPlayer(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = { def registerPlayer(guid: UniqueNumberOps, tplayer: Player): TaskBundle = {
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), RegisterEquipment) val holsterTasks = visibleSlotTaskBuilding(guid, tplayer.Holsters(), registerEquipment)
val inventoryTasks = RegisterInventory(tplayer) val inventoryTasks = registerInventory(guid, tplayer)
TaskResolver.GiveTask(GUIDTask.RegisterObjectTask(tplayer)(guid).task, holsterTasks ++ inventoryTasks) TaskBundle(RegisterObjectTask(guid, tplayer), holsterTasks ++ inventoryTasks)
} }
/** /**
@ -188,25 +218,39 @@ object GUIDTask {
* a task built of lesser registration tasks and supporting tasks should be written instead. * a task built of lesser registration tasks and supporting tasks should be written instead.
* @param vehicle the `Vehicle` object being registered * @param vehicle the `Vehicle` object being registered
* @param guid implicit reference to a unique number system * @param guid implicit reference to a unique number system
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
def RegisterVehicle(vehicle: Vehicle)(implicit guid: ActorRef): TaskResolver.GiveTask = { def registerVehicle(guid: UniqueNumberOps, vehicle: Vehicle): TaskBundle = {
val weaponTasks = VisibleSlotTaskBuilding(vehicle.Weapons.values, RegisterEquipment) val weaponTasks = visibleSlotTaskBuilding(guid, vehicle.Weapons.values, registerEquipment)
val utilTasks = val utilTasks =
Vehicle.EquipmentUtilities(vehicle.Utilities).values.map(util => { RegisterObjectTask(util()) }).toList Vehicle.EquipmentUtilities(vehicle.Utilities).values.map(util => { registerObject(guid, util()) }).toList
val inventoryTasks = RegisterInventory(vehicle) val inventoryTasks = registerInventory(guid, vehicle)
TaskResolver.GiveTask(RegisterObjectTask(vehicle).task, weaponTasks ++ utilTasks ++ inventoryTasks) TaskBundle(RegisterObjectTask(guid, vehicle), weaponTasks ++ utilTasks ++ inventoryTasks)
} }
def RegisterDeployableTurret( def registerDeployableTurret(guid: UniqueNumberOps, obj: PlanetSideGameObject with WeaponTurret): TaskBundle = {
obj: PlanetSideGameObject with WeaponTurret TaskBundle(
)(implicit guid: ActorRef): TaskResolver.GiveTask = { RegisterObjectTask(guid, obj),
TaskResolver.GiveTask( visibleSlotTaskBuilding(guid, obj.Weapons.values, registerEquipment) ++ registerInventory(guid, obj)
RegisterObjectTask(obj).task,
VisibleSlotTaskBuilding(obj.Weapons.values, GUIDTask.RegisterEquipment) ++ RegisterInventory(obj)
) )
} }
//unregistration tasking
protected case class UnregisterObjectTask(
guid: UniqueNumberOps,
obj: IdentifiableEntity
) extends Task {
def action(): Future[Any] = {
guid.Unregister(obj)
}
def undo(): Unit = RegisterObjectTask(guid, obj)
def isSuccessful() : Boolean = !obj.HasGUID
override def description(): String = s"unregister $obj"
}
/** /**
* Construct tasking that unregisters an object from a globally unique identifier system.<br> * Construct tasking that unregisters an object from a globally unique identifier system.<br>
* <br> * <br>
@ -214,73 +258,10 @@ object GUIDTask {
* It is the most basic operation that all objects that can have their GUIDs revoked must perform. * It is the most basic operation that all objects that can have their GUIDs revoked must perform.
* @param obj the object being unregistered * @param obj the object being unregistered
* @param guid implicit reference to a unique number system * @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterObjectTask` * @see `GUIDTask.registerObjectTask`
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
def UnregisterObjectTask(obj: IdentifiableEntity)(implicit guid: ActorRef): TaskResolver.GiveTask = { def unregisterObject(guid: UniqueNumberOps, obj: IdentifiableEntity): TaskBundle = TaskBundle(UnregisterObjectTask(guid, obj))
TaskResolver.GiveTask(
new Task() {
private val localObject = obj
private val localAccessor = guid
override def Description: String = s"unregister $localObject"
override def isComplete: Task.Resolution.Value =
if (!localObject.HasGUID) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
def Execute(resolver: ActorRef): Unit = {
import net.psforever.objects.guid.actor.Unregister
localAccessor ! Unregister(localObject, resolver)
}
}
)
}
/**
* Construct tasking that unregisters a `Tool` object from a globally unique identifier system.<br>
* <br>
* This task performs an operation that reverses the effect of `RegisterTool`.
* @param obj the `Tool` object being unregistered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterTool`
* @return a `TaskResolver.GiveTask` message
*/
def UnregisterTool(obj: Tool)(implicit guid: ActorRef): TaskResolver.GiveTask = {
val ammoTasks: List[TaskResolver.GiveTask] =
(0 until obj.MaxAmmoSlot).map(ammoIndex => UnregisterObjectTask(obj.AmmoSlots(ammoIndex).Box)).toList
TaskResolver.GiveTask(UnregisterObjectTask(obj).task, ammoTasks)
}
/**
* Construct tasking that unregisters a `LockerContainer` object from a globally unique identifier system.
* @param obj the object being unregistered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterLocker`
* @return a `TaskResolver.GiveTask` message
*/
def UnregisterLocker(obj: LockerContainer)(implicit guid: ActorRef): TaskResolver.GiveTask = {
TaskResolver.GiveTask(UnregisterObjectTask(obj).task, UnregisterInventory(obj))
}
def UnregisterLocker(obj: LockerEquipment)(implicit guid: ActorRef): TaskResolver.GiveTask = {
TaskResolver.GiveTask(RegisterObjectTask(obj).task, RegisterInventory(obj))
}
/**
* Construct tasking that unregisters the objects that are within the given container's inventory
* from a globally unique identifier system.
* @param container the storage unit in which objects can be found
* @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterInventory`<br>
* `Container`
* @return a list of `TaskResolver.GiveTask` messages
*/
def UnregisterInventory(container: Container)(implicit guid: ActorRef): List[TaskResolver.GiveTask] = {
container.Inventory.Items.map(entry => { UnregisterEquipment(entry.obj) })
}
/** /**
* Construct tasking that unregisters an object from a globally unique identifier system * Construct tasking that unregisters an object from a globally unique identifier system
@ -289,32 +270,88 @@ object GUIDTask {
* This task performs an operation that reverses the effect of `RegisterEquipment`. * This task performs an operation that reverses the effect of `RegisterEquipment`.
* @param obj the `Equipment` object being unregistered * @param obj the `Equipment` object being unregistered
* @param guid implicit reference to a unique number system * @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterEquipment` * @see `GUIDTask.registerEquipment`
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
def UnregisterEquipment(obj: Equipment)(implicit guid: ActorRef): TaskResolver.GiveTask = { def unregisterTool(guid: UniqueNumberOps, obj: Tool): TaskBundle = {
TaskBundle(
UnregisterObjectTask(guid, obj),
(0 until obj.MaxAmmoSlot).map(ammoIndex => unregisterObject(guid, obj.AmmoSlots(ammoIndex).Box))
)
}
/**
* Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers,
* after determining whether the object is complex (`Tool` or `Locker`) or is simple.<br>
* <br>
* The objects in this case are specifically `Equipment`, a subclass of the basic register-able `IdentifiableEntity`.
* About five subclasses of `Equipment` exist, but they decompose into two groups - "complex objects" and "simple objects."
* "Simple objects" are most groups of `Equipment` and just their own GUID to be registered.
* "Complex objects" are just the `Tool` category of `Equipment`.
* They have internal objects that must also have their GUID's registered to function.<br>
* <br>
* Using this function when passing unknown `Equipment` is recommended.
* The type will be sorted and the object will be handled according to its complexity level.
* @param obj the `Equipment` object being registered
* @param guid implicit reference to a unique number system
* @return a `TaskBundle` message
*/
def unregisterEquipment(guid: UniqueNumberOps, obj: Equipment): TaskBundle = {
obj match { obj match {
case tool: Tool => case tool: Tool => unregisterTool(guid, tool)
UnregisterTool(tool) case _ => unregisterObject(guid, obj)
case _ =>
UnregisterObjectTask(obj)
} }
} }
/**
* Construct tasking that unregisters the objects that are within the given container's inventory
* from a globally unique identifier system.
* @param container the storage unit in which objects can be found
* @param guid implicit reference to a unique number system
* @see `GUIDTask.registerInventory`<br>
* `Container`
* @return a list of `TaskBundle` messages
*/
def unregisterInventory(guid: UniqueNumberOps, container: Container): List[TaskBundle] = {
container.Inventory.Items.map{ entry => unregisterEquipment(guid, entry.obj) }
}
/**
* Construct tasking that unregisters a `LockerContainer` object from a globally unique identifier system.
* @param obj the object being unregistered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.registerLocker`
* @return a `TaskBundle` message
*/
def unregisterLocker(guid: UniqueNumberOps, obj: LockerContainer): TaskBundle = {
TaskBundle(UnregisterObjectTask(guid, obj), unregisterInventory(guid, obj))
}
/**
* Construct tasking that unregisters a `LockerContainer` object from a globally unique identifier system.
* @param obj the object being unregistered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.registerLocker`
* @return a `TaskBundle` message
*/
def unregisterLocker(guid: UniqueNumberOps, obj: LockerEquipment): TaskBundle = {
TaskBundle(UnregisterObjectTask(guid, obj), unregisterInventory(guid, obj))
}
/** /**
* Construct tasking that unregisters a `Player` object from a globally unique identifier system.<br> * Construct tasking that unregisters a `Player` object from a globally unique identifier system.<br>
* <br> * <br>
* This task performs an operation that reverses the effect of `RegisterAvatar`. * This task performs an operation that reverses the effect of `RegisterAvatar`.
* @param tplayer the `Player` object being unregistered * @param tplayer the `Player` object being unregistered
* @param guid implicit reference to a unique number system * @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterAvatar` * @see `GUIDTask.registerAvatar`
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
def UnregisterAvatar(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = { def unregisterAvatar(guid: UniqueNumberOps, tplayer: Player): TaskBundle = {
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), UnregisterEquipment) val holsterTasks = visibleSlotTaskBuilding(guid, tplayer.Holsters(), unregisterEquipment)
val lockerTask = List(UnregisterObjectTask(tplayer.avatar.locker)) val lockerTask = List(unregisterObject(guid, tplayer.avatar.locker))
val inventoryTasks = UnregisterInventory(tplayer) val inventoryTasks = unregisterInventory(guid, tplayer)
TaskResolver.GiveTask(UnregisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks) TaskBundle(UnregisterObjectTask(guid, tplayer), holsterTasks ++ lockerTask ++ inventoryTasks)
} }
/** /**
@ -324,13 +361,13 @@ object GUIDTask {
* This task performs an operation that reverses the effect of `RegisterPlayer`. * This task performs an operation that reverses the effect of `RegisterPlayer`.
* @param tplayer the `Player` object being unregistered * @param tplayer the `Player` object being unregistered
* @param guid implicit reference to a unique number system * @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterAvatar` * @see `GUIDTask.registerAvatar`
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
def UnregisterPlayer(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = { def unregisterPlayer(guid: UniqueNumberOps, tplayer: Player): TaskBundle = {
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), UnregisterEquipment) val holsterTasks = visibleSlotTaskBuilding(guid, tplayer.Holsters(), unregisterEquipment)
val inventoryTasks = UnregisterInventory(tplayer) val inventoryTasks = unregisterInventory(guid, tplayer)
TaskResolver.GiveTask(GUIDTask.UnregisterObjectTask(tplayer).task, holsterTasks ++ inventoryTasks) TaskBundle(UnregisterObjectTask(guid, tplayer), holsterTasks ++ inventoryTasks)
} }
/** /**
@ -339,26 +376,25 @@ object GUIDTask {
* This task performs an operation that reverses the effect of `RegisterVehicle`. * This task performs an operation that reverses the effect of `RegisterVehicle`.
* @param vehicle the `Vehicle` object being unregistered * @param vehicle the `Vehicle` object being unregistered
* @param guid implicit reference to a unique number system * @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterVehicle` * @see `GUIDTask.registerVehicle`
* @return a `TaskResolver.GiveTask` message * @return a `TaskBundle` message
*/ */
def UnregisterVehicle(vehicle: Vehicle)(implicit guid: ActorRef): TaskResolver.GiveTask = { def unregisterVehicle(guid: UniqueNumberOps, vehicle: Vehicle): TaskBundle = {
val weaponTasks = VisibleSlotTaskBuilding(vehicle.Weapons.values, UnregisterEquipment) val weaponTasks = visibleSlotTaskBuilding(guid, vehicle.Weapons.values, unregisterEquipment)
val utilTasks = val utilTasks =
Vehicle.EquipmentUtilities(vehicle.Utilities).values.map(util => { UnregisterObjectTask(util()) }).toList Vehicle.EquipmentUtilities(vehicle.Utilities).values.map(util => { unregisterObject(guid, util()) }).toList
val inventoryTasks = UnregisterInventory(vehicle) val inventoryTasks = unregisterInventory(guid, vehicle)
TaskResolver.GiveTask(UnregisterObjectTask(vehicle).task, weaponTasks ++ utilTasks ++ inventoryTasks) TaskBundle(UnregisterObjectTask(guid, vehicle), weaponTasks ++ utilTasks ++ inventoryTasks)
} }
def UnregisterDeployableTurret( def unregisterDeployableTurret(guid: UniqueNumberOps, obj: PlanetSideGameObject with WeaponTurret): TaskBundle = {
obj: PlanetSideGameObject with WeaponTurret TaskBundle(
)(implicit guid: ActorRef): TaskResolver.GiveTask = { UnregisterObjectTask(guid, obj),
TaskResolver.GiveTask( visibleSlotTaskBuilding(guid, obj.Weapons.values, unregisterEquipment) ++ unregisterInventory(guid, obj)
UnregisterObjectTask(obj).task,
VisibleSlotTaskBuilding(obj.Weapons.values, GUIDTask.UnregisterEquipment) ++ UnregisterInventory(obj)
) )
} }
//support
/** /**
* Construct tasking that allocates work upon encountered `Equipment` objects * Construct tasking that allocates work upon encountered `Equipment` objects
* in reference to a globally unique identifier system of a pool of numbers. * in reference to a globally unique identifier system of a pool of numbers.
@ -367,12 +403,14 @@ object GUIDTask {
* @param func the function used to build tasking from any discovered `Equipment`; * @param func the function used to build tasking from any discovered `Equipment`;
* strictly either `RegisterEquipment` or `UnregisterEquipment` * strictly either `RegisterEquipment` or `UnregisterEquipment`
* @param guid implicit reference to a unique number system * @param guid implicit reference to a unique number system
* @return a list of `TaskResolver.GiveTask` messages * @return a list of `TaskBundle` messages
*/ */
def VisibleSlotTaskBuilding(list: Iterable[EquipmentSlot], func: Equipment => TaskResolver.GiveTask)(implicit private def visibleSlotTaskBuilding(
guid: ActorRef guid: UniqueNumberOps,
): List[TaskResolver.GiveTask] = { list: Iterable[EquipmentSlot],
recursiveVisibleSlotTaskBuilding(list.iterator, func) func: (UniqueNumberOps, Equipment) => TaskBundle
): List[TaskBundle] = {
recursiveVisibleSlotTaskBuilding(guid, list.iterator, func)
} }
/** /**
@ -386,18 +424,17 @@ object GUIDTask {
* @return a `List` of `Equipment` tasking * @return a `List` of `Equipment` tasking
*/ */
@tailrec private def recursiveVisibleSlotTaskBuilding( @tailrec private def recursiveVisibleSlotTaskBuilding(
iter: Iterator[EquipmentSlot], guid: UniqueNumberOps,
func: Equipment => TaskResolver.GiveTask, iter: Iterator[EquipmentSlot],
list: List[TaskResolver.GiveTask] = Nil func: (UniqueNumberOps, Equipment) => TaskBundle,
)(implicit guid: ActorRef): List[TaskResolver.GiveTask] = { list: List[TaskBundle] = Nil
): List[TaskBundle] = {
if (!iter.hasNext) { if (!iter.hasNext) {
list list
} else { } else {
iter.next().Equipment match { iter.next().Equipment match {
case Some(item) => case Some(item) => recursiveVisibleSlotTaskBuilding(guid, iter, func, list :+ func(guid, item))
recursiveVisibleSlotTaskBuilding(iter, func, list :+ func(item)) case None => recursiveVisibleSlotTaskBuilding(guid, iter, func, list)
case None =>
recursiveVisibleSlotTaskBuilding(iter, func, list)
} }
} }
} }

View file

@ -2,7 +2,7 @@
package net.psforever.objects.guid package net.psforever.objects.guid
import net.psforever.objects.entity.{IdentifiableEntity, NoGUIDException} import net.psforever.objects.entity.{IdentifiableEntity, NoGUIDException}
import net.psforever.objects.guid.key.LoanedKey import net.psforever.objects.guid.key.{AvailabilityPolicy, LoanedKey}
import net.psforever.objects.guid.pool.{ExclusivePool, GenericPool, NumberPool} import net.psforever.objects.guid.pool.{ExclusivePool, GenericPool, NumberPool}
import net.psforever.objects.guid.source.NumberSource import net.psforever.objects.guid.source.NumberSource
import net.psforever.types.PlanetSideGUID import net.psforever.types.PlanetSideGUID
@ -24,10 +24,7 @@ class NumberPoolHub(private val source: NumberSource) {
import scala.collection.mutable import scala.collection.mutable
private val hash: mutable.HashMap[String, NumberPool] = mutable.HashMap[String, NumberPool]() private val hash: mutable.HashMap[String, NumberPool] = mutable.HashMap[String, NumberPool]()
private val bigpool: mutable.LongMap[String] = mutable.LongMap[String]() private val bigpool: mutable.LongMap[String] = mutable.LongMap[String]()
hash += "generic" -> new GenericPool(bigpool, source.size) hash += "generic" -> GenericPool(bigpool, source.size, poolName = "generic")
source.finalizeRestrictions.foreach(i =>
bigpool += i.toLong -> ""
) //these numbers can never be pooled; the source can no longer restrict numbers
/** /**
* Given a globally unique identifier, return any object registered to it.<br> * Given a globally unique identifier, return any object registered to it.<br>
@ -327,7 +324,7 @@ class NumberPoolHub(private val source: NumberSource) {
* @param number the number whose assignment is requested * @param number the number whose assignment is requested
* @return an object that has been registered * @return an object that has been registered
*/ */
def latterPartRegister(obj: IdentifiableEntity, number: Int): Try[IdentifiableEntity] = { private[guid] def latterPartRegister(obj: IdentifiableEntity, number: Int): Try[IdentifiableEntity] = {
register_GetMonitorFromSource(number) match { register_GetMonitorFromSource(number) match {
case Success(monitor) => case Success(monitor) =>
monitor.Object = obj monitor.Object = obj
@ -459,7 +456,7 @@ class NumberPoolHub(private val source: NumberSource) {
* @param number the number to return. * @param number the number to return.
* @return any object previously using this number * @return any object previously using this number
*/ */
def latterPartUnregister(number: Int): Option[IdentifiableEntity] = source.returnNumber(number) private[guid] def latterPartUnregister(number: Int): Option[IdentifiableEntity] = source.returnNumber(number)
/** /**
* Determines if the object is registered.<br> * Determines if the object is registered.<br>

View file

@ -1,27 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.guid
import akka.actor.ActorRef
trait Task {
def Description: String = "write_descriptive_task_message"
def Execute(resolver: ActorRef): Unit
def isComplete: Task.Resolution.Value = Task.Resolution.Incomplete
def Timeout: Long = 200L //milliseconds
def onSuccess(): Unit = {}
def onFailure(ex: Throwable): Unit = {}
def onTimeout(ex: Throwable): Unit = onFailure(ex)
def onAbort(ex: Throwable): Unit = {}
def Cleanup(): Unit = {}
}
object Task {
def TimeNow: Long = {
System.nanoTime()
//java.time.Instant.now().getEpochSecond
}
object Resolution extends Enumeration {
val Success, Incomplete, Failure = Value
}
}

View file

@ -1,507 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.guid
import java.util.concurrent.TimeoutException
import akka.actor.{Actor, ActorRef, Cancellable}
import akka.routing.Broadcast
import net.psforever.objects.Default
import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
class TaskResolver() extends Actor {
/** list of all work currently managed by this resolver */
private val tasks: ListBuffer[TaskResolver.TaskEntry] = new ListBuffer[TaskResolver.TaskEntry]
/** scheduled termination of tardy managed work */
private var timeoutCleanup: Cancellable = Default.Cancellable
/** logging utilities; default to tracing */
private[this] val log = org.log4s.getLogger
private def trace(msg: String) = log.trace(msg)
/**
* Deal with any tasks that are still enqueued with this expiring `TaskResolver`.
*/
override def aroundPostStop() = {
/*
First, eliminate all timed-out tasks.
Secondly, deal with all tasks that have reported "success" but have not yet been handled.
Finally, all other remaining tasks should be treated as if they had failed.
*/
timeoutCleanup.cancel()
TimeoutCleanup()
tasks.filter(entry => entry.task.isComplete == Task.Resolution.Success).foreach(entry => OnSuccess(entry.task))
val ex: Throwable = new Exception(s"a task is being stopped")
tasks.foreach(entry => {
OnFailure(entry.task, ex)
})
super.aroundPostStop()
}
def receive: Receive = {
case TaskResolver.GiveTask(aTask, Nil) =>
GiveTask(aTask)
case TaskResolver.GiveTask(aTask, subtasks) =>
QueueSubtasks(aTask, subtasks)
case TaskResolver.GiveSubtask(aTask, subtasks, resolver) =>
QueueSubtasks(aTask, subtasks, resolver)
case TaskResolver.CompletedSubtask(obj) => //inter-resolver calls
ExecuteNewTasks(obj)
case Success(obj: Task) => //inter-resolver calls
OnSuccess(obj)
case Success | Success(_) => //success redirected from called event
OnSuccess()
case TaskResolver.Failure(obj, ex) => //inter-resolver calls
OnFailure(obj, ex)
case Failure(ex) => //failure redirected from called event
OnFailure(ex)
case TaskResolver.AbortTask(task, ex) =>
OnAbort(task, ex)
case TaskResolver.TimeoutCleanup() =>
TimeoutCleanup()
case msg =>
log.warn(s"$self received an unexpected message $msg from ${sender()}")
}
/**
* Accept simple work and perform it.
* @param aTask the work to be completed
*/
private def GiveTask(aTask: Task): Unit = {
val entry: TaskResolver.TaskEntry = TaskResolver.TaskEntry(aTask)
tasks += entry
trace(s"enqueue and start task ${aTask.Description}")
entry.Execute(self)
StartTimeoutCheck()
}
/**
* Start the periodic checks for a task that has run for too long (timed-out), unless those checks are already running.
*/
private def StartTimeoutCheck(): Unit = {
if (timeoutCleanup.isCancelled) {
timeoutCleanup = context.system.scheduler.scheduleWithFixedDelay(
500 milliseconds,
500 milliseconds,
self,
TaskResolver.TimeoutCleanup()
)
}
}
/**
* Accept complicated work and divide it into a main task and tasks that must be handled before the main task.
* Do not start the main task until all of the aforementioned "sub-tasks" are completed.<br>
* <br>
* Sub-tasks can be nested many times.
* All immediate sub-tasks count as the primary sub-tasks for the current main task.
* Each pair of main task and sub-tasks, for every sub-task discovered, is passed on to another `TaskResolver` for completion.
* The parent of this `TaskResolver` is the router logic for all brethren `TaskResolver` `Actors`.
* @param task the work to be completed
* @param subtasks other work that needs to be completed first
* @param resolver the `TaskResolver` that distributed this work, thus determining that this work is a sub-task;
* by default, no one, as the work is identified as a main task
*/
private def QueueSubtasks(
task: Task,
subtasks: List[TaskResolver.GiveTask],
resolver: ActorRef = ActorRef.noSender
): Unit = {
val entry: TaskResolver.TaskEntry = TaskResolver.TaskEntry(task, subtasks.map(task => task.task), resolver)
tasks += entry
trace(s"enqueue task ${task.Description}")
if (subtasks.isEmpty) { //a leaf in terms of task dependency; so, not dependent on any other work
trace(s"start task ${task.Description}")
entry.Execute(self)
} else {
trace(s"enqueuing ${subtasks.length} substask(s) belonging to ${task.Description}")
subtasks.foreach({ subtask =>
context.parent ! TaskResolver.GiveSubtask(
subtask.task,
subtask.subs,
self
) //route back to submit subtask to pool
})
}
StartTimeoutCheck()
}
/**
* Perform these checks when a task has reported successful completion to this TaskResolver.
* Every task and subtask will be checked, starting from the end of the list of queued entries
* and only the first discovered one will be used.
*/
private def OnSuccess(): Unit = {
//by reversing the List, we find the most outstanding Task with the completion state
TaskResolver.filterCompletion(tasks.indices.reverseIterator, tasks.toList, Task.Resolution.Success) match {
case Some(index) =>
GeneralOnSuccess(index)
case None => ;
}
}
/**
* Perform these checks when a task has reported successful completion to this TaskResolver.
* @param task a `Task` object
*/
private def OnSuccess(task: Task): Unit = {
//find specific task and dequeue
TaskResolver.findTask(tasks.iterator, task) match {
case Some(index) =>
GeneralOnSuccess(index)
case None => ;
}
}
/**
* Perform these checks when a task has reported successful completion to this TaskResolver.
* This is what actually happens upon completion.
* @param index the `TaskEntry` index
*/
private def GeneralOnSuccess(index: Int): Unit = {
val entry = tasks(index)
entry.task.onSuccess()
trace(s"success with task ${entry.task.Description}")
if (entry.supertaskRef != ActorRef.noSender) {
entry.supertaskRef ! TaskResolver.CompletedSubtask(
entry.task
) //alert our dependent task's resolver that we have completed
}
TaskCleanup(index)
}
/**
* Scan across a group of sub-tasks and determine if the associated main `Task` may execute.
* All of the sub-tasks must report a `Success` completion status before the main work can begin.
* @param subtask a `Task` that is a subtask of some parent task in this resolver's group
*/
private def ExecuteNewTasks(subtask: Task): Unit = {
TaskResolver.findTaskWithSubtask(tasks.iterator, subtask) match {
case Some(index) =>
val entry = tasks(index)
if (TaskResolver.filterCompletionMatch(entry.subtasks.iterator, Task.Resolution.Success)) {
trace(s"start new task ${entry.task.Description}")
entry.Execute(self)
StartTimeoutCheck()
}
case None => ;
}
}
/**
* Perform these checks when a task has reported failure to this `TaskResolver`.
* Since the `Failure(Throwable)` can not be associated with a specific task,
* every task and subtask will be checked, starting from the end of the list of queued entries,
* and only the first discovered one will be used.
* Consequently, the specific `Throwable` that contains the error message may have nothing to do with the failed task.
* @param ex a `Throwable` that reports what happened to the task
*/
private def OnFailure(ex: Throwable): Unit = {
//by reversing the List, we find the most outstanding Task with the completion state
TaskResolver.filterCompletion(tasks.indices.reverseIterator, tasks.toList, Task.Resolution.Failure) match {
case Some(index) =>
GeneralOnFailure(index, ex)
case None => ;
}
}
/**
* Perform these checks when a task has reported failure to this `TaskResolver`.
* @param subtask the task that had reported failure from some other resolver
* @param ex a `Throwable` that reports what happened to the task
*/
private def OnFailure(subtask: Task, ex: Throwable): Unit = {
TaskResolver.findTaskWithSubtask(tasks.iterator, subtask) match {
case Some(index) =>
GeneralOnFailure(index, ex)
case None => ;
}
}
/**
* Perform these checks when a task has reported failure to this `TaskResolver`.
* This is what actually happens upon completion.
* @param index the `TaskEntry` index
* @param ex a `Throwable` that reports what happened to the task
*/
private def GeneralOnFailure(index: Int, ex: Throwable): Unit = {
val entry = tasks(index)
val task = entry.task
trace(s"failure with task ${task.Description}")
task.onAbort(ex)
task.onFailure(ex)
if (entry.supertaskRef != ActorRef.noSender) {
entry.supertaskRef ! TaskResolver.Failure(task, ex) //alert our superior task's resolver we have completed
}
FaultSubtasks(entry)
TaskCleanup(index)
}
/**
* Instruct all subtasks of a given `Task` to fail.
* @param entry the target parent entry (that has failed)
*/
private def FaultSubtasks(entry: TaskResolver.TaskEntry): Unit = {
val ex: Throwable = new Exception(s"a task ${entry.task} had a subtask that failed")
entry.subtasks.foreach(subtask => {
context.parent ! Broadcast(TaskResolver.Failure(subtask, ex)) //we have no clue where this subtask was hosted
})
}
/**
* If a specific `Task` is governed by this `TaskResolver`, find its index and dispose of it and its known sub-tasks.
* @param task the work to be found
* @param ex a `Throwable` that reports what happened to the work
*/
private def OnAbort(task: Task, ex: Throwable): Unit = {
TaskResolver.findTask(tasks.iterator, task) match {
case Some(index) =>
PropagateAbort(index, ex)
TaskCleanup(index)
case None => ;
}
}
/**
* If a specific `Task` is governed by this `TaskResolver`, dispose of it and its known sub-tasks.
* @param index the index of the discovered work
* @param ex a `Throwable` that reports what happened to the work
*/
private def PropagateAbort(index: Int, ex: Throwable): Unit = {
tasks(index).subtasks.foreach({ subtask =>
if (subtask.isComplete == Task.Resolution.Success) {
trace(s"aborting task ${subtask.Description}")
subtask.onAbort(ex)
}
context.parent ! Broadcast(TaskResolver.AbortTask(subtask, ex))
})
}
/**
* Find all tasks that have been running for too long and declare them as timed-out.
* Run periodically, as long as work is being performed.
*/
private def TimeoutCleanup(): Unit = {
TaskResolver
.filterTimeout(tasks.indices.reverseIterator, tasks.toList, Task.TimeNow)
.foreach({ index =>
val ex: Throwable = new TimeoutException(s"a task ${tasks(index).task} has timed out")
tasks(index).task.onTimeout(ex)
PropagateAbort(index, ex)
})
}
/**
* Remove a `Task` that has reported completion.
* @param index an index of work in the `List` of `Task`s
*/
private def TaskCleanup(index: Int): Unit = {
tasks(index).task.Cleanup()
tasks.remove(index)
if (tasks.isEmpty) {
timeoutCleanup.cancel()
}
}
}
object TaskResolver {
/**
* Give this `TaskResolver` simple work to be performed.
* @param task the work to be completed
* @param subs other work that needs to be completed first
*/
final case class GiveTask(task: Task, subs: List[GiveTask] = Nil)
/**
* Pass around complex work to be performed.
* @param task the work to be completed
* @param subs other work that needs to be completed first
* @param resolver the `TaskResolver` that will handle work that depends on the outcome of this work
*/
private final case class GiveSubtask(task: Task, subs: List[GiveTask], resolver: ActorRef)
/**
* Run a scheduled timed-out `Task` check.
*/
private final case class TimeoutCleanup()
/**
* A specific kind of `Failure` that reports on which specific `Task` has reported failure.
* @param obj a task object
* @param ex information about what went wrong
*/
private final case class Failure(obj: Task, ex: Throwable)
/**
* A specific kind of `Success` that reports on which specific `Task` has reported Success where that `Task` was some other `Task`'s subtask.
* @param obj a task object
*/
private final case class CompletedSubtask(obj: Task)
/**
* A `Broadcast` message designed to find and remove a particular task from this series of routed `Actors`.
* @param task the work to be removed
* @param ex an explanation why the work is being aborted
*/
private final case class AbortTask(task: Task, ex: Throwable)
/**
* Storage unit for a specific unit of work, plus extra information.
* @param task the work to be completed
* @param subtasks other work that needs to be completed first
* //@param isASubtask whether this work is intermediary or the last in a dependency chain
* @param supertaskRef the `TaskResolver` that will handle work that depends on the outcome of this work
*/
private final case class TaskEntry(
task: Task,
subtasks: List[Task] = Nil,
supertaskRef: ActorRef = ActorRef.noSender
) {
private var start: Long = 0L
private var isExecuting: Boolean = false
def Start: Long = start
def Executing: Boolean = isExecuting
/**
* Only execute each task once.
* @param ref the `TaskResolver` currently handling this `Task`/`TaskEntry`
*/
def Execute(ref: ActorRef): Unit = {
if (!isExecuting) {
isExecuting = true
start = Task.TimeNow
task.Execute(ref)
}
}
}
/**
* Scan across a group of tasks to determine which ones match the target completion status.
* @param iter an `Iterator` of enqueued `TaskEntry` indices
* @param resolution the target completion status
* @return the first valid index when `TaskEntry` has its primary `Task` matching the completion status
*/
@tailrec private def filterCompletion(
iter: Iterator[Int],
tasks: List[TaskEntry],
resolution: Task.Resolution.Value
): Option[Int] = {
if (!iter.hasNext) {
None
} else {
val index: Int = iter.next()
if (tasks(index).task.isComplete == resolution) {
Some(index)
} else {
filterCompletion(iter, tasks, resolution)
}
}
}
/**
* Scan across a group of sub-tasks to determine if they all match the target completion status.
* @param iter an `Iterator` of enqueued sub-tasks
* @param resolution the target completion status
* @return `true`, if all tasks match the complete status;
* `false`, otherwise
*/
@tailrec private def filterCompletionMatch(iter: Iterator[Task], resolution: Task.Resolution.Value): Boolean = {
if (!iter.hasNext) {
true
} else {
if (iter.next().isComplete == resolution) {
filterCompletionMatch(iter, resolution)
} else {
false
}
}
}
/**
* Find the indices of all enqueued work that has timed-out.
* @param iter an `Iterator` of enqueued `TaskEntry` indices
* @param now the current time in milliseconds
* @param indexList a persistent `List` of indices
* @return the `List` of all valid `Task` indices
*/
@tailrec private def filterTimeout(
iter: Iterator[Int],
tasks: List[TaskEntry],
now: Long,
indexList: List[Int] = Nil
): List[Int] = {
if (!iter.hasNext) {
indexList
} else {
val index: Int = iter.next()
val taskEntry = tasks(index)
if (
taskEntry.Executing && taskEntry.task.isComplete == Task.Resolution.Incomplete && now - taskEntry.Start > taskEntry.task.Timeout
) {
filterTimeout(iter, tasks, now, indexList :+ index)
} else {
filterTimeout(iter, tasks, now, indexList)
}
}
}
/**
* Find the index of the targeted `Task`, if it is enqueued here.
* @param iter an `Iterator` of entries
* @param target a target `Task`
* @param index the current index in the aforementioned `List`;
* defaults to 0
* @return the index of the discovered task, or `None`
*/
@tailrec private def findTask(iter: Iterator[TaskEntry], target: Task, index: Int = 0): Option[Int] = {
if (!iter.hasNext) {
None
} else {
if (iter.next().task == target) {
Some(index)
} else {
findTask(iter, target, index + 1)
}
}
}
/**
* Find the index of the targeted `Task`, if it is enqueued here, given a specific "subtask" of that `Task`.
* @param iter an `Iterator` of entries
* @param target a target subtask
* @param index the current index in the aforementioned `List`;
* defaults to 0
* @return the index of the discovered task, or `None`
*/
@tailrec private def findTaskWithSubtask(iter: Iterator[TaskEntry], target: Task, index: Int = 0): Option[Int] = {
if (!iter.hasNext) {
None
} else {
val tEntry = iter.next()
if (tEntry.subtasks.contains(target)) {
Some(index)
} else {
findTaskWithSubtask(iter, target, index + 1)
}
}
}
}

View file

@ -0,0 +1,215 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.guid
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Future, Promise}
import scala.util.{Failure, Success}
/**
* Parts of the task resolution lifecycle.
*/
sealed trait TaskBehaviors {
/** What the task is supposed to accomplish. */
def action(): Future[Any]
/** A reversal of 'what the task is supposed to accomplish'. */
def undo(): Unit
/** Has the task been successfully completed? */
def isSuccessful(): Boolean
/** Describe this task's actions. */
def description(): String = getClass.getSimpleName
}
/**
* A primary unit of work in a workflow.
*/
trait Task
extends TaskBehaviors {
/** A careful determination if the task can be attempted.
* @see `Task.action`
*/
private[guid] def performAction(): Future[Any] = {
if (!isSuccessful()) {
action()
} else {
Future(Failure(new TaskNotExecutedException(task = this)))
}
}
/** A careful determination if the task needs to be undone.
* @see `Task.undo`
*/
private[guid] def performUndo(): Unit = {
if (isSuccessful()) undo() else ()
}
}
/**
* A primary unit of work in a workflow that is set up to execute and never be taken back.
* Good for top-level tasking that only reports on the success of work carried out by subtasks.
*/
trait StraightforwardTask
extends Task {
def undo(): Unit = { /* blank */ }
def isSuccessful(): Boolean = false /* always primed to be executed */
}
/**
* The packaging of a more complicated unit of work in a workflow
* in which one task relies on the successful completion of other tasks.
* @param mainTask the primary task
* @param subTasks tasks that are necessary to complete before starting on the primary one
*/
final case class TaskBundle(mainTask: Task, subTasks: Seq[TaskBundle])
extends TaskBehaviors {
/** Attempt 'what the [primary] task is supposed to accomplish'. */
def action(): Future[Any] = mainTask.performAction()
/** Attempt a reversal of what the all the connected tasks are 'supposed to accomplish'. */
def undo() : Unit = {
mainTask.performUndo()
subTasks.foreach { _.undo() }
}
/** A hierarchical analysis of whether `the task been successfully completed`. */
def isSuccessful(): Boolean = mainTask.isSuccessful() && subTasks.forall { _.isSuccessful() }
override def description(): String = {
val subCount: String = if (subTasks.nonEmpty) s" (${subTasks.size} subtasks)" else ""
s"${mainTask.description()}$subCount"
}
}
object TaskBundle {
/**
* The packaging of a unit of work in a workflow.
* @param task the task
*/
def apply(task: Task): TaskBundle = TaskBundle(task, List())
/**
* The packaging of a unit of work in a workflow
* and a single task required to be completed first.
* @param task the primary task
* @param subTask the task that must be completed before the primary task
*/
def apply(task: Task, subTask: Task): TaskBundle = TaskBundle(task, TaskBundle(subTask))
/**
* The packaging of a unit of work in a workflow
* and the task(s) required to be completed first.
* @param task the primary task
* @param subTask the task(s) that must be completed before the primary task
*/
def apply(task: Task, subTask: TaskBundle): TaskBundle = TaskBundle(task, Seq(subTask))
}
class TaskNotExecutedException(task: TaskBehaviors, msg: String) extends Exception(msg) {
def this(task: Task) = {
this(task, s"task '${task.description()}' was not successful")
}
def this(task: TaskBundle) = {
this(task, s"task ${task.description()} was not successful")
}
}
object TaskWorkflow {
/**
* The entry into the task workflow resolution process.
* @param taskTree the packaged tasks that need to be completed
* @return the anticipation of a task to be completed
*/
def execute(taskTree: TaskBundle): Future[Any] = {
evaluateTaskAndSubs(taskTree)
}
private def evaluateTaskAndSubs(task: TaskBundle): Future[Any] = {
val promise = Promise[Any]()
val (result, subResults) = composeTaskAndSubs(task)
result.onComplete { _ =>
if (matchOnFutureFailure(result)) {
//every subtask that has already succeeded must be undone
subResults
.zip(task.subTasks)
.collect { case (a, b) if matchOnFutureSuccess(a) => b }
.foreach { _.undo() }
}
promise.completeWith(result)
}
promise.future
}
private def composeTaskAndSubs(task: TaskBundle): (Future[Any], Seq[Future[Any]]) = {
val promise = Promise[Any]()
val composedSubs = task.subTasks.map(evaluateTaskAndSubs)
composedSubs match {
case Nil =>
//no subtasks; just execute the main task
promise.completeWith(task.action())
case list =>
var unassignedCompletion: Boolean = true //shared mutex
//wait for subtasks to complete
list.foreach { result =>
result.onComplete { _ =>
unassignedCompletion.synchronized {
if (unassignedCompletion && composedSubs.forall(matchOnFutureCompletion)) {
unassignedCompletion = false
if (composedSubs.forall(matchOnFutureSuccess)) {
//if all subtasks passed, execute the main task
promise.completeWith(task.action())
} else {
//if some subtasks did not succeed, pass on wrapped failure
promise.completeWith(Future(Failure(new TaskNotExecutedException(task))))
}
}
}
}
}
}
(promise.future, composedSubs)
}
/**
* Does this anticipation of a task report having completed?
* @param f the anticipation
* @return whether it has been completed (passed or failed)
*/
def matchOnFutureCompletion(f: Future[Any]): Boolean = {
/*
if 'matchOnFutureCompletion(FUTURE) == false' then 'matchOnFutureSuccess(FUTURE) == matchOnFutureFailure(FUTURE)'
if 'matchOnFutureCompletion(FUTURE) == true' then 'matchOnFutureSuccess(FUTURE) != matchOnFutureFailure(FUTURE)'
*/
f.value match {
case Some(_) => true
case None => false
}
}
/**
* Does this anticipation of a task report having succeeded?
* The only true success is one where there is no `Failure` and no `Exception`.
* @param f the anticipation
* @return whether it has succeeded
*/
def matchOnFutureSuccess(f: Future[Any]): Boolean = {
f.value match {
case Some(Success(_: Exception)) => false
case Some(Success(Failure(_))) => false
case Some(Success(_)) => true
case _ => false
}
}
/**
* Does this anticipation of a task report having failed?
* Having not yet completed does not count as a failure.
* @param f the anticipation
* @return whether it has failed
*/
def matchOnFutureFailure(f: Future[Any]): Boolean = {
f.value match {
case Some(Failure(_)) => true
case Some(Success(_: Exception)) => true
case Some(Success(Failure(_))) => true
case _ => false
}
}
}

View file

@ -0,0 +1,454 @@
// Copyright (c) 2017-2021 PSForever
package net.psforever.objects.guid
import akka.actor.{Actor, ActorContext, ActorRef, Props}
import akka.pattern.{AskTimeoutException, ask}
import akka.util.Timeout
import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.guid.uns.{
AlreadyRegisteredEntity,
AlreadyUnregisteredEntity,
NumberPoolActor,
RegisteredEntity,
UnregisteredEntity
}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{Future, Promise}
import scala.util.{Failure, Success}
/**
* Wrap around converted `NumberPool`s and synchronize a portion of the number registration process
* as a part of the global unique identifier (GUID, UID) number system (UNS, "unique number system").
* The ultimate goal is to manage a coherent group of unique identifiers for a given "region".
* Both parts of the UID system sit atop the `Zone` for easy external access.
* The plain part - the `NumberPoolHub` here - is used for low-priority requests
* such as checking for existing associations.<br>
* <br>
* A four part process is used for object registration tasks.
* First, the requested `NumberPool` is located among the list of known `NumberPool`s.
* Second, an asynchronous request is sent to that pool to retrieve a number.
* (Only any number. Only a failing case allows for selection of a specific number.)
* Third, the asynchronous request returns and the original information about the request is recovered.
* Fourth, both sides of the contract are completed by the object being assigned the number and
* the underlying "number source" is made to remember an association between the object and the number.
* Short circuits and recoveries as available on all steps though reporting is split between logging and callbacks.
* The process of removing the association between a number and object (unregistering) is a similar four part process.<br>
* <br>
* The important relationship between this `Actor` and the `Map` of `NumberPoolActors` is as a "gate."
* A single `Map` is constructed and shared between multiple entry points to the UID system where requests are messaged.
* Multiple entry points send messages to the same `NumberPool`.
* That `NumberPool` deals with the messages one at a time and sends reply to each entry point that communicated with it.
* This process is almost as fast as the process of the `NumberPool` selecting a number.
* (At least, both should be fast.)
* @param guid the supporting datatype for the unique number distribution
* @param poolActors a mapping created from the `NumberPool`s, to achieve synchronized access
*/
class UniqueNumberOps(
private val guid: NumberPoolHub,
private val poolActors: Map[String, ActorRef]
) {
/** The timeout used by all number pool `ask` messaging */
private implicit val timeout = UniqueNumberOps.timeout
/**
* The entry point for the entity GUID registration process.
* A special check is made first to determine if the entity is already registered, and if so to where.
* If the entity is not registered, then the process continues.
* @param obj the entity to be assigned a GUID
* @param poolName the pool from which the entity wants a GUID to be selected
* @return the anticipation of this activity being completed
*/
def Register(
obj: IdentifiableEntity,
poolName: String
): Future[Any] = {
val result: Promise[Any] = Promise()
if (obj.HasGUID) {
alreadyRegisteredTo(obj, poolName) match {
case Some(pname) =>
result.success(AlreadyRegisteredEntity(RegisteredEntity(obj, pname, guid, obj.GUID.guid)))
case None =>
result.failure(new RegisteredToWrongPlaceException(obj, obj.GUID.guid))
}
} else {
result.completeWith(registrationProcess(obj, poolName))
}
result.future
}
/**
* The entry point for the entity GUID unregistration process.
* A special check is made first to determine where, if at all, the entity is registered.
* Obviously, if the entity is not registered somewhere within purview of this UNS, the process can not continue.
* If the entity's registration number pool is found, then the process continues.
* @param obj the entity to be unassigned its GUID
* @return the anticipation of this activity being completed
*/
def Unregister(obj: IdentifiableEntity): Future[Any] = {
val result: Promise[Any] = Promise()
if (obj.HasGUID) {
val number = obj.GUID.guid
guid.WhichPool(number) match {
case Some(pname) =>
result.completeWith(unregistrationProcess(obj, number, pname))
case None =>
result.failure(new RegisteredToWrongPlaceException(obj, number))
}
} else {
UniqueNumberOps.log.warn(s"$obj is already unregistered")
result.success(Future(AlreadyUnregisteredEntity(UnregisteredEntity(obj, "", guid, -1))))
}
result.future
}
/**
* A step of the entity GUID registration process.
* Pass control through to the next step.
* @see `registrationProcess(IdentifiableEntity, NumberPoolHub, Map[String, ActorRef], String)`
* @param obj the entity to be assigned a GUID
* @param poolName the pool to which the object is trying to register
* @return the anticipation of this activity being completed
*/
private def registrationProcess(
obj: IdentifiableEntity,
poolName: String
): Future[Any] = {
registrationProcess(obj, guid, poolActors, poolName)
}
/**
* A step of the entity GUID registration process.
* Send a message to the `NumberPool` to request a number.
* If a number is received, continue with a successful registration process.
* If no number is received, or some other issue occurs, attempt to recover from the error and report it.
* This method is designed to be recursive as it is also utilized for the recovery attempt
* and must pass all of the necessary information on to that next attempt.
* @param obj the entity to be assigned a GUID
* @param hub the supporting datatype for the unique number distribution
* @param pools a mapping created from the `NumberPool`s, to achieve synchronized access
* @param poolName the pool to which the object is trying to register
* @return the anticipation of this activity being completed
*/
private def registrationProcess(
obj: IdentifiableEntity,
hub: NumberPoolHub,
pools: Map[String, ActorRef],
poolName: String
): Future[Any] = {
val promisingResult: Promise[Any] = Promise()
pools.get(poolName) match {
case Some(pool) =>
//cache
val localPromise = promisingResult
val localTarget = obj
val localUns = hub
val localPools = pools
val localPoolName = poolName
val localPool = pool
val result = ask(pool, NumberPoolActor.GetAnyNumber())(timeout)
result.onComplete {
case Success(NumberPoolActor.GiveNumber(number)) =>
UniqueNumberOps.processRegisterResult(
localPromise,
localTarget,
localUns,
localPoolName,
localPool,
number
)
case Success(NumberPoolActor.NoNumber(ex)) =>
registrationProcessRetry(localPromise, ex, localTarget, localUns, localPools, localPoolName)
case msg =>
UniqueNumberOps.log.warn(s"unexpected message during $localTarget's registration process - $msg")
}
result.recover {
case ex: AskTimeoutException =>
localPromise.failure(new RegisteringException(msg = s"did not register entity $localTarget in time", ex))
}
case None =>
//do not log
val ex = new Exception(s"can not find pool $poolName")
registrationProcessRetry(promisingResult, ex, obj, guid, pools, poolName)
}
promisingResult.future
}
/**
* na
* @param promise the ongoing promise to be fulfilled for the future
* @param exception an issue that has arrisen, forcing the retry attempt
* @param obj the entity to be assigned a GUID
* @param hub the supporting datatype for the unique number distribution
* @param pools a mapping created from the `NumberPool`s, to achieve synchronized access
* @param poolName the pool to which the object is trying to register
*/
def registrationProcessRetry(
promise: Promise[Any],
exception: Throwable,
obj: IdentifiableEntity,
hub: NumberPoolHub,
pools: Map[String, ActorRef],
poolName: String
): Unit = {
if (poolName.equals("generic")) {
promise.failure(new RegisteringException(msg = s"did not register entity $obj", exception))
} else {
org.log4s.getLogger("UniqueNumberOps").warn(s"${exception.getLocalizedMessage()} - $poolName")
promise.completeWith(registrationProcess(obj, guid, pools, poolName = "generic"))
}
}
/**
* A step of the entity GUID unregistration process.
* Pass control through to the next step.
* @see `unregistrationProcess(IdentifiableEntity, NumberPoolHub, Map[String, ActorRef], Int, String)`
* @param obj the entity to be unassigned its GUID
* @param number the number that was previously drawn from the specified `NumberPool`
* @param poolName the pool to which the number will try to be returned
* @return the anticipation of this activity being completed
*/
private def unregistrationProcess(
obj: IdentifiableEntity,
number: Int,
poolName: String
): Future[Any] = {
unregistrationProcess(obj, guid, poolActors, number, poolName)
}
/**
* A step of the entity GUID unregistration process.
* ...
* @param obj the entity to be unassigned its GUID
* @param hub the supporting datatype for the unique number distribution
* @param pools a mapping created from the `NumberPool`s, to achieve synchronized access
* @param number the number that was previously drawn from the specified `NumberPool`
* @param poolName the pool to which the number will try to be returned
* @return the anticipation of this activity being completed
*/
private def unregistrationProcess(
obj: IdentifiableEntity,
hub: NumberPoolHub,
pools: Map[String, ActorRef],
number: Int,
poolName: String
): Future[Any] = {
val promisingResult: Promise[Any] = Promise()
pools.get(poolName) match {
case Some(pool) =>
//cache
val localPromise = promisingResult
val localTarget = obj
val localUns = hub
val localPoolName = poolName
val localPool = pool
val localNumber = number
val result = ask(pool, NumberPoolActor.ReturnNumber(number))
result.onComplete {
case Success(NumberPoolActor.ReturnNumberResult(_, None)) =>
UniqueNumberOps.processUnregisterResult(
localPromise,
localTarget,
localUns,
localPoolName,
localPool,
localNumber
)
case Success(NumberPoolActor.ReturnNumberResult(_, Some(ex))) => //if there is a problem when returning the number
localPromise.failure { new UnregisteringException(msg = s"could not unregister $localTarget with number $localNumber", ex) }
case msg =>
UniqueNumberOps.log.warn(s"unexpected message $msg during $localTarget's unregistration process")
}
result.recover {
case ex: AskTimeoutException =>
localPromise.failure { new UnregisteringException(msg = s"did not unregister entity $localTarget in time", ex) }
}
case None =>
//do not log; use callback
promisingResult.failure { new UnregisteringException(msg = s"can not find pool $poolName; $obj was not unregistered") }
}
promisingResult.future
}
/**
* Generate a relevant logging message for an object that is trying to register to this UNS
* but is actually already registered to this UNS.
* Also take note if the entity is (probably) not registered to this UNS.
* @param obj the object that was trying to register
* @param poolName the pool to which the object was trying to register
* @return the pool name to which the entity is registered, if it can be discovered
*/
private def alreadyRegisteredTo(obj: IdentifiableEntity, poolName: String): Option[String] = {
val (msg, determinedName) =
guid.WhichPool(obj) match {
case out @ Some(pname) =>
if (poolName.equals(pname)) {
(s"to pool $poolName", Some(poolName))
} else {
(s"but to different pool $pname", out)
}
case None =>
("but not to any pool known to this system", None)
}
UniqueNumberOps.log.warn(s"$obj already registered $msg")
determinedName
}
}
object UniqueNumberOps {
private val log = org.log4s.getLogger
private implicit val timeout = Timeout(2.seconds)
/**
* Final step of the object registration process.
* This step completes the registration by asking the `NumberPoolHub` to sort out its `NumberSource`.
* @param promise the ongoing promise to be fulfilled for the future
* @param obj the entity to be assigned a GUID
* @param guid the supporting datatype for the unique number distribution
* @param poolName the name of the pool to which the object is trying to register
* @param pool the pool to which the object is trying to register
* @param number the number that was drawn
*/
private def processRegisterResult(
promise: Promise[Any],
obj: IdentifiableEntity,
guid: NumberPoolHub,
poolName: String,
pool: ActorRef,
number: Int
): Unit = {
guid.latterPartRegister(obj, number) match {
case Success(_) =>
promise.success(RegisteredEntity(obj, poolName, guid, number))
case Failure(ex) =>
//do not log; use callback
returnNumberNoCallback(number, pool) //recovery?
promise.failure(ex)
}
}
/**
* A step of the object unregistration process.
* This step completes revoking of the object's registration by consulting the `NumberSource`.
* @param promise the ongoing promise to be fulfilled for the future
* @param obj the entity to be unassigned its GUID
* @param guid the supporting datatype for the unique number distribution
* @param poolName the name of pool to which the number will try to be returned
* @param pool the pool to which the number will try to be returned
* @param number the number that was previously drawn from the specified `NumberPool`
*/
private def processUnregisterResult(
promise: Promise[Any],
obj: IdentifiableEntity,
guid: NumberPoolHub,
poolName: String,
pool: ActorRef,
number: Int
): Unit = {
guid.latterPartUnregister(number) match {
case Some(_) =>
obj.Invalidate()
promise.success(UnregisteredEntity(obj, poolName, guid, number))
case None =>
//do not log
requestSpecificNumberNoCallback(number, pool) //recovery?
promise.failure(new UnregisteringException(msg = s"failed to unregister $obj from number $number; this may be a critical error"))
}
}
/**
* Access a specific `NumberPool` in a way that doesn't propagate a callback and reset one of its numbers.
* The `ask` pattern catches any reply message and ensures nothing happens because of it.
* @param number the number that was drawn from a `NumberPool`
* @param pool the `NumberPool` from which the `number` was drawn
*/
private def returnNumberNoCallback(number: Int, pool: ActorRef): Unit = {
val result = ask(pool, NumberPoolActor.ReturnNumber(number))
result.onComplete { _ => ; }
result.recover { case _ => ; }
}
/**
* Access a specific `NumberPool` in a way that doesn't propagate a callback and claim one of its numbers.
* The `ask` pattern catches any reply message and ensures nothing happens because of it.
* @param number the number to be drawn from a `NumberPool`
* @param pool the `NumberPool` from which the `number` is to be drawn
*/
private def requestSpecificNumberNoCallback(number: Int, pool: ActorRef): Unit = {
val result = ask(pool, NumberPoolActor.GetSpecificNumber(number))
result.onComplete { _ => ; }
result.recover { case _ => ; }
}
}
class RegisteringException(msg: String) extends Exception(msg) {
def this(msg: String, cause: Throwable) = {
this(msg)
initCause(cause)
}
}
class UnregisteringException(msg: String) extends Exception(msg) {
def this(msg: String, cause: Throwable) = {
this(msg)
initCause(cause)
}
}
/**
* The entity was registered, but not to the target UNS.
* Rookie mistake.
* @param obj the entity to be assigned a GUID
* @param number the name associated with this entity
*/
class RegisteredToWrongPlaceException(obj: IdentifiableEntity, number: Int)
extends RuntimeException(s"$obj registered to number $number that is not part of a known or local number pool") {
def this(obj: IdentifiableEntity, number: Int, cause: Throwable) = {
this(obj, number)
initCause(cause)
}
}
/**
* A class for spawning `Actor`s to manage the number pools and
* create a number system operations class to access those pools within the context of registering and unregistering.
* This `Actor` persists to maintain the number pool `Actor`s.
* Note the `final` do-nothing `receive` method.
* This `Actor` should do __nothing__ through message passing.
* @see `UniqueNumberOps`
* @param hub the number pool management class
* @param poolActorConversionFunc the number pool management class
*/
class UniqueNumberSetup(
hub: NumberPoolHub,
poolActorConversionFunc: (ActorContext, NumberPoolHub) => Map[String, ActorRef]
) extends Actor {
init()
final def receive: Receive = { case _ => ; }
def init(): UniqueNumberOps = {
new UniqueNumberOps(hub, poolActorConversionFunc(context, hub))
}
}
object UniqueNumberSetup {
/**
* Transform `NumberPool`s into `NumberPoolActor`s and pair them with their name.
* @param poolSource where the raw `NumberPools` are located
* @param context used to create the `NumberPoolActor` instances
* @return a `Map` of the pool names to the `ActorRef` created from the `NumberPool`
*/
def AllocateNumberPoolActors(context: ActorContext, poolSource: NumberPoolHub): Map[String, ActorRef] = {
poolSource.Pools
.map { case (pname, pool) => (pname, context.actorOf(Props(classOf[NumberPoolActor], pool), pname)) }
.toMap
}
}

View file

@ -1,86 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.guid.actor
import akka.actor.ActorRef
import net.psforever.objects.entity.IdentifiableEntity
/**
* A message for accepting object-number registration requests.<br>
* <br>
* The callback is actually an `ActorRef` to which a `RegisterSuccess` message or a `RegisterFailure` message is sent.
* This is as opposed to what a "callback" is normally - a function.
* @param obj the mandatory object
* @param name the optional name of the number pool to which this object is registered
* @param number the optional number pre-selected for registering this object
* @param callback the optional custom callback for the messages from the success or failure conditions
*/
final case class Register(
obj: IdentifiableEntity,
name: Option[String],
number: Option[Int],
callback: Option[ActorRef]
)
object Register {
/**
* Overloaded constructor, accepting just the object.
* @param obj the object to be registered
* @return a `Register` object
*/
def apply(obj: IdentifiableEntity): Register = {
new Register(obj, None, None, None)
}
/**
* Overloaded constructor, accepting the object and a callback.
* @param obj the object to be registered
* @param callback the custom callback for the messages from the success or failure conditions
* @return a `Register` object
*/
def apply(obj: IdentifiableEntity, callback: ActorRef): Register = {
new Register(obj, None, None, Some(callback))
}
/**
* Overloaded constructor, accepting an object and a pre-selected number.
* @param obj the object to be registered
* @param number the pre-selected number
* @return a `Register` object
*/
def apply(obj: IdentifiableEntity, number: Int): Register = {
new Register(obj, None, Some(number), None)
}
/**
* Overloaded constructor, accepting an object, a pre-selected number, and a callback.
* @param obj the object to be registered
* @param number the pre-selected number
* @param callback the custom callback for the messages from the success or failure conditions
* @return a `Register` object
*/
def apply(obj: IdentifiableEntity, number: Int, callback: ActorRef): Register = {
new Register(obj, None, Some(number), Some(callback))
}
/**
* Overloaded constructor, accepting an object and a number pool.
* @param obj the object to be registered
* @param name the number pool name
* @return a `Register` object
*/
def apply(obj: IdentifiableEntity, name: String): Register = {
new Register(obj, Some(name), None, None)
}
/**
* Overloaded constructor, accepting an object, a number pool, and a callback.
* @param obj the object to be registered
* @param name the number pool name
* @param callback the custom callback for the messages from the success or failure conditions
* @return a `Register` object
*/
def apply(obj: IdentifiableEntity, name: String, callback: ActorRef): Register = {
new Register(obj, Some(name), None, Some(callback))
}
}

View file

@ -1,348 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.guid.actor
import akka.actor.{Actor, ActorContext, ActorRef, Props}
import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.guid.NumberPoolHub
import scala.util.{Failure, Success}
/**
* An `Actor` that wraps around converted `NumberPool`s and synchronizes a portion of the number registration process.
* The ultimate goal is to manage a coherent group of unique identifiers for a given "region" (`Zone`).
* Both parts of the UID system sit atop the `Zone` for easy external access.
* The plain part - the `NumberPoolHub` here - is used for low-priority requests such as checking for existing associations.
* This `Actor` is the involved portion that paces registration and unregistration.<br>
* <br>
* A four part process is used for object registration tasks.
* First, the requested `NumberPool` is located among the list of known `NumberPool`s.
* Second, an asynchronous request is sent to that pool to retrieve a number.
* (Only any number. Only a failing case allows for selection of a specific number.)
* Third, the asynchronous request returns and the original information about the request is recovered.
* Fourth, both sides of the contract are completed by the object being assigned the number and
* the underlying "number source" is made to remember an association between the object and the number.
* Short circuits and recoveries as available on all steps though reporting is split between logging and callbacks.
* The process of removing the association between a number and object (unregistering) is a similar four part process.<br>
* <br>
* The important relationship between this `Actor` and the `Map` of `NumberPoolActors` is an "gate."
* A single `Map` is constructed and shared between multiple entry points to the UID system where requests are messaged.
* Multiple entry points send messages to the same `NumberPool`.
* That `NumberPool` deals with the messages one at a time and sends reply to each entry point that communicated with it.
* This process is almost as fast as the process of the `NumberPool` selecting a number.
* (At least, both should be fast.)
* @param guid the `NumberPoolHub` that is partially manipulated by this `Actor`
* @param poolActors a common mapping created from the `NumberPool`s in `guid`;
* there is currently no check for this condition save for requests failing
*/
class UniqueNumberSystem(private val guid: NumberPoolHub, private val poolActors: Map[String, ActorRef]) extends Actor {
/** Information about Register and Unregister requests that persists between messages to a specific `NumberPool`. */
private val requestQueue: collection.mutable.LongMap[UniqueNumberSystem.GUIDRequest] =
new collection.mutable.LongMap()
/** The current value for the next request entry's index. */
private var index: Long = Long.MinValue
private[this] val log = org.log4s.getLogger
def receive: Receive = {
case Register(obj, Some(pname), None, call) =>
val callback = call.getOrElse(sender())
if (obj.HasGUID) {
AlreadyRegistered(obj, pname)
callback ! Success(obj)
} else {
val id: Long = index
index += 1
requestQueue += id -> UniqueNumberSystem.GUIDRequest(obj, pname, callback)
RegistrationProcess(pname, id)
}
//this message is automatically sent by NumberPoolActor
case NumberPoolActor.GiveNumber(number, id) =>
id match {
case Some(nid: Long) =>
RegistrationProcess(requestQueue.remove(nid), number, nid)
case _ =>
log.warn(s"received a number but there is no request to process it; returning number to pool")
NoCallbackReturnNumber(number) //recovery?
//no callback is possible
}
//this message is automatically sent by NumberPoolActor
case NumberPoolActor.NoNumber(ex, id) =>
id match {
case Some(nid: Long) =>
requestQueue.remove(nid) match {
case Some(entry) =>
entry.replyTo ! Failure(ex) //ONLY callback that is possible
case None => ;
log.warn(
s"failed number request and no record of number request - $ex"
) //neither a successful request nor an entry of making the request
}
case None => ;
log.warn(
s"failed number request and no record of number request - $ex"
) //neither a successful request nor an entry of making the request
case _ => ;
log.warn(s"unrecognized request $id accompanying a failed number request - $ex")
}
case Unregister(obj, call) =>
val callback = call.getOrElse(sender())
try {
val number = obj.GUID.guid
guid.WhichPool(number) match {
case Some(pname) =>
val id: Long = index
index += 1
requestQueue += id -> UniqueNumberSystem.GUIDRequest(obj, pname, callback)
UnregistrationProcess(pname, number, id)
case None =>
callback ! Failure(new Exception(s"the GUID of object $obj - $number - is not a part of this number pool"))
}
} catch {
case _: Exception =>
log.warn(s"$obj is already unregistered")
callback ! Success(obj)
}
//this message is automatically sent by NumberPoolActor
case NumberPoolActor.ReturnNumberResult(number, None, id) =>
id match {
case Some(nid: Long) =>
UnregistrationProcess(requestQueue.remove(nid), number, nid)
case _ =>
log.error(s"returned a number but there is no request to process it; recovering the number from pool")
NoCallbackGetSpecificNumber(number) //recovery?
//no callback is possible
}
//this message is automatically sent by NumberPoolActor
case NumberPoolActor.ReturnNumberResult(number, Some(ex), id) => //if there is a problem when returning the number
id match {
case Some(nid: Long) =>
requestQueue.remove(nid) match {
case Some(entry) =>
entry.replyTo ! Failure(new Exception(s"for ${entry.target} with number $number, ${ex.getMessage}"))
case None => ;
log.error(s"could not find original request $nid that caused error $ex, but pool was ${sender()}")
//no callback is possible
}
case _ => ;
log.error(s"could not find original request $id that caused error $ex, but pool was ${sender()}")
//no callback is possible
}
case msg =>
log.warn(s"unexpected message received - $msg")
}
/**
* A step of the object registration process.
* Send a message to the `NumberPool` to request a number back.
* @param poolName the pool to which the object is trying to register
* @param id a potential identifier to associate this request
*/
private def RegistrationProcess(poolName: String, id: Long): Unit = {
poolActors.get(poolName) match {
case Some(pool) =>
pool ! NumberPoolActor.GetAnyNumber(Some(id))
case None =>
//do not log; use callback
requestQueue.remove(id).get.replyTo ! Failure(
new Exception(s"can not find pool $poolName; nothing was registered")
)
}
}
/**
* A step of the object registration process.
* If there is a successful request object to be found, continue the registration request.
* @param request the original request data
* @param number the number that was drawn from a `NumberPool`
*/
private def RegistrationProcess(request: Option[UniqueNumberSystem.GUIDRequest], number: Int, id: Long): Unit = {
request match {
case Some(entry) =>
processRegisterResult(entry, number)
case None =>
log.error(s"returned a number but the rest of the request is missing (id:$id)")
if (id != Long.MinValue) { //check to ignore endless loop of error-catching
log.warn("returning number to pool")
NoCallbackReturnNumber(number) //recovery?
//no callback is possible
}
}
}
/**
* A step of the object registration process.
* This step completes the registration by asking the `NumberPoolHub` to sort out its `NumberSource`.
* @param entry the original request data
* @param number the number to use
*/
private def processRegisterResult(entry: UniqueNumberSystem.GUIDRequest, number: Int): Unit = {
val obj = entry.target
guid.latterPartRegister(obj, number) match {
case Success(_) =>
entry.replyTo ! Success(obj)
case Failure(ex) =>
//do not log; use callback
NoCallbackReturnNumber(number, entry.targetPool) //recovery?
entry.replyTo ! Failure(ex)
}
}
/**
* A step of the object unregistration process.
* Send a message to the `NumberPool` to restore the availability of one of its numbers.
* @param poolName the pool to which the number will try to be returned
* @param number the number that was previously drawn from the specified `NumberPool`
* @param id a potential identifier to associate this request
*/
private def UnregistrationProcess(poolName: String, number: Int, id: Long): Unit = {
poolActors.get(poolName) match {
case Some(pool) =>
pool ! NumberPoolActor.ReturnNumber(number, Some(id))
case None =>
//do not log; use callback
requestQueue.remove(id).get.replyTo ! Failure(
new Exception(s"can not find pool $poolName; nothing was de-registered")
)
}
}
/**
* A step of the object unregistration process.
* If there is a successful request object to be found, continue the registration request.
* @param request the original request data
* @param number the number that was drawn from the `NumberPool`
*/
private def UnregistrationProcess(request: Option[UniqueNumberSystem.GUIDRequest], number: Int, id: Long): Unit = {
request match {
case Some(entry) =>
processUnregisterResult(entry, number)
case None =>
log.error(s"returned a number but the rest of the request is missing (id:$id)")
if (id != Long.MinValue) { //check to ignore endless loop of error-catching
log.error("recovering the number from pool")
NoCallbackGetSpecificNumber(number) //recovery?
//no callback is possible
}
}
}
/**
* A step of the object unregistration process.
* This step completes revoking of the object's registration by consulting the `NumberSource`.
* @param entry the original request data
* @param number the number to use
*/
private def processUnregisterResult(entry: UniqueNumberSystem.GUIDRequest, number: Int): Unit = {
val obj = entry.target
guid.latterPartUnregister(number) match {
case Some(_) =>
obj.Invalidate()
entry.replyTo ! Success(obj)
case None =>
//do not log; use callback
NoCallbackGetSpecificNumber(number, entry.targetPool) //recovery?
entry.replyTo ! Failure(new Exception(s"failed to unregister a number; this may be a critical error"))
}
}
/**
* Generate a relevant logging message for an object that is trying to register is actually already registered.
* @param obj the object that was trying to register
* @param poolName the pool to which the object was trying to register
*/
private def AlreadyRegistered(obj: IdentifiableEntity, poolName: String): Unit = {
val msg =
guid.WhichPool(obj) match {
case Some(pname) =>
if (poolName.equals(pname)) {
s"to pool $poolName"
} else {
s"but to different pool $pname"
}
case None =>
"but not to any pool known to this system"
}
log.warn(s"$obj already registered $msg")
}
/**
* Access a specific `NumberPool` in a way that doesn't invoke a callback and reset one of its numbers.
* @param number the number that was drawn from a `NumberPool`
*/
private def NoCallbackReturnNumber(number: Int): Unit = {
guid.WhichPool(number) match {
case Some(pname) =>
NoCallbackReturnNumber(number, pname)
case None =>
log.error(s"critical: tried to return number $number but could not find containing pool")
}
}
/**
* Access a specific `NumberPool` in a way that doesn't invoke a callback and reset one of its numbers.
* To avoid fully processing the callback, an id of `Long.MinValue` is used to short circuit the routine.
* @param number the number that was drawn from a `NumberPool`
* @param poolName the `NumberPool` from which the `number` was drawn
* @see `UniqueNumberSystem.UnregistrationProcess(Option[GUIDRequest], Int, Int)`
*/
private def NoCallbackReturnNumber(number: Int, poolName: String): Unit = {
poolActors(poolName) ! NumberPoolActor.ReturnNumber(number, Some(Long.MinValue))
}
/**
* Access a specific `NumberPool` in a way that doesn't invoke a callback and claim one of its numbers.
* @param number the number to be drawn from a `NumberPool`
*/
private def NoCallbackGetSpecificNumber(number: Int): Unit = {
guid.WhichPool(number) match {
case Some(pname) =>
NoCallbackGetSpecificNumber(number, pname)
case None =>
log.error(s"critical: tried to re-register number $number but could not find containing pool")
}
}
/**
* Access a specific `NumberPool` in a way that doesn't invoke a callback and claim one of its numbers.
* To avoid fully processing the callback, an id of `Long.MinValue` is used to short circuit the routine.
* @param number the number to be drawn from a `NumberPool`
* @param poolName the `NumberPool` from which the `number` is to be drawn
* @see `UniqueNumberSystem.RegistrationProcess(Option[GUIDRequest], Int, Int)`
*/
private def NoCallbackGetSpecificNumber(number: Int, poolName: String): Unit = {
poolActors(poolName) ! NumberPoolActor.GetSpecificNumber(number, Some(Long.MinValue))
}
}
object UniqueNumberSystem {
/**
* Persistent record of the important information between the time fo request and the time of reply.
* @param target the object
* @param targetPool the name of the `NumberPool` being used
* @param replyTo the callback `ActorRef`
*/
private final case class GUIDRequest(target: IdentifiableEntity, targetPool: String, replyTo: ActorRef)
/**
* Transform `NumberPool`s into `NumberPoolActor`s and pair them with their name.
* @param poolSource where the raw `NumberPools` are located
* @param context used to create the `NumberPoolActor` instances
* @return a `Map` of the pool names to the `ActorRef` created from the `NumberPool`
*/
def AllocateNumberPoolActors(poolSource: NumberPoolHub)(implicit context: ActorContext): Map[String, ActorRef] = {
poolSource.Pools
.map({
case ((pname, pool)) =>
pname -> context.actorOf(Props(classOf[NumberPoolActor], pool), pname)
})
.toMap
}
}

View file

@ -1,23 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.guid.actor
import akka.actor.ActorRef
import net.psforever.objects.entity.IdentifiableEntity
/**
* A message for accepting object-number unregistration requests.
* When given to a number pool (`NumberPoolAccessorActor`), that `Actor` assumes itself to have the object.
* When given to a hub object (`NumberPoolHubActor`), it will attempt to determine which pool currently has the object.<br>
* <br>
* The callback is actually an `ActorRef` to which a `RegisterSuccess` message or a `RegisterFailure` message is sent.
* This is as opposed to what a "callback" is normally - a function.
* @param obj the mandatory object
* @param callback the optional custom callback for the messages from the success or failure conditions
*/
final case class Unregister(obj: IdentifiableEntity, callback: Option[ActorRef] = None)
object Unregister {
def apply(obj: IdentifiableEntity, callback: ActorRef): Unregister = {
Unregister(obj, Some(callback))
}
}

View file

@ -0,0 +1,19 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.guid.key
/**
* The availability of individual global unique identifier (GUID) keys is maintained by the given policy.
*/
sealed trait AvailabilityPolicy
object AvailabilityPolicy {
/**An `Available` key is ready and waiting to be `Leased` for use. */
case object Available extends AvailabilityPolicy
/** A `Leased` key has been issued and is currently being used for some purpose.*/
case object Leased extends AvailabilityPolicy
/** A `Dangling` key ia a unique sort of key that has been `Leased` but has not yet been applied for any specific purpose.
* As a policy, it should be used as a status to check but should not be designated on any key. */
case object Dangling extends AvailabilityPolicy
}

View file

@ -2,7 +2,6 @@
package net.psforever.objects.guid.key package net.psforever.objects.guid.key
import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.guid.AvailabilityPolicy
/** /**
* The only indirect public access a queued number monitor object (`Key`) is allowed. * The only indirect public access a queued number monitor object (`Key`) is allowed.
@ -12,7 +11,7 @@ import net.psforever.objects.guid.AvailabilityPolicy
class LoanedKey(private val guid: Int, private val key: Monitor) { class LoanedKey(private val guid: Int, private val key: Monitor) {
def GUID: Int = guid def GUID: Int = guid
def Policy: AvailabilityPolicy.Value = key.policy def Policy: AvailabilityPolicy = key.policy
def Object: Option[IdentifiableEntity] = key.obj def Object: Option[IdentifiableEntity] = key.obj
@ -29,9 +28,7 @@ class LoanedKey(private val guid: Int, private val key: Monitor) {
* @return `true`, if the assignment worked; `false`, otherwise * @return `true`, if the assignment worked; `false`, otherwise
*/ */
def Object_=(obj: Option[IdentifiableEntity]): Option[IdentifiableEntity] = { def Object_=(obj: Option[IdentifiableEntity]): Option[IdentifiableEntity] = {
if ( if (key.policy == AvailabilityPolicy.Leased) {
key.policy == AvailabilityPolicy.Leased || (key.policy == AvailabilityPolicy.Restricted && key.obj.isEmpty)
) {
if (key.obj.isDefined) { if (key.obj.isDefined) {
key.obj.get.Invalidate() key.obj.get.Invalidate()
key.obj = None key.obj = None

View file

@ -2,10 +2,9 @@
package net.psforever.objects.guid.key package net.psforever.objects.guid.key
import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.guid.AvailabilityPolicy
trait Monitor { trait Monitor {
var policy: AvailabilityPolicy.Value var policy: AvailabilityPolicy
var obj: Option[IdentifiableEntity] var obj: Option[IdentifiableEntity]
} }

View file

@ -1,8 +1,6 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.guid.key package net.psforever.objects.guid.key
import net.psforever.objects.guid.AvailabilityPolicy
/** /**
* An unmodifiable reference to an active number monitor object (`Key`). * An unmodifiable reference to an active number monitor object (`Key`).
* @param guid the number (globally unique identifier) * @param guid the number (globally unique identifier)
@ -11,7 +9,7 @@ import net.psforever.objects.guid.AvailabilityPolicy
final class SecureKey(private val guid: Int, private val key: Monitor) { final class SecureKey(private val guid: Int, private val key: Monitor) {
def GUID: Int = guid def GUID: Int = guid
def Policy: AvailabilityPolicy.Value = key.policy def Policy: AvailabilityPolicy = key.policy
import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.entity.IdentifiableEntity
def Object: Option[IdentifiableEntity] = key.obj def Object: Option[IdentifiableEntity] = key.obj

View file

@ -6,8 +6,13 @@ import net.psforever.objects.guid.selector.{NumberSelector, SpecificSelector}
import scala.collection.mutable import scala.collection.mutable
import scala.util.{Failure, Success, Try} import scala.util.{Failure, Success, Try}
class GenericPool(private val hub: mutable.LongMap[String], private val max: Int) extends NumberPool { class GenericPool(
val numbers: mutable.ListBuffer[Int] = mutable.ListBuffer[Int]() private val hub: mutable.LongMap[String],
private val max: Int,
private val poolName: String,
private val selectionFunc: (List[Long], Int) => Int
) extends NumberPool {
private val numbers: mutable.ListBuffer[Int] = mutable.ListBuffer[Int]()
private val selector: SpecificSelector = new SpecificSelector private val selector: SpecificSelector = new SpecificSelector
selector.SelectionIndex = -1 selector.SelectionIndex = -1
@ -17,22 +22,26 @@ class GenericPool(private val hub: mutable.LongMap[String], private val max: Int
def Selector: NumberSelector = selector def Selector: NumberSelector = selector
def Selector_=(slctr: NumberSelector): Unit = {} //intentionally blank def Selector_=(slctr: NumberSelector): Unit = { /* intentionally blank */ }
def Get(): Try[Int] = { def Get(): Try[Int] = {
val specific = selector.SelectionIndex val specific = selector.SelectionIndex
selector.SelectionIndex = -1 //clear selector.SelectionIndex = -1 //clear
if (specific == -1) { if (specific == -1) {
val number = GenericPool.rand(hub.keys.toList, max) val number = selectionFunc(hub.keys.toList, max)
hub += number.toLong -> "generic" if (number > -1) {
numbers += number hub += number.toLong -> poolName
Success(number) numbers += number
Success(number)
} else {
Failure(new Exception("no numbers available in this pool"))
}
} else if (hub.get(specific).isEmpty) { } else if (hub.get(specific).isEmpty) {
hub += specific.toLong -> "generic" hub += specific.toLong -> poolName
numbers += specific numbers += specific
Success(specific) Success(specific)
} else { } else {
Failure(new Exception("selector was not initialized properly, or no numbers available in the pool")) Failure(new Exception("selector may not have been initialized properly"))
} }
} }
@ -49,6 +58,43 @@ class GenericPool(private val hub: mutable.LongMap[String], private val max: Int
} }
object GenericPool { object GenericPool {
/**
* Overloaded constructor that assigns a "numerical first discovery" function for number selection.
* @param hub na
* @param max na
* @param poolName na
* @return a `GenericPool` entity
*/
def apply(
hub: mutable.LongMap[String],
max: Int,
poolName: String
): GenericPool =
new GenericPool(hub, max, poolName, GenericPool.first)
/**
* Get some number that is not accounted for in any other fixed pool, making it available in this generic one.<br>
* <br>
* Returns the first number that is detected as available between two sorted numbers.
* @param list all of the non-repeating numbers to be compared
* @param domainSize how many numbers can be supported
* @return the next available number, or -1
*/
def first(list: List[Long], domainSize: Int): Int = {
if (list.size < domainSize) {
val sortedList: List[Long] = 0L +: list.sorted :+ domainSize
var index: Int = 0
val listLen = sortedList.length - 1
while(index < listLen && index < domainSize) {
val curr = sortedList(index + 1) - sortedList(index)
if (curr > 1) {
return sortedList(index).toInt + 1
}
index += 1
}
}
-1
}
/** /**
* Get some number that is not accounted for in any other fixed pool, making it available in this generic one.<br> * Get some number that is not accounted for in any other fixed pool, making it available in this generic one.<br>
@ -63,7 +109,7 @@ object GenericPool {
* @param domainSize how many numbers can be supported * @param domainSize how many numbers can be supported
* @return midpoint of the largest distance between any two of the existing numbers, or -1 * @return midpoint of the largest distance between any two of the existing numbers, or -1
*/ */
private def rand(list: List[Long], domainSize: Int): Int = { def rand(list: List[Long], domainSize: Int): Int = {
if (list.size < domainSize) { if (list.size < domainSize) {
//get a list of all assigned numbers with an appended min and max //get a list of all assigned numbers with an appended min and max
val sortedList: List[Long] = -1L +: list.sorted :+ domainSize.toLong val sortedList: List[Long] = -1L +: list.sorted :+ domainSize.toLong
@ -78,7 +124,8 @@ object GenericPool {
} }
} }
//find half of the distance between the two numbers with the greatest delta value //find half of the distance between the two numbers with the greatest delta value
if (maxDelta > 1) { ((sortedList(maxDeltaIndex + 1) + sortedList(maxDeltaIndex)) / 2f).toInt } if (maxDelta == 2) { sortedList(maxDeltaIndex).toInt + 1 }
else if (maxDelta > 1) { ((sortedList(maxDeltaIndex + 1) + sortedList(maxDeltaIndex)) / 2f).toInt }
else { -1 } else { -1 }
} else { } else {
-1 -1

View file

@ -2,11 +2,10 @@
package net.psforever.objects.guid.source package net.psforever.objects.guid.source
import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.guid.AvailabilityPolicy import net.psforever.objects.guid.key.{AvailabilityPolicy, Monitor}
import net.psforever.objects.guid.key.Monitor
private class Key extends Monitor { private class Key extends Monitor {
var policy: AvailabilityPolicy.Value = AvailabilityPolicy.Available var policy: AvailabilityPolicy = AvailabilityPolicy.Available
var obj: Option[IdentifiableEntity] = None var obj: Option[IdentifiableEntity] = None
} }

View file

@ -2,8 +2,7 @@
package net.psforever.objects.guid.source package net.psforever.objects.guid.source
import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.guid.key.{LoanedKey, SecureKey} import net.psforever.objects.guid.key.{AvailabilityPolicy, LoanedKey, SecureKey}
import net.psforever.objects.guid.AvailabilityPolicy
/** /**
* A `NumberSource` is considered a master "pool" of numbers from which all numbers are available to be drawn. * A `NumberSource` is considered a master "pool" of numbers from which all numbers are available to be drawn.
@ -18,13 +17,14 @@ class MaxNumberSource(val max: Int) extends NumberSource {
} }
private val ary: Array[Key] = Array.ofDim[Key](max + 1) private val ary: Array[Key] = Array.ofDim[Key](max + 1)
(0 to max).foreach(x => { ary(x) = new Key }) (0 to max).foreach(x => { ary(x) = new Key })
private var allowRestrictions: Boolean = true
def size: Int = ary.length def size: Int = ary.length
def countAvailable: Int = ary.count(key => key.policy == AvailabilityPolicy.Available) def countAvailable: Int = ary.count { _.policy == AvailabilityPolicy.Available }
def countUsed: Int = ary.count(_.policy != AvailabilityPolicy.Available) def countUsed: Int = ary.count { _.policy == AvailabilityPolicy.Leased }
def countDangling: Int = ary.count { key => key.policy == AvailabilityPolicy.Leased && key.obj.isEmpty }
def test(number: Int): Boolean = -1 < number && number < size def test(number: Int): Boolean = -1 < number && number < size
@ -78,39 +78,14 @@ class MaxNumberSource(val max: Int) extends NumberSource {
out out
} }
/**
* Produce a modifiable wrapper for the `Monitor` for this number, only if the number has not been used.
* This wrapped `Monitor` can only be assigned once and the number may not be `returnNumber`ed to this source.
* @param number the number
* @return the wrapped `Monitor`
* @throws ArrayIndexOutOfBoundsException if the requested number is above or below the range
*/
def restrictNumber(number: Int): Option[LoanedKey] = {
ary.lift(number) match {
case Some(key: Key) if allowRestrictions && key.policy != AvailabilityPolicy.Restricted =>
key.policy = AvailabilityPolicy.Restricted
Some(new LoanedKey(number, key))
case _ =>
None
}
}
def finalizeRestrictions: List[Int] = {
allowRestrictions = false
ary.zipWithIndex.filter(entry => entry._1.policy == AvailabilityPolicy.Restricted).map(entry => entry._2).toList
}
def clear(): List[IdentifiableEntity] = { def clear(): List[IdentifiableEntity] = {
val leased = ary.filter(_.policy != AvailabilityPolicy.Available) ary.foreach { _.policy = AvailabilityPolicy.Available }
leased collect { case key if key.obj.isEmpty => ary.collect {
key.policy = AvailabilityPolicy.Available case key if key.obj.nonEmpty =>
} val obj = key.obj.get
leased.toList collect { case key if key.obj.nonEmpty => key.obj = None
key.policy = AvailabilityPolicy.Available obj
val out = key.obj.get }.toList
key.obj = None
out
}
} }
} }

View file

@ -2,7 +2,7 @@
package net.psforever.objects.guid.source package net.psforever.objects.guid.source
import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.guid.key.{LoanedKey, SecureKey} import net.psforever.objects.guid.key.{AvailabilityPolicy, LoanedKey, SecureKey}
/** /**
* A `NumberSource` is considered a master "pool" of numbers from which all numbers are available to be drawn. * A `NumberSource` is considered a master "pool" of numbers from which all numbers are available to be drawn.
@ -30,6 +30,20 @@ trait NumberSource {
*/ */
def size: Int def size: Int
/**
* Select the type of count desired based on the allocation policy of the key.
* @param policy the allocation policy
* @return the number of keys belonging to this policy
*/
def count(policy: AvailabilityPolicy): Int = {
policy match {
case AvailabilityPolicy.Available => countAvailable
case AvailabilityPolicy.Leased => countUsed
case AvailabilityPolicy.Dangling => countDangling
}
}
/** /**
* The count of numbers that can still be drawn. * The count of numbers that can still be drawn.
* @return the count * @return the count
@ -42,6 +56,13 @@ trait NumberSource {
*/ */
def countUsed: Int def countUsed: Int
/**
* The count of numbers that can not be drawn but have not yet been assigned to an entity.
* Could only ever be a non-zero count if the number of used keys is a non-zero count.
* @return the count
*/
def countDangling: Int
/** /**
* Is this number a member of this number source? * Is this number a member of this number source?
* @param number the number * @param number the number
@ -97,26 +118,9 @@ trait NumberSource {
*/ */
def returnNumber(number: Int): Option[IdentifiableEntity] def returnNumber(number: Int): Option[IdentifiableEntity]
/**
* Produce a modifiable wrapper for the `Monitor` for this number, only if the number has not been used.
* This wrapped `Monitor` can only be assigned once and the number may not be `returnNumber`ed to this source.
* @param number the number
* @return the wrapped `Monitor`
*/
def restrictNumber(number: Int): Option[LoanedKey]
/**
* Numbers from this source may not longer be marked as `Restricted`.
* @return the `List` of all numbers that have been restricted
*/
def finalizeRestrictions: List[Int]
import net.psforever.objects.entity.IdentifiableEntity
/** /**
* Reset all number `Monitor`s so that their underlying number is not longer treated as assigned. * Reset all number `Monitor`s so that their underlying number is not longer treated as assigned.
* Perform some level of housecleaning to ensure that all dependencies are resolved in some manner. * Perform some level of housecleaning to ensure that all dependencies are resolved in some manner.
* This is the only way to free `Monitors` that are marked as `Restricted`.
* @return a `List` of assignments maintained by all the currently-used number `Monitors` * @return a `List` of assignments maintained by all the currently-used number `Monitors`
*/ */
def clear(): List[IdentifiableEntity] def clear(): List[IdentifiableEntity]

View file

@ -2,8 +2,7 @@
package net.psforever.objects.guid.source package net.psforever.objects.guid.source
import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.guid.AvailabilityPolicy import net.psforever.objects.guid.key.{AvailabilityPolicy, LoanedKey, SecureKey}
import net.psforever.objects.guid.key.{LoanedKey, SecureKey}
/** /**
* A `NumberSource` is considered a master "pool" of numbers from which all numbers are available to be drawn. * A `NumberSource` is considered a master "pool" of numbers from which all numbers are available to be drawn.
@ -27,9 +26,11 @@ class SpecificNumberSource(values: Iterable[Int]) extends NumberSource {
def size : Int = ary.size def size : Int = ary.size
def countAvailable : Int = ary.values.count(key => key.policy == AvailabilityPolicy.Available) def countAvailable : Int = ary.values.count { _.policy == AvailabilityPolicy.Available }
def countUsed : Int = ary.values.count(_.policy != AvailabilityPolicy.Available) def countUsed : Int = ary.values.count { _.policy == AvailabilityPolicy.Leased }
def countDangling: Int = ary.values.count { key => key.policy == AvailabilityPolicy.Leased && key.obj.isEmpty }
def test(number : Int) : Boolean = ary.get(number).nonEmpty def test(number : Int) : Boolean = ary.get(number).nonEmpty
@ -74,34 +75,14 @@ class SpecificNumberSource(values: Iterable[Int]) extends NumberSource {
} }
} }
def restrictNumber(number : Int) : Option[LoanedKey] = {
ary.get(number) match {
case Some(key) if key.policy != AvailabilityPolicy.Restricted =>
key.policy = AvailabilityPolicy.Restricted
Some(new LoanedKey(number, key))
case _ =>
None
}
}
def finalizeRestrictions : List[Int] = {
ary
.filter { case (_, key : Key) => key.policy == AvailabilityPolicy.Restricted }
.keys
.toList
}
def clear(): List[IdentifiableEntity] = { def clear(): List[IdentifiableEntity] = {
val leased = ary.values.filter(_.policy != AvailabilityPolicy.Available) ary.values.foreach { _.policy = AvailabilityPolicy.Available }
leased collect { case key if key.obj.isEmpty => ary.values.collect {
key.policy = AvailabilityPolicy.Available case key if key.obj.nonEmpty =>
} val obj = key.obj.get
leased.toList collect { case key if key.obj.nonEmpty => key.obj = None
key.policy = AvailabilityPolicy.Available obj
val out = key.obj.get }.toList
key.obj = None
out
}
} }
} }

View file

@ -1,5 +1,5 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.guid.actor package net.psforever.objects.guid.uns
import akka.actor.Actor import akka.actor.Actor
import net.psforever.objects.guid.pool.NumberPool import net.psforever.objects.guid.pool.NumberPool
@ -21,27 +21,27 @@ class NumberPoolActor(pool: NumberPool) extends Actor {
private[this] val log = org.log4s.getLogger private[this] val log = org.log4s.getLogger
def receive: Receive = { def receive: Receive = {
case NumberPoolActor.GetAnyNumber(id) => case NumberPoolActor.GetAnyNumber() =>
sender() ! (pool.Get() match { sender() ! (pool.Get() match {
case Success(value) => case Success(value) =>
NumberPoolActor.GiveNumber(value, id) NumberPoolActor.GiveNumber(value)
case Failure(ex) => ; case Failure(ex) =>
NumberPoolActor.NoNumber(ex, id) NumberPoolActor.NoNumber(ex)
}) })
case NumberPoolActor.GetSpecificNumber(number, id) => case NumberPoolActor.GetSpecificNumber(number) =>
sender() ! (NumberPoolActor.GetSpecificNumber(pool, number) match { sender() ! (NumberPoolActor.GetSpecificNumber(pool, number) match {
case Success(value) => case Success(value) =>
NumberPoolActor.GiveNumber(value, id) NumberPoolActor.GiveNumber(value)
case Failure(ex) => ; case Failure(ex) => ;
NumberPoolActor.NoNumber(ex, id) NumberPoolActor.NoNumber(ex)
}) })
case NumberPoolActor.ReturnNumber(number, id) => case NumberPoolActor.ReturnNumber(number) =>
val result = pool.Return(number) val result = pool.Return(number)
val ex: Option[Throwable] = if (!result) { Some(new Exception("number was not returned")) } val ex: Option[Throwable] = if (!result) { Some(new Exception("number was not returned")) }
else { None } else { None }
sender() ! NumberPoolActor.ReturnNumberResult(number, ex, id) sender() ! NumberPoolActor.ReturnNumberResult(number, ex)
case msg => case msg =>
log.warn(s"Received an unexpected message - ${msg.toString}") log.warn(s"Received an unexpected message - ${msg.toString}")
@ -52,36 +52,36 @@ object NumberPoolActor {
/** /**
* A message to invoke the current `NumberSelector`'s functionality. * A message to invoke the current `NumberSelector`'s functionality.
* @param id a potential identifier to associate this request
*/ */
final case class GetAnyNumber(id: Option[Any] = None) final case class GetAnyNumber()
/** /**
* A message to invoke a `SpecificSelector` to acquire the specific `number`, if it is available in this pool. * A message to invoke a `SpecificSelector` to acquire the specific `number`, if it is available in this pool.
* @param number the pre-selected number * @param number the pre-selected number
* @param id a potential identifier to associate this request
*/ */
final case class GetSpecificNumber(number: Int, id: Option[Any] = None) final case class GetSpecificNumber(number: Int)
/** /**
* A message to distribute the `number` that was drawn. * A message to distribute the `number` that was drawn.
* @param number the pre-selected number * @param number the pre-selected number
* @param id a potential identifier to associate this request
*/ */
final case class GiveNumber(number: Int, id: Option[Any] = None) final case class GiveNumber(number: Int)
final case class NoNumber(ex: Throwable, id: Option[Any] = None) final case class NoNumber(ex: Throwable)
/** /**
* A message to invoke the `returnNumber` functionality of the current `NumberSelector`. * A message to invoke the `returnNumber` functionality of the current `NumberSelector`.
* @param number the number * @param number the number
*/ */
final case class ReturnNumber(number: Int, id: Option[Any] = None) final case class ReturnNumber(number: Int)
final case class ReturnNumberResult(number: Int, ex: Option[Throwable], id: Option[Any] = None) final case class ReturnNumberResult(number: Int, ex: Option[Throwable])
/** /**
* Use the `SpecificSelector` on this pool to extract a specific object from the pool, if it is included and available. * Use the `SpecificSelector` on this pool to extract a specific object from the pool, if it is included and available.
* Getting a specific number involves creating the appropriate selector and swapping with the current selector.
* This process may involve re-formatting the underlying number pool array once for each selector.
* @see `NumberSelector.Format`
* @param pool the `NumberPool` to draw from * @param pool the `NumberPool` to draw from
* @param number the number requested * @param number the number requested
* @return the number requested, or an error * @return the number requested, or an error

View file

@ -0,0 +1,14 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.guid.uns
import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.guid.NumberPoolHub
final case class RegisteredEntity(
obj: IdentifiableEntity,
pool_name: String,
guid_system: NumberPoolHub,
number: Int
)
final case class AlreadyRegisteredEntity(msg: RegisteredEntity)

View file

@ -0,0 +1,14 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.guid.uns
import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.guid.NumberPoolHub
final case class UnregisteredEntity(
obj: IdentifiableEntity,
pool_name: String,
guid_system: NumberPoolHub,
number: Int
)
final case class AlreadyUnregisteredEntity(msg: UnregisteredEntity)

View file

@ -0,0 +1,17 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.locker
import net.psforever.objects.definition.EquipmentDefinition
import net.psforever.objects.definition.converter.LockerContainerConverter
import net.psforever.objects.equipment.EquipmentSize
class LockerContainerDefinition extends EquipmentDefinition(objectId = 456) {
Name = "locker_container"
Size = EquipmentSize.Inventory
Packet = LockerContainerDefinition.converter
registerAs = "lockers"
}
object LockerContainerDefinition {
val converter = new LockerContainerConverter()
}

View file

@ -4,7 +4,7 @@ package net.psforever.objects.serverobject.pad
import akka.actor.{Cancellable, Props} import akka.actor.{Cancellable, Props}
import net.psforever.objects.avatar.SpecialCarry import net.psforever.objects.avatar.SpecialCarry
import net.psforever.objects.entity.WorldEntity import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.guid.GUIDTask.UnregisterVehicle import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.pad.process.{VehicleSpawnControlBase, VehicleSpawnControlConcealPlayer} import net.psforever.objects.serverobject.pad.process.{VehicleSpawnControlBase, VehicleSpawnControlConcealPlayer}
import net.psforever.objects.zones.{Zone, ZoneAware, Zoning} import net.psforever.objects.zones.{Zone, ZoneAware, Zoning}
@ -516,7 +516,7 @@ object VehicleSpawnControl {
if (zone.Vehicles.contains(vehicle)) { //already added to zone if (zone.Vehicles.contains(vehicle)) { //already added to zone
vehicle.Actor ! Vehicle.Deconstruct(Some(0.seconds)) vehicle.Actor ! Vehicle.Deconstruct(Some(0.seconds))
} else { //just registered to zone } else { //just registered to zone
zone.tasks ! UnregisterVehicle(vehicle)(zone.GUID) TaskWorkflow.execute(GUIDTask.unregisterVehicle(zone.GUID, vehicle))
} }
} }
} }

View file

@ -2,7 +2,7 @@
package net.psforever.objects.serverobject.shuttle package net.psforever.objects.serverobject.shuttle
import akka.actor.{Actor, ActorRef} import akka.actor.{Actor, ActorRef}
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} import net.psforever.objects.guid._
import net.psforever.objects.{Player, Vehicle} import net.psforever.objects.{Player, Vehicle}
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.doors.Door
@ -14,7 +14,7 @@ import net.psforever.services.hart.{HartTimer, HartTimerActions}
import net.psforever.services.{Service, ServiceManager} import net.psforever.services.{Service, ServiceManager}
import net.psforever.types.ChatMessageType import net.psforever.types.ChatMessageType
import scala.util.Success import scala.concurrent.Future
/** /**
* An `Actor` that handles messages being dispatched to a specific `OrbitalShuttlePad`.<br> * An `Actor` that handles messages being dispatched to a specific `OrbitalShuttlePad`.<br>
@ -114,7 +114,7 @@ class OrbitalShuttlePadControl(pad: OrbitalShuttlePad) extends Actor {
newShuttle.Position = position + Vector3(0, -8.25f, 0).Rz(pad.Orientation.z) //magic offset number newShuttle.Position = position + Vector3(0, -8.25f, 0).Rz(pad.Orientation.z) //magic offset number
newShuttle.Orientation = pad.Orientation newShuttle.Orientation = pad.Orientation
newShuttle.Faction = pad.Faction newShuttle.Faction = pad.Faction
zone.tasks ! OrbitalShuttlePadControl.registerShuttle(zone, newShuttle, self) TaskWorkflow.execute(OrbitalShuttlePadControl.registerShuttle(zone, newShuttle, self))
context.become(shuttleTime) context.become(shuttleTime)
case _ => ; case _ => ;
@ -127,33 +127,23 @@ object OrbitalShuttlePadControl {
* @param zone the zone the shuttle and the pad will occupy * @param zone the zone the shuttle and the pad will occupy
* @param shuttle the vehicle that will be the shuttle * @param shuttle the vehicle that will be the shuttle
* @param ref a reference to the control agency for the orbital shuttle pad * @param ref a reference to the control agency for the orbital shuttle pad
* @return a `TaskResolver.GiveTask` object * @return a `TaskBundle` object
*/ */
def registerShuttle(zone: Zone, shuttle: Vehicle, ref: ActorRef): TaskResolver.GiveTask = { def registerShuttle(zone: Zone, shuttle: Vehicle, ref: ActorRef): TaskBundle = {
TaskResolver.GiveTask( import scala.concurrent.ExecutionContext.Implicits.global
new Task() { TaskBundle(
new StraightforwardTask() {
private val localZone = zone private val localZone = zone
private val localShuttle = shuttle private val localShuttle = shuttle
private val localSelf = ref private val localSelf = ref
override def Description: String = s"register an orbital shuttle" override def description(): String = s"register an orbital shuttle"
override def isComplete : Task.Resolution.Value = if (localShuttle.HasGUID) { def action() : Future[Any] = {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
def Execute(resolver : ActorRef) : Unit = {
localZone.Transport.tell(Zone.Vehicle.Spawn(localShuttle), localSelf) localZone.Transport.tell(Zone.Vehicle.Spawn(localShuttle), localSelf)
resolver ! Success(true) Future(this)
} }
}, GUIDTask.registerVehicle(zone.GUID, shuttle)
override def onFailure(ex : Throwable) : Unit = {
super.onFailure(ex)
localSelf ! Zone.Vehicle.CanNotSpawn(localZone, localShuttle, ex.getMessage)
}
}, List(GUIDTask.RegisterVehicle(shuttle)(zone.GUID))
) )
} }

View file

@ -13,6 +13,7 @@ import net.psforever.objects.serverobject.structures.AmenityDefinition
abstract class TerminalDefinition(objectId: Int) extends AmenityDefinition(objectId) { abstract class TerminalDefinition(objectId: Int) extends AmenityDefinition(objectId) {
Name = "terminal" Name = "terminal"
Packet = new TerminalConverter Packet = new TerminalConverter
registerAs = "terminals"
/** /**
* The unimplemented functionality for the entry function of form of activity * The unimplemented functionality for the entry function of form of activity

View file

@ -12,6 +12,7 @@ import net.psforever.objects.serverobject.structures.{Amenity, AmenityDefinition
*/ */
class SpawnTubeDefinition(object_id: Int) extends AmenityDefinition(object_id) with SpawnPointDefinition { class SpawnTubeDefinition(object_id: Int) extends AmenityDefinition(object_id) with SpawnPointDefinition {
Packet = new SpawnTubeConverter Packet = new SpawnTubeConverter
registerAs = "terminals"
} }
object SpawnTubeDefinition { object SpawnTubeDefinition {

View file

@ -228,6 +228,7 @@ object Utility {
extends AmenityDefinition(DeployedItem.router_telepad_deployable.id) extends AmenityDefinition(DeployedItem.router_telepad_deployable.id)
with BaseDeployableDefinition { with BaseDeployableDefinition {
Packet = new SmallDeployableConverter Packet = new SmallDeployableConverter
registerAs = "terminals"
def Item: DeployedItem.Value = DeployedItem.router_telepad_deployable def Item: DeployedItem.Value = DeployedItem.router_telepad_deployable
} }

View file

@ -7,7 +7,7 @@ import net.psforever.objects._
import net.psforever.objects.ballistics.VehicleSource import net.psforever.objects.ballistics.VehicleSource
import net.psforever.objects.entity.WorldEntity import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.equipment.{Equipment, EquipmentSlot, JammableMountedWeapons} import net.psforever.objects.equipment.{Equipment, EquipmentSlot, JammableMountedWeapons}
import net.psforever.objects.guid.GUIDTask import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.inventory.{GridInventory, InventoryItem} import net.psforever.objects.inventory.{GridInventory, InventoryItem}
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
@ -407,7 +407,7 @@ class VehicleControl(vehicle: Vehicle)
CancelJammeredSound(vehicle) CancelJammeredSound(vehicle)
CancelJammeredStatus(vehicle) CancelJammeredStatus(vehicle)
//unregister //unregister
zone.tasks ! GUIDTask.UnregisterVehicle(vehicle)(zone.GUID) TaskWorkflow.execute(GUIDTask.unregisterVehicle(zone.GUID, vehicle))
//banished to the shadow realm //banished to the shadow realm
vehicle.Position = Vector3.Zero vehicle.Position = Vector3.Zero
//queue final deletion //queue final deletion

View file

@ -2,16 +2,12 @@
package net.psforever.objects.zones package net.psforever.objects.zones
import akka.actor.{ActorContext, ActorRef, Props} import akka.actor.{ActorContext, ActorRef, Props}
import akka.routing.RandomPool
import net.psforever.objects.{PlanetSideGameObject, _} import net.psforever.objects.{PlanetSideGameObject, _}
import net.psforever.objects.ballistics.{Projectile, SourceEntry} import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.ce.Deployable import net.psforever.objects.ce.Deployable
import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.objects.guid.{NumberPoolHub, TaskResolver} import net.psforever.objects.guid.{NumberPoolHub, UniqueNumberOps, UniqueNumberSetup}
import net.psforever.objects.guid.actor.UniqueNumberSystem
import net.psforever.objects.guid.key.LoanedKey import net.psforever.objects.guid.key.LoanedKey
import net.psforever.objects.guid.selector.RandomSelector
import net.psforever.objects.guid.source.MaxNumberSource import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.inventory.Container import net.psforever.objects.inventory.Container
import net.psforever.objects.serverobject.painbox.{Painbox, PainboxDefinition} import net.psforever.objects.serverobject.painbox.{Painbox, PainboxDefinition}
@ -39,6 +35,7 @@ import net.psforever.actors.session.AvatarActor
import net.psforever.actors.zone.ZoneActor import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.avatar.Avatar import net.psforever.objects.avatar.Avatar
import net.psforever.objects.geometry.d3.VolumetricGeometry import net.psforever.objects.geometry.d3.VolumetricGeometry
import net.psforever.objects.guid.pool.NumberPool
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.doors.Door
@ -79,14 +76,16 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
/** Governs general synchronized external requests. */ /** Governs general synchronized external requests. */
var actor: typed.ActorRef[ZoneActor.Command] = _ var actor: typed.ActorRef[ZoneActor.Command] = _
/** Actor that handles SOI related functionality, for example if a player is in a SOI */ /** Actor that handles SOI related functionality, for example if a player is in an SOI */
private var soi = Default.Actor private var soi = Default.Actor
/** Used by the globally unique identifier system to coordinate requests. */
private var accessor: ActorRef = ActorRef.noSender
/** The basic support structure for the globally unique number system used by this `Zone`. */ /** The basic support structure for the globally unique number system used by this `Zone`. */
private var guid: NumberPoolHub = new NumberPoolHub(new MaxNumberSource(65536)) private var guid: NumberPoolHub = new NumberPoolHub(new MaxNumberSource(65536))
/** The core of the unique number system, to which requests may be submitted.
* @see `UniqueNumberSys`
* @see `Zone.Init(ActorContext)`
*/
private[zones] var unops: UniqueNumberOps = _
/** The blockmap structure for partitioning entities and environmental aspects of the zone. /** The blockmap structure for partitioning entities and environmental aspects of the zone.
* For a standard 8912`^`2 map, each of the four hundred formal map grids is 445.6m long and wide. * For a standard 8912`^`2 map, each of the four hundred formal map grids is 445.6m long and wide.
@ -105,8 +104,6 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
/** Used by the `Zone` to coordinate `Equipment` dropping and collection requests. */ /** Used by the `Zone` to coordinate `Equipment` dropping and collection requests. */
private var ground: ActorRef = Default.Actor private var ground: ActorRef = Default.Actor
private var taskResolver: ActorRef = Default.Actor
/** /**
*/ */
private val constructions: ListBuffer[Deployable] = ListBuffer() private val constructions: ListBuffer[Deployable] = ListBuffer()
@ -177,7 +174,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
* First, the `Actor`-driven aspect of the globally unique identifier system for this `Zone` is finalized. * First, the `Actor`-driven aspect of the globally unique identifier system for this `Zone` is finalized.
* Second, all supporting `Actor` agents are created, e.g., `ground`. * Second, all supporting `Actor` agents are created, e.g., `ground`.
* Third, the `ZoneMap` server objects are loaded and constructed within that aforementioned system. * Third, the `ZoneMap` server objects are loaded and constructed within that aforementioned system.
* To avoid being called more than once, there is a test whether the `accessor` for the globally unique identifier system has been changed.<br> * To avoid being called more than once, there is a test whether the globally unique identifier system has been changed.<br>
* <br> * <br>
* Execution of this operation should be fail-safe. * Execution of this operation should be fail-safe.
* The chances of failure should be mitigated or skipped. * The chances of failure should be mitigated or skipped.
@ -187,15 +184,9 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
* @param context a reference to an `ActorContext` necessary for `Props` * @param context a reference to an `ActorContext` necessary for `Props`
*/ */
def init(implicit context: ActorContext): Unit = { def init(implicit context: ActorContext): Unit = {
if (accessor == ActorRef.noSender) { if (unops == null) {
SetupNumberPools() SetupNumberPools()
taskResolver = CreateTaskResolvers(context) context.actorOf(Props(classOf[UniqueNumberSys], this, this.guid), s"zone-$id-uns")
accessor = context.actorOf(
RandomPool(25).props(
Props(classOf[UniqueNumberSystem], this.guid, UniqueNumberSystem.AllocateNumberPoolActors(this.guid))
),
s"zone-$id-uns"
)
ground = context.actorOf(Props(classOf[ZoneGroundActor], this, equipmentOnGround), s"zone-$id-ground") ground = context.actorOf(Props(classOf[ZoneGroundActor], this, equipmentOnGround), s"zone-$id-ground")
deployables = context.actorOf(Props(classOf[ZoneDeployableActor], this, constructions), s"zone-$id-deployables") deployables = context.actorOf(Props(classOf[ZoneDeployableActor], this, constructions), s"zone-$id-deployables")
transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"zone-$id-vehicles") transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"zone-$id-vehicles")
@ -334,26 +325,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
} }
} }
def SetupNumberPools(): Unit = { def SetupNumberPools(): Unit = { /* override to tailor to suit requirements of zone */ }
guid.AddPool("environment", (0 to 3000).toList) //TODO tailor to suit requirements of zone
//TODO unlump pools later; do not make any single pool too big
guid.AddPool("dynamic", (3001 to 10000).toList).Selector =
new RandomSelector //TODO all things will be registered here, for now
guid.AddPool("b", (10001 to 15000).toList).Selector = new RandomSelector
guid.AddPool("c", (15001 to 20000).toList).Selector = new RandomSelector
guid.AddPool("d", (20001 to 25000).toList).Selector = new RandomSelector
guid.AddPool("e", (25001 to 30000).toList).Selector = new RandomSelector
guid.AddPool("f", (30001 to 35000).toList).Selector = new RandomSelector
guid.AddPool("g", (35001 until 40100).toList).Selector = new RandomSelector
guid.AddPool("projectiles", (Projectile.baseUID until Projectile.rangeUID).toList)
guid.AddPool("locker-contents", (40150 until 40450).toList).Selector = new RandomSelector
//TODO disabled temporarily to lighten load times
//guid.AddPool("h", (40150 to 45000).toList).Selector = new RandomSelector
//guid.AddPool("i", (45001 to 50000).toList).Selector = new RandomSelector
//guid.AddPool("j", (50001 to 55000).toList).Selector = new RandomSelector
//guid.AddPool("k", (55001 to 60000).toList).Selector = new RandomSelector
//guid.AddPool("l", (60001 to 65535).toList).Selector = new RandomSelector
}
def findSpawns( def findSpawns(
faction: PlanetSideEmpire.Value, faction: PlanetSideEmpire.Value,
@ -436,14 +408,14 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
def Number: Int = zoneNumber def Number: Int = zoneNumber
/** /**
* The globally unique identifier system is synchronized via an `Actor` to ensure that concurrent requests do not clash. * The globally unique identifier system ensures that concurrent requests do not clash.
* A clash is merely when the same number is produced more than once by the same system due to concurrent requests. * A clash is merely when the same number is produced more than once by the same system due to concurrent requests.
* @return synchronized reference to the globally unique identifier system * @return reference to the globally unique identifier system
*/ */
def GUID: ActorRef = accessor def GUID: UniqueNumberOps = unops
/** /**
* Replace the current globally unique identifier system with a new one. * Replace the current globally unique identifier support structure with a new one.
* The replacement will not occur if the current system is populated or if its synchronized reference has been created. * The replacement will not occur if the current system is populated or if its synchronized reference has been created.
* The primary use of this function should be testing. * The primary use of this function should be testing.
* A warning will be issued. * A warning will be issued.
@ -475,12 +447,14 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
* @return `true`, if the new pool is created; * @return `true`, if the new pool is created;
* `false`, if the new pool can not be created because the system has already been started * `false`, if the new pool can not be created because the system has already been started
*/ */
def AddPool(name: String, pool: Seq[Int]): Boolean = { def AddPool(name: String, pool: Seq[Int]): Option[NumberPool] = {
if (accessor == Default.Actor || accessor == null) { if (unops == null) {
guid.AddPool(name, pool.toList) guid.AddPool(name, pool.toList) match {
true case _: Exception => None
case out => Some(out)
}
} else { } else {
false None
} }
} }
@ -489,13 +463,12 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
* Throws exceptions for specific reasons if the pool can not be removed before the system has been started. * Throws exceptions for specific reasons if the pool can not be removed before the system has been started.
* @see `NumberPoolHub.RemovePool` * @see `NumberPoolHub.RemovePool`
* @param name the name of the pool * @param name the name of the pool
* @return `true`, if the new pool is un-made; * @return `true`, if the pool is un-made;
* `false`, if the new pool can not be removed because the system has already been started * `false`, if the pool can not be removed (because the system has already been started?)
*/ */
def RemovePool(name: String): Boolean = { def RemovePool(name: String): Boolean = {
if (accessor == Default.Actor) { if (unops == null) {
guid.RemovePool(name) guid.RemovePool(name).nonEmpty
true
} else { } else {
false false
} }
@ -526,7 +499,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
/** /**
* Recover an object from the globally unique identifier system by the number that was assigned previously. * Recover an object from the globally unique identifier system by the number that was assigned previously.
* The object must be upcast into due to the differtence between the storage type and the return type. * The object must be upcast into due to the minor difference between the storage type and the return type.
* @param object_guid the globally unique identifier requested * @param object_guid the globally unique identifier requested
* @return the associated object, if it exists * @return the associated object, if it exists
* @see `NumberPoolHub(Int)` * @see `NumberPoolHub(Int)`
@ -606,7 +579,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
private def BuildSupportObjects(): Unit = { private def BuildSupportObjects(): Unit = {
//guard against errors here, but don't worry about specifics; let ZoneActor.ZoneSetupCheck complain about problems //guard against errors here, but don't worry about specifics; let ZoneActor.ZoneSetupCheck complain about problems
val other: ListBuffer[IdentifiableEntity] = new ListBuffer[IdentifiableEntity]() val other: ListBuffer[PlanetSideGameObject] = new ListBuffer[PlanetSideGameObject]()
//turret to weapon //turret to weapon
map.turretToWeapon.foreach({ map.turretToWeapon.foreach({
case (turret_guid, weapon_guid) => case (turret_guid, weapon_guid) =>
@ -634,7 +607,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
} }
}) })
//after all fixed GUID's are defined ... //after all fixed GUID's are defined ...
other.foreach(obj => guid.register(obj, "dynamic")) other.foreach(obj => guid.register(obj, obj.Definition.registerAs))
} }
private def MakeBuildings(implicit context: ActorContext): PairMap[Int, Building] = { private def MakeBuildings(implicit context: ActorContext): PairMap[Int, Building] = {
@ -836,11 +809,26 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
vehicleEvents = bus vehicleEvents = bus
VehicleEvents VehicleEvents
} }
}
def tasks: ActorRef = taskResolver /**
* A local class for spawning `Actor`s to manage the number pools for this zone,
protected def CreateTaskResolvers(context: ActorContext, numberCreated: Int = 20): ActorRef = { * create a number system operations class to access those pools within the context of registering and unregistering,
context.actorOf(RandomPool(numberCreated).props(Props[TaskResolver]()), s"zone-$id-taskResolver") * and assign that number pool operations class to the containing zone
* through specific scope access.
* @see `UniqueNumberOps`
* @see `UniqueNumberSetup`
* @see `UniqueNumberSetup.AllocateNumberPoolActors`
* @see `Zone.unops`
* @param zone the zone in which the operations class will be referenced
* @param guid the number pool management class
*/
private class UniqueNumberSys(zone: Zone, guid: NumberPoolHub)
extends UniqueNumberSetup(guid, UniqueNumberSetup.AllocateNumberPoolActors) {
override def init(): UniqueNumberOps = {
val unsys = super.init()
zone.unops = unsys // zone.unops is accessible from here by virtue of being 'private[zones]`
unsys
} }
} }

View file

@ -1,15 +1,15 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.services package net.psforever.services
import akka.actor.{ActorRef, Cancellable} import akka.actor.Cancellable
import net.psforever.objects.guid.TaskResolver import net.psforever.objects.guid.{StraightforwardTask, TaskBundle, TaskWorkflow}
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.objects.{Default, PlanetSideGameObject} import net.psforever.objects.{Default, PlanetSideGameObject}
import net.psforever.types.Vector3 import net.psforever.types.Vector3
import net.psforever.services.support.{SimilarityComparator, SupportActor, SupportActorCaseConversions} import net.psforever.services.support.{SimilarityComparator, SupportActor, SupportActorCaseConversions}
import scala.concurrent.Future
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.util.Success
/** /**
* The base class for a type of "destruction `Actor`" intended to be used for delaying object cleanup activity. * The base class for a type of "destruction `Actor`" intended to be used for delaying object cleanup activity.
@ -30,7 +30,7 @@ import scala.util.Success
* and finally unregistering it. * and finally unregistering it.
* Some types of object have (de-)implementation variations which should be made explicit through the overrides. * Some types of object have (de-)implementation variations which should be made explicit through the overrides.
*/ */
abstract class RemoverActor(val taskResolver: ActorRef) extends SupportActor[RemoverActor.Entry] { abstract class RemoverActor() extends SupportActor[RemoverActor.Entry] {
/** /**
* The timer that checks whether entries in the first pool are still eligible for that pool. * The timer that checks whether entries in the first pool are still eligible for that pool.
@ -249,30 +249,23 @@ abstract class RemoverActor(val taskResolver: ActorRef) extends SupportActor[Rem
def SecondJob(entry: RemoverActor.Entry): Unit = { def SecondJob(entry: RemoverActor.Entry): Unit = {
entry.obj.Position = Vector3.Zero //somewhere it will not disturb anything entry.obj.Position = Vector3.Zero //somewhere it will not disturb anything
taskResolver ! FinalTask(entry) TaskWorkflow.execute(FinalTask(entry))
} }
def FinalTask(entry: RemoverActor.Entry): TaskResolver.GiveTask = { def FinalTask(entry: RemoverActor.Entry): TaskBundle = {
import net.psforever.objects.guid.Task import scala.concurrent.ExecutionContext.Implicits.global
TaskResolver.GiveTask( TaskBundle(
new Task() { new StraightforwardTask() {
private val localEntry = entry // private val localEntry = entry
private val localAnnounce = self // private val localAnnounce = self
override def isComplete: Task.Resolution.Value = def action(): Future[Any] = {
if (!localEntry.obj.HasGUID) { Future(this)
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
def Execute(resolver: ActorRef): Unit = {
resolver ! Success(this)
} }
override def onFailure(ex: Throwable): Unit = { // override def onFailure(ex: Throwable): Unit = {
localAnnounce ! RemoverActor.FailureToWork(localEntry, ex) // localAnnounce ! RemoverActor.FailureToWork(localEntry, ex)
} // }
}, },
List(DeletionTask(entry)) List(DeletionTask(entry))
) )
@ -319,7 +312,7 @@ abstract class RemoverActor(val taskResolver: ActorRef) extends SupportActor[Rem
* @see `GUIDTask` * @see `GUIDTask`
* @param entry the entry * @param entry the entry
*/ */
def DeletionTask(entry: RemoverActor.Entry): TaskResolver.GiveTask def DeletionTask(entry: RemoverActor.Entry): TaskBundle
} }
object RemoverActor extends SupportActorCaseConversions { object RemoverActor extends SupportActorCaseConversions {

View file

@ -6,7 +6,7 @@ import akka.actor.{Actor, ActorRef, Cancellable, Props}
import scala.collection.mutable import scala.collection.mutable
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import net.psforever.objects.guid.GUIDTask import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.avatar.Avatar import net.psforever.objects.avatar.Avatar
import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.mount.Mountable
@ -389,7 +389,8 @@ class PersistenceMonitor(name: String, squadService: ActorRef) extends Actor {
} }
inZone.Population.tell(Zone.Population.Release(avatar), parent) inZone.Population.tell(Zone.Population.Release(avatar), parent)
inZone.AvatarEvents.tell(AvatarServiceMessage(inZone.id, AvatarAction.ObjectDelete(pguid, pguid)), parent) inZone.AvatarEvents.tell(AvatarServiceMessage(inZone.id, AvatarAction.ObjectDelete(pguid, pguid)), parent)
inZone.tasks.tell(GUIDTask.UnregisterPlayer(player)(inZone.GUID), parent) TaskWorkflow.execute(GUIDTask.unregisterPlayer(inZone.GUID, player))
//inZone.tasks.tell(GUIDTask.UnregisterPlayer(player)(inZone.GUID), parent)
AvatarLogout(avatar) AvatarLogout(avatar)
} }
@ -408,7 +409,8 @@ class PersistenceMonitor(name: String, squadService: ActorRef) extends Actor {
squadService.tell(Service.Leave(Some(avatar.id.toString)), context.parent) squadService.tell(Service.Leave(Some(avatar.id.toString)), context.parent)
Deployables.Disown(inZone, avatar, context.parent) Deployables.Disown(inZone, avatar, context.parent)
inZone.Population.tell(Zone.Population.Leave(avatar), context.parent) inZone.Population.tell(Zone.Population.Leave(avatar), context.parent)
inZone.tasks.tell(GUIDTask.UnregisterObjectTask(avatar.locker)(inZone.GUID), context.parent) TaskWorkflow.execute(GUIDTask.unregisterObject(inZone.GUID, avatar.locker))
//inZone.tasks.tell(GUIDTask.UnregisterObjectTask(avatar.locker)(inZone.GUID), context.parent)
log.info(s"Logout of ${avatar.name}") log.info(s"Logout of ${avatar.name}")
} }
} }

View file

@ -10,8 +10,8 @@ import net.psforever.services.avatar.support.{CorpseRemovalActor, DroppedItemRem
import net.psforever.services.{GenericEventBus, RemoverActor, Service} import net.psforever.services.{GenericEventBus, RemoverActor, Service}
class AvatarService(zone: Zone) extends Actor { class AvatarService(zone: Zone) extends Actor {
private val undertaker: ActorRef = context.actorOf(Props(classOf[CorpseRemovalActor], zone.tasks), s"${zone.id}-corpse-removal-agent") private val undertaker: ActorRef = context.actorOf(Props[CorpseRemovalActor](), s"${zone.id}-corpse-removal-agent")
private val janitor = context.actorOf(Props(classOf[DroppedItemRemover], zone.tasks), s"${zone.id}-item-remover-agent") private val janitor = context.actorOf(Props[DroppedItemRemover](), s"${zone.id}-item-remover-agent")
private[this] val log = org.log4s.getLogger private[this] val log = org.log4s.getLogger

View file

@ -1,8 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.services.avatar.support package net.psforever.services.avatar.support
import akka.actor.ActorRef import net.psforever.objects.guid.{GUIDTask, TaskBundle}
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.types.ExoSuitType import net.psforever.types.ExoSuitType
import net.psforever.services.{RemoverActor, Service} import net.psforever.services.{RemoverActor, Service}
@ -10,7 +9,7 @@ import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import scala.concurrent.duration._ import scala.concurrent.duration._
class CorpseRemovalActor(taskResolver: ActorRef) extends RemoverActor(taskResolver) { class CorpseRemovalActor extends RemoverActor() {
final val FirstStandardDuration: FiniteDuration = 1 minute final val FirstStandardDuration: FiniteDuration = 1 minute
final val SecondStandardDuration: FiniteDuration = 500 milliseconds final val SecondStandardDuration: FiniteDuration = 500 milliseconds
@ -32,9 +31,9 @@ class CorpseRemovalActor(taskResolver: ActorRef) extends RemoverActor(taskResolv
def ClearanceTest(entry: RemoverActor.Entry): Boolean = !entry.zone.Corpses.contains(entry.obj) def ClearanceTest(entry: RemoverActor.Entry): Boolean = !entry.zone.Corpses.contains(entry.obj)
def DeletionTask(entry: RemoverActor.Entry): TaskResolver.GiveTask = { def DeletionTask(entry: RemoverActor.Entry): TaskBundle = {
val player = entry.obj.asInstanceOf[Player] val player = entry.obj.asInstanceOf[Player]
val task = GUIDTask.UnregisterPlayer(player)(entry.zone.GUID) val task = GUIDTask.unregisterPlayer(entry.zone.GUID, player)
player.ExoSuit = ExoSuitType.Standard player.ExoSuit = ExoSuitType.Standard
task task
} }

View file

@ -1,15 +1,14 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.services.avatar.support package net.psforever.services.avatar.support
import akka.actor.ActorRef
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.objects.guid.{GUIDTask, TaskBundle}
import net.psforever.services.{RemoverActor, Service} import net.psforever.services.{RemoverActor, Service}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import scala.concurrent.duration._ import scala.concurrent.duration._
class DroppedItemRemover(taskResolver: ActorRef) extends RemoverActor(taskResolver) { class DroppedItemRemover extends RemoverActor() {
final val FirstStandardDuration: FiniteDuration = 3 minutes final val FirstStandardDuration: FiniteDuration = 3 minutes
final val SecondStandardDuration: FiniteDuration = 500 milliseconds final val SecondStandardDuration: FiniteDuration = 500 milliseconds
@ -31,7 +30,7 @@ class DroppedItemRemover(taskResolver: ActorRef) extends RemoverActor(taskResolv
def ClearanceTest(entry: RemoverActor.Entry): Boolean = !entry.zone.EquipmentOnGround.contains(entry.obj) def ClearanceTest(entry: RemoverActor.Entry): Boolean = !entry.zone.EquipmentOnGround.contains(entry.obj)
def DeletionTask(entry: RemoverActor.Entry): TaskResolver.GiveTask = { def DeletionTask(entry: RemoverActor.Entry): TaskBundle = {
GUIDTask.UnregisterEquipment(entry.obj.asInstanceOf[Equipment])(entry.zone.GUID) GUIDTask.unregisterEquipment(entry.zone.GUID, entry.obj.asInstanceOf[Equipment])
} }
} }

View file

@ -19,10 +19,10 @@ class LocalService(zone: Zone) extends Actor {
Props[HackClearActor](), s"${zone.id}-local-hack-clearer" Props[HackClearActor](), s"${zone.id}-local-hack-clearer"
) )
private val hackCapturer = context.actorOf( private val hackCapturer = context.actorOf(
Props(classOf[HackCaptureActor], zone.tasks), s"${zone.id}-local-hack-capturer" Props[HackCaptureActor](), s"${zone.id}-local-hack-capturer"
) )
private val captureFlagManager = context.actorOf( private val captureFlagManager = context.actorOf(
Props(classOf[CaptureFlagManager], zone.tasks, zone), s"${zone.id}-local-capture-flag-manager" Props(classOf[CaptureFlagManager], zone), s"${zone.id}-local-capture-flag-manager"
) )
private[this] val log = org.log4s.getLogger private[this] val log = org.log4s.getLogger

View file

@ -1,8 +1,9 @@
package net.psforever.services.local.support package net.psforever.services.local.support
import akka.actor.{Actor, ActorRef, Cancellable} import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.login.WorldSession
import net.psforever.objects.{Default, Player} import net.psforever.objects.{Default, Player}
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.serverobject.llu.CaptureFlag import net.psforever.objects.serverobject.llu.CaptureFlag
import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
@ -16,14 +17,11 @@ import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID, Vector3} import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID, Vector3}
import scala.concurrent.duration.DurationInt import scala.concurrent.duration.DurationInt
import scala.util.Success
/** /**
* Responsible for handling capture flag related lifecycles * Responsible for handling capture flag related lifecycles
* @param taskResolver A reference to a zone's task resolver actor
*/ */
class CaptureFlagManager(val taskResolver: ActorRef, zone: Zone) extends Actor{ class CaptureFlagManager(zone: Zone) extends Actor{
private[this] val log = org.log4s.getLogger(self.path.name) private[this] val log = org.log4s.getLogger(self.path.name)
var galaxyService: ActorRef = ActorRef.noSender var galaxyService: ActorRef = ActorRef.noSender
@ -105,14 +103,14 @@ class CaptureFlagManager(val taskResolver: ActorRef, zone: Zone) extends Actor{
socket.captureFlag = flag socket.captureFlag = flag
TrackFlag(flag) TrackFlag(flag)
taskResolver ! CallBackForTask( TaskWorkflow.execute(WorldSession.CallBackForTask(
TaskResolver.GiveTask(GUIDTask.RegisterObjectTask(flag)(socket.Zone.GUID).task), GUIDTask.registerObject(socket.Zone.GUID, flag),
socket.Zone.LocalEvents, socket.Zone.LocalEvents,
LocalServiceMessage( LocalServiceMessage(
socket.Zone.id, socket.Zone.id,
LocalAction.LluSpawned(PlanetSideGUID(-1), flag) LocalAction.LluSpawned(PlanetSideGUID(-1), flag)
) )
) ))
// Broadcast chat message for LLU spawn // Broadcast chat message for LLU spawn
val owner = flag.Owner.asInstanceOf[Building] val owner = flag.Owner.asInstanceOf[Building]
@ -184,7 +182,7 @@ class CaptureFlagManager(val taskResolver: ActorRef, zone: Zone) extends Actor{
// Unregister LLU from clients, // Unregister LLU from clients,
flag.Zone.LocalEvents ! LocalServiceMessage(flag.Zone.id, LocalAction.LluDespawned(PlanetSideGUID(-1), flag)) flag.Zone.LocalEvents ! LocalServiceMessage(flag.Zone.id, LocalAction.LluDespawned(PlanetSideGUID(-1), flag))
// Then unregister it from the GUID pool // Then unregister it from the GUID pool
taskResolver ! TaskResolver.GiveTask(GUIDTask.UnregisterObjectTask(flag)(flag.Zone.GUID).task) TaskWorkflow.execute(GUIDTask.unregisterObject(flag.Zone.GUID,flag))
} }
private def ChatBroadcast(zone: Zone, message: String, fanfare: Boolean = true): Unit = { private def ChatBroadcast(zone: Zone, message: String, fanfare: Boolean = true): Unit = {
@ -202,25 +200,6 @@ class CaptureFlagManager(val taskResolver: ActorRef, zone: Zone) extends Actor{
) )
) )
} }
// Todo: Duplicate from SessionActor. Make common.
def CallBackForTask(task: TaskResolver.GiveTask, sendTo: ActorRef, pass: Any): TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private val localDesc = task.task.Description
private val destination = sendTo
private val passMsg = pass
override def Description: String = s"callback for tasking $localDesc"
def Execute(resolver: ActorRef): Unit = {
destination ! passMsg
resolver ! Success(this)
}
},
List(task)
)
}
} }
object CaptureFlagManager { object CaptureFlagManager {

View file

@ -1,6 +1,6 @@
package net.psforever.services.local.support package net.psforever.services.local.support
import akka.actor.{Actor, ActorRef, Cancellable} import akka.actor.{Actor, Cancellable}
import net.psforever.actors.zone.{BuildingActor, ZoneActor} import net.psforever.actors.zone.{BuildingActor, ZoneActor}
import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.hackable.Hackable
@ -20,7 +20,7 @@ import scala.util.Random
/** /**
* Responsible for handling the aspects related to hacking control consoles and capturing bases. * Responsible for handling the aspects related to hacking control consoles and capturing bases.
*/ */
class HackCaptureActor(val taskResolver: ActorRef) extends Actor { class HackCaptureActor extends Actor {
private[this] val log = org.log4s.getLogger private[this] val log = org.log4s.getLogger
private var clearTrigger: Cancellable = Default.Cancellable private var clearTrigger: Cancellable = Default.Cancellable

View file

@ -1,10 +1,10 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.services.vehicle.support package net.psforever.services.vehicle.support
import akka.actor.{ActorRef, Cancellable} import akka.actor.Cancellable
import net.psforever.objects.equipment.EquipmentSlot import net.psforever.objects.equipment.EquipmentSlot
import net.psforever.objects.{AmmoBox, Default, PlanetSideGameObject, Tool} import net.psforever.objects.{AmmoBox, Default, PlanetSideGameObject, Tool}
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} import net.psforever.objects.guid._
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurret} import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurret}
import net.psforever.objects.vehicles.MountedWeapons import net.psforever.objects.vehicles.MountedWeapons
@ -13,8 +13,8 @@ import net.psforever.types.PlanetSideGUID
import net.psforever.services.support.{SimilarityComparator, SupportActor, SupportActorCaseConversions} import net.psforever.services.support.{SimilarityComparator, SupportActor, SupportActorCaseConversions}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.Future
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.util.Success
class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] { class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] {
var task: Cancellable = Default.Cancellable var task: Cancellable = Default.Cancellable
@ -172,39 +172,34 @@ class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] {
val oldBoxesTask = oldBoxes val oldBoxesTask = oldBoxes
.filterNot { box => newBoxes.exists(_ eq box) } .filterNot { box => newBoxes.exists(_ eq box) }
.map(box => GUIDTask.UnregisterEquipment(box)(guid)) .map(box => GUIDTask.unregisterEquipment(guid, box))
.toList .toList
val newBoxesTask = TaskResolver.GiveTask( import scala.concurrent.ExecutionContext.Implicits.global
new Task() { val newBoxesTask = TaskBundle(
new StraightforwardTask() {
private val localFunc: () => Unit = FinishUpgradingTurret(entry) private val localFunc: () => Unit = FinishUpgradingTurret(entry)
override def isComplete = Task.Resolution.Success def action(): Future[Any] = {
def Execute(resolver: ActorRef): Unit = {
resolver ! Success(this)
}
override def onSuccess(): Unit = {
super.onSuccess()
localFunc() localFunc()
Future(this)
} }
}, },
newBoxes newBoxes
.filterNot { box => oldBoxes.exists(_ eq box) } .filterNot { box => oldBoxes.exists(_ eq box) }
.map(box => GUIDTask.RegisterEquipment(box)(guid)) .map(box => GUIDTask.registerEquipment(guid, box))
.toList .toList
) )
target.Zone.tasks ! TaskResolver.GiveTask( TaskWorkflow.execute(TaskBundle(
new Task() { new StraightforwardTask() {
private val tasks = oldBoxesTask private val tasks = oldBoxesTask
def Execute(resolver: ActorRef): Unit = { def action(): Future[Any] = {
tasks.foreach { resolver ! _ } tasks.foreach { TaskWorkflow.execute }
resolver ! Success(this) Future(this)
} }
}, },
List(newBoxesTask) newBoxesTask
) ))
} }
/** /**

View file

@ -12,6 +12,7 @@ import io.circe.parser._
import net.psforever.objects.{GlobalDefinitions, LocalLockerItem, LocalProjectile} import net.psforever.objects.{GlobalDefinitions, LocalLockerItem, LocalProjectile}
import net.psforever.objects.ballistics.Projectile import net.psforever.objects.ballistics.Projectile
import net.psforever.objects.definition.BasicDefinition import net.psforever.objects.definition.BasicDefinition
import net.psforever.objects.guid.selector.{NumberSelector, RandomSelector, SpecificSelector}
import net.psforever.objects.serverobject.doors.{Door, DoorDefinition, SpawnTubeDoor} import net.psforever.objects.serverobject.doors.{Door, DoorDefinition, SpawnTubeDoor}
import net.psforever.objects.serverobject.generator.Generator import net.psforever.objects.serverobject.generator.Generator
import net.psforever.objects.serverobject.llu.{CaptureFlagSocket, CaptureFlagSocketDefinition} import net.psforever.objects.serverobject.llu.{CaptureFlagSocket, CaptureFlagSocketDefinition}
@ -55,6 +56,25 @@ object Zones {
} }
} }
private case class GuidNumberPool(
name: String,
start: Int,
max: Int,
selector: String
) {
def getSelector() : NumberSelector = {
if (selector.equals("random")) new RandomSelector
else new SpecificSelector
}
}
private implicit val decodeNumberPool: Decoder[GuidNumberPool] = Decoder.forProduct4(
"Name",
"Start",
"Max",
"Selector"
)(GuidNumberPool.apply)
private implicit val decodeZoneMapEntity: Decoder[ZoneMapEntity] = Decoder.forProduct11( private implicit val decodeZoneMapEntity: Decoder[ZoneMapEntity] = Decoder.forProduct11(
"Id", "Id",
"ObjectName", "ObjectName",
@ -585,44 +605,84 @@ object Zones {
} }
} }
lazy val zones: Seq[Zone] = ZoneInfo.values.map { info => lazy val zones: Seq[Zone] = {
new Zone(info.id, zoneMaps.find(_.name == info.map.value).get, info.value) { val defaultGuids =
override def init(implicit context: ActorContext): Unit = { try {
super.init(context) val res = Source.fromResource("guid-pools/default.json")
val json = res.mkString
res.close()
decode[Seq[GuidNumberPool]](json).toOption.get
} catch {
case _: Exception => Seq()
}
if (!info.id.startsWith("tz")) { ZoneInfo.values.map { info =>
this.HotSpotCoordinateFunction = Zones.HotSpots.standardRemapping(info.map.scale, 80, 80) val guids =
this.HotSpotTimeFunction = Zones.HotSpots.standardTimeRules try {
Zones.initZoneAmenities(this) val res = Source.fromResource(s"guid-pools/${info.id}.json")
val json = res.mkString
res.close()
val custom = decode[Seq[GuidNumberPool]](json).toOption.get
customizePools(defaultGuids, custom)
} catch {
case _: Exception => defaultGuids
} }
info.id match { new Zone(info.id, zoneMaps.find(_.name.equals(info.map.value)).get, info.value) {
case "home1" => private val addPoolsFunc: () => Unit = addPools(guids, zone = this)
this.Buildings.values.foreach(_.Faction = PlanetSideEmpire.NC)
case "home2" =>
this.Buildings.values.foreach(_.Faction = PlanetSideEmpire.TR)
case "home3" =>
this.Buildings.values.foreach(_.Faction = PlanetSideEmpire.VS)
case zoneid if zoneid.startsWith("c") =>
this.map.cavern = true
case _ => ()
}
// Set up warp gate factions aka "sanctuary link". Those names make no sense anymore, don't even ask. override def SetupNumberPools() : Unit = addPoolsFunc()
this.Buildings.foreach {
case (_, building) if building.Name.startsWith("WG") => override def init(implicit context: ActorContext): Unit = {
building.Name match { super.init(context)
case "WG_Amerish_to_Solsar" | "WG_Esamir_to_VSSanc" => building.Faction = PlanetSideEmpire.NC
case "WG_Hossin_to_VSSanc" | "WG_Solsar_to_Amerish" => building.Faction = PlanetSideEmpire.TR if (!info.id.startsWith("tz")) {
case "WG_Ceryshen_to_Hossin" | "WG_Forseral_to_Solsar" => building.Faction = PlanetSideEmpire.VS this.HotSpotCoordinateFunction = Zones.HotSpots.standardRemapping(info.map.scale, 80, 80)
case _ => () this.HotSpotTimeFunction = Zones.HotSpots.standardTimeRules
} Zones.initZoneAmenities(this)
case _ => () }
info.id match {
case "home1" =>
this.Buildings.values.foreach(_.Faction = PlanetSideEmpire.NC)
case "home2" =>
this.Buildings.values.foreach(_.Faction = PlanetSideEmpire.TR)
case "home3" =>
this.Buildings.values.foreach(_.Faction = PlanetSideEmpire.VS)
case zoneid if zoneid.startsWith("c") =>
this.map.cavern = true
case _ => ;
}
// Set up warp gate factions aka "sanctuary link". Those names make no sense anymore, don't even ask.
this.Buildings.foreach {
case (_, building) if building.Name.startsWith("WG") =>
building.Name match {
case "WG_Amerish_to_Solsar" | "WG_Esamir_to_VSSanc" => building.Faction = PlanetSideEmpire.NC
case "WG_Hossin_to_VSSanc" | "WG_Solsar_to_Amerish" => building.Faction = PlanetSideEmpire.TR
case "WG_Ceryshen_to_Hossin" | "WG_Forseral_to_Solsar" => building.Faction = PlanetSideEmpire.VS
case _ => ;
}
case _ => ;
}
} }
} }
} }
} }
private def customizePools(base: Seq[GuidNumberPool], custom: Seq[GuidNumberPool]): Seq[GuidNumberPool] = {
val exclude = custom.map { _.name }
val remainder = base.filterNot { entry => exclude.contains { entry.name } }
custom ++ remainder
}
private def addPools(guids: Seq[GuidNumberPool], zone: Zone)(): Unit = {
guids.foreach { entry =>
zone.AddPool(name = entry.name, (entry.start to entry.max).toList)
.foreach { _.Selector = entry.getSelector() }
}
}
def initZoneAmenities(zone: Zone): Unit = { def initZoneAmenities(zone: Zone): Unit = {
initResourceSilos(zone) initResourceSilos(zone)
initWarpGates(zone) initWarpGates(zone)

View file

@ -1614,7 +1614,6 @@ class DamageableVehicleDestroyMountedTest extends FreedContextActorTest {
override def Activity = activityProbe.ref override def Activity = activityProbe.ref
override def AvatarEvents = avatarProbe.ref override def AvatarEvents = avatarProbe.ref
override def VehicleEvents = vehicleProbe.ref override def VehicleEvents = vehicleProbe.ref
override def tasks = catchall.ref
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._
this.actor = catchall.ref.toTyped[ZoneActor.Command] this.actor = catchall.ref.toTyped[ZoneActor.Command]
} }

View file

@ -9,7 +9,7 @@ import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.avatar.{Avatar, Certification, PlayerControl} import net.psforever.objects.avatar.{Avatar, Certification, PlayerControl}
import net.psforever.objects.{ConstructionItem, Deployables, GlobalDefinitions, Player} import net.psforever.objects.{ConstructionItem, Deployables, GlobalDefinitions, Player}
import net.psforever.objects.ce.{Deployable, DeployedItem} import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.guid.{NumberPoolHub, TaskResolver} import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.zones.{Zone, ZoneDeployableActor, ZoneMap} import net.psforever.objects.zones.{Zone, ZoneDeployableActor, ZoneMap}
import net.psforever.packet.game._ import net.psforever.packet.game._
@ -141,7 +141,6 @@ class DeployableBehaviorSetupOwnedP2Test extends FreedContextActorTest {
override def Deployables: ActorRef = deployables override def Deployables: ActorRef = deployables
override def Players = List(avatar) override def Players = List(avatar)
override def LivePlayers = List(player) override def LivePlayers = List(player)
override def tasks: ActorRef = eventsProbe.ref
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command] this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
} }
@ -165,7 +164,7 @@ class DeployableBehaviorSetupOwnedP2Test extends FreedContextActorTest {
assert(!avatar.deployables.Contains(jmine), "owned setup test, 2 - avatar already owns deployable") assert(!avatar.deployables.Contains(jmine), "owned setup test, 2 - avatar already owns deployable")
zone.Deployables ! Zone.Deployable.BuildByOwner(jmine, player, citem) zone.Deployables ! Zone.Deployable.BuildByOwner(jmine, player, citem)
//assert(false, "test needs to be fixed") //assert(false, "test needs to be fixed")
val eventsMsgs = eventsProbe.receiveN(8, 10.seconds) val eventsMsgs = eventsProbe.receiveN(7, 10.seconds)
eventsMsgs.head match { eventsMsgs.head match {
case AvatarServiceMessage( case AvatarServiceMessage(
"TestCharacter1", "TestCharacter1",
@ -220,11 +219,6 @@ class DeployableBehaviorSetupOwnedP2Test extends FreedContextActorTest {
case _ => case _ =>
assert(false, "owned setup test, 2 - construction tool not deleted") assert(false, "owned setup test, 2 - construction tool not deleted")
} }
eventsMsgs(7) match {
case TaskResolver.GiveTask(_, _) => ;
case _ =>
assert(false, "owned setup test, 2 - construction tool not unregistered")
}
assert(player.Slot(slot = 0).Equipment.isEmpty, "owned setup test, 2 - player hand should be empty") assert(player.Slot(slot = 0).Equipment.isEmpty, "owned setup test, 2 - player hand should be empty")
assert(deployableList.contains(jmine), "owned setup test, 2 - deployable not appended to list") assert(deployableList.contains(jmine), "owned setup test, 2 - deployable not appended to list")
assert(avatar.deployables.Contains(jmine), "owned setup test, 2 - avatar does not own deployable") assert(avatar.deployables.Contains(jmine), "owned setup test, 2 - avatar does not own deployable")
@ -244,7 +238,6 @@ class DeployableBehaviorDeconstructTest extends ActorTest {
GUID(guid) GUID(guid)
override def AvatarEvents: ActorRef = eventsProbe.ref override def AvatarEvents: ActorRef = eventsProbe.ref
override def LocalEvents: ActorRef = eventsProbe.ref override def LocalEvents: ActorRef = eventsProbe.ref
override def tasks: ActorRef = eventsProbe.ref
override def Deployables: ActorRef = deployables override def Deployables: ActorRef = deployables
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command] this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
@ -261,7 +254,7 @@ class DeployableBehaviorDeconstructTest extends ActorTest {
assert(deployableList.contains(jmine), "deconstruct test - deployable not appended to list") assert(deployableList.contains(jmine), "deconstruct test - deployable not appended to list")
jmine.Actor ! Deployable.Deconstruct() jmine.Actor ! Deployable.Deconstruct()
val eventsMsgs = eventsProbe.receiveN(3, 10.seconds) val eventsMsgs = eventsProbe.receiveN(2, 10.seconds)
eventsMsgs.head match { eventsMsgs.head match {
case LocalServiceMessage("test", LocalAction.EliminateDeployable(`jmine`, PlanetSideGUID(1), Vector3(1,2,3), 2)) => ; case LocalServiceMessage("test", LocalAction.EliminateDeployable(`jmine`, PlanetSideGUID(1), Vector3(1,2,3), 2)) => ;
case _ => assert(false, "deconstruct test - not eliminating deployable") case _ => assert(false, "deconstruct test - not eliminating deployable")
@ -277,10 +270,6 @@ class DeployableBehaviorDeconstructTest extends ActorTest {
) => ; ) => ;
case _ => assert(false, "owned deconstruct test - not removing icon") case _ => assert(false, "owned deconstruct test - not removing icon")
} }
eventsMsgs(2) match {
case TaskResolver.GiveTask(_, _) => ;
case _ => assert(false, "deconstruct test - not unregistering deployable")
}
assert(!deployableList.contains(jmine), "deconstruct test - deployable not removed from list") assert(!deployableList.contains(jmine), "deconstruct test - deployable not removed from list")
} }
} }
@ -304,7 +293,6 @@ class DeployableBehaviorDeconstructOwnedTest extends FreedContextActorTest {
override def Deployables: ActorRef = deployables override def Deployables: ActorRef = deployables
override def Players = List(avatar) override def Players = List(avatar)
override def LivePlayers = List(player) override def LivePlayers = List(player)
override def tasks: ActorRef = eventsProbe.ref
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command] this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
} }
@ -324,12 +312,12 @@ class DeployableBehaviorDeconstructOwnedTest extends FreedContextActorTest {
"DeployableBehavior" should { "DeployableBehavior" should {
"deconstruct and alert owner" in { "deconstruct and alert owner" in {
zone.Deployables ! Zone.Deployable.BuildByOwner(jmine, player, citem) zone.Deployables ! Zone.Deployable.BuildByOwner(jmine, player, citem)
eventsProbe.receiveN(8, 10.seconds) eventsProbe.receiveN(7, 10.seconds)
assert(deployableList.contains(jmine), "owned deconstruct test - deployable not appended to list") assert(deployableList.contains(jmine), "owned deconstruct test - deployable not appended to list")
assert(avatar.deployables.Contains(jmine), "owned deconstruct test - avatar does not own deployable") assert(avatar.deployables.Contains(jmine), "owned deconstruct test - avatar does not own deployable")
jmine.Actor ! Deployable.Deconstruct() jmine.Actor ! Deployable.Deconstruct()
val eventsMsgs = eventsProbe.receiveN(4, 10.seconds) val eventsMsgs = eventsProbe.receiveN(3, 10.seconds)
eventsMsgs.head match { eventsMsgs.head match {
case LocalServiceMessage("test", LocalAction.EliminateDeployable(`jmine`, PlanetSideGUID(1), Vector3(1,2,3), 2)) => ; case LocalServiceMessage("test", LocalAction.EliminateDeployable(`jmine`, PlanetSideGUID(1), Vector3(1,2,3), 2)) => ;
case _ => assert(false, "owned deconstruct test - not eliminating deployable") case _ => assert(false, "owned deconstruct test - not eliminating deployable")
@ -349,10 +337,6 @@ class DeployableBehaviorDeconstructOwnedTest extends FreedContextActorTest {
) => ; ) => ;
case _ => assert(false, "owned deconstruct test - not removing icon") case _ => assert(false, "owned deconstruct test - not removing icon")
} }
eventsMsgs(3) match {
case TaskResolver.GiveTask(_, _) => ;
case _ => assert(false, "owned deconstruct test - not unregistering deployable")
}
assert(deployableList.isEmpty, "owned deconstruct test - deployable still in list") assert(deployableList.isEmpty, "owned deconstruct test - deployable still in list")
assert(!avatar.deployables.Contains(jmine), "owned deconstruct test - avatar still owns deployable") assert(!avatar.deployables.Contains(jmine), "owned deconstruct test - avatar still owns deployable")

View file

@ -330,7 +330,6 @@ class ExplosiveDeployableJammerTest extends ActorTest {
override def Deployables: ActorRef = deployables override def Deployables: ActorRef = deployables
override def Players = List(avatar1, avatar2) override def Players = List(avatar1, avatar2)
override def LivePlayers = List(player1, player2) override def LivePlayers = List(player1, player2)
override def tasks: ActorRef = eventsProbe.ref
} }
player1.Spawn() player1.Spawn()
player2.Spawn() player2.Spawn()
@ -408,7 +407,6 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest {
override def Deployables: ActorRef = deployables override def Deployables: ActorRef = deployables
override def Players = List(avatar1, avatar2) override def Players = List(avatar1, avatar2)
override def LivePlayers = List(player1, player2) override def LivePlayers = List(player1, player2)
override def tasks: ActorRef = eventsProbe.ref
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command] this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
} }
@ -518,7 +516,6 @@ class ExplosiveDeployableDestructionTest extends ActorTest {
override def Deployables: ActorRef = deployables override def Deployables: ActorRef = deployables
override def Players = List(avatar1, avatar2) override def Players = List(avatar1, avatar2)
override def LivePlayers = List(player1, player2) override def LivePlayers = List(player1, player2)
override def tasks: ActorRef = eventsProbe.ref
} }
player1.Spawn() player1.Spawn()
player1.Actor = player1Probe.ref player1.Actor = player1Probe.ref

View file

@ -2,13 +2,11 @@
package objects package objects
import akka.actor.{ActorRef, Props} import akka.actor.{ActorRef, Props}
import akka.routing.RandomPool
import akka.testkit.TestProbe import akka.testkit.TestProbe
import base.FreedContextActorTest import base.FreedContextActorTest
import net.psforever.actors.zone.{BuildingActor, ZoneActor} import net.psforever.actors.zone.{BuildingActor, ZoneActor}
import net.psforever.objects.guid.actor.UniqueNumberSystem
import net.psforever.objects.{GlobalDefinitions, Vehicle} import net.psforever.objects.{GlobalDefinitions, Vehicle}
import net.psforever.objects.guid.{NumberPoolHub, TaskResolver} import net.psforever.objects.guid.{NumberPoolHub, UniqueNumberOps, UniqueNumberSetup}
import net.psforever.objects.guid.source.MaxNumberSource import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.shuttle.{OrbitalShuttle, OrbitalShuttlePad, OrbitalShuttlePadControl, ShuttleAmenity} import net.psforever.objects.serverobject.shuttle.{OrbitalShuttle, OrbitalShuttlePad, OrbitalShuttlePadControl, ShuttleAmenity}
@ -23,7 +21,7 @@ import scala.collection.concurrent.TrieMap
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
import scala.concurrent.duration._ import scala.concurrent.duration._
class OrbitalShuttlePadControltest extends FreedContextActorTest { class OrbitalShuttlePadControlTest extends FreedContextActorTest {
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._
system.spawn(InterstellarClusterService(Nil), InterstellarClusterService.InterstellarClusterServiceKey.id) system.spawn(InterstellarClusterService(Nil), InterstellarClusterService.InterstellarClusterServiceKey.id)
val services = ServiceManager.boot(system) val services = ServiceManager.boot(system)
@ -32,22 +30,17 @@ class OrbitalShuttlePadControltest extends FreedContextActorTest {
expectNoMessage(1000 milliseconds) expectNoMessage(1000 milliseconds)
var buildingMap = new TrieMap[Int, Building]() var buildingMap = new TrieMap[Int, Building]()
val vehicles = ListBuffer[Vehicle]() val vehicles = ListBuffer[Vehicle]()
val guid = new NumberPoolHub(new MaxNumberSource(max = 15)) val guid = new NumberPoolHub(new MaxNumberSource(max = 20))
guid.AddPool("dynamic", (11 to 15).toList) guid.AddPool("vehicles", (11 to 15).toList)
guid.AddPool("tools", (16 to 19).toList)
val catchall = new TestProbe(system).ref val catchall = new TestProbe(system).ref
val resolver = context.actorOf(RandomPool(1).props(Props[TaskResolver]()), s"test-taskResolver") val unops = new UniqueNumberOps(guid, UniqueNumberSetup.AllocateNumberPoolActors(context, guid))
val uns = context.actorOf(
RandomPool(1).props(
Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystem.AllocateNumberPoolActors(this.guid))
),
s"test-uns"
)
val zone = new Zone("test", new ZoneMap("test-map"), 0) { val zone = new Zone("test", new ZoneMap("test-map"), 0) {
val transport: ActorRef = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"zone-test-vehicles") val transport: ActorRef = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"zone-test-vehicles")
override def SetupNumberPools() = {} override def SetupNumberPools() = {}
GUID(guid) GUID(guid)
override def GUID = { uns } override def GUID = { unops }
override def AvatarEvents = catchall override def AvatarEvents = catchall
override def LocalEvents = catchall override def LocalEvents = catchall
override def VehicleEvents = catchall override def VehicleEvents = catchall
@ -55,7 +48,6 @@ class OrbitalShuttlePadControltest extends FreedContextActorTest {
override def Transport = { transport } override def Transport = { transport }
override def Vehicles = { vehicles.toList } override def Vehicles = { vehicles.toList }
override def Buildings = { buildingMap.toMap } override def Buildings = { buildingMap.toMap }
override def tasks = { resolver }
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command] this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]

View file

@ -0,0 +1,116 @@
// Copyright (c) 2021 PSForever
package objects
import net.psforever.objects.guid.{Task, TaskBundle, TaskWorkflow}
import org.scalatest.flatspec.AsyncFlatSpec
import scala.concurrent.Future
import scala.util.Failure
class AsyncTaskWorkflowTest extends AsyncFlatSpec {
case class StringAppendTask(product: StringBuilder, str: String) extends Task {
def action() = { Future({ product.append(str) }) }
def undo() = {
val index = product.indexOf(str)
product.replace(index, index + str.length, "[successful task undo]")
}
def isSuccessful() = { product.indexOf(str) > -1 }
}
case class FailedStringAppendTask(product: StringBuilder, str: String) extends Task {
def action() = { Future(Failure(new Exception("intentional failure"))) }
def undo() = {
val index = product.indexOf(str)
product.replace(index, index + str.length, "[failed task undo]")
}
def isSuccessful() = { product.indexOf(str) > -1 }
}
behavior of "TaskWorkFlow"
it should "append a string as a task" in {
val test: StringBuilder = new StringBuilder()
assert(test.mkString.isEmpty)
val result = TaskWorkflow.execute(TaskBundle(StringAppendTask(test, "hello")))
result map { _ =>
assert(test.mkString.equals("hello"), "async result does not equal 'hello'")
}
}
it should "append the strings in order of subtask then main task" in {
val test: StringBuilder = new StringBuilder()
assert(test.mkString.isEmpty)
val result = TaskWorkflow.execute(TaskBundle(StringAppendTask(test, " world"), StringAppendTask(test, "hello")))
result map { _ =>
assert(test.mkString.equals("hello world"), "async result does not equal 'hello world'")
}
}
it should "append the strings in order of subtasks then main task, with the subtasks being in either order" in {
val test: StringBuilder = new StringBuilder()
assert(test.mkString.isEmpty)
val result = TaskWorkflow.execute(TaskBundle(
StringAppendTask(test, " world"),
Seq(
TaskBundle(StringAppendTask(test, " hello")),
TaskBundle(StringAppendTask(test, " or goodbye"))
)
))
result map { _ =>
val output = test.mkString
assert(
output.equals(" or goodbye hello world") || output.equals(" hello or goodbye world"),
s"async result '$output' does not equal either pattern"
)
}
}
it should "if a task fails, do not undo it" in {
val test: StringBuilder = new StringBuilder()
assert(test.mkString.isEmpty)
val result = TaskWorkflow.execute(TaskBundle(FailedStringAppendTask(test, " world")))
result map { _ =>
val output = test.mkString
assert(output.equals(""),"async result was written when should have not been written")
//see implementation of FailedStringAppendTask.undo
}
}
it should "if a middling subtask fails, its parent task will not be executed or undone, but its own subtask will be undone (1)" in {
val test: StringBuilder = new StringBuilder()
assert(test.mkString.isEmpty)
val result = TaskWorkflow.execute(TaskBundle(
StringAppendTask(test, " world"),
TaskBundle(FailedStringAppendTask(test, "hello"), StringAppendTask(test, " or goodbye")))
)
result map { _ =>
val output = test.mkString
assert(output.equals("[successful task undo]"),s"async result, formerly successful, was written as if it had failed - $output")
//see implementation of StringAppendTask.undo
}
}
it should "if a middling subtask fails, its parent task will not be executed or undone, but its own subtasks will be undone (2)" in {
val test: StringBuilder = new StringBuilder()
assert(test.mkString.isEmpty)
val result = TaskWorkflow.execute(TaskBundle(
StringAppendTask(test, " world"),
TaskBundle(FailedStringAppendTask(test, "hello"), List(
TaskBundle(StringAppendTask(test, " or goodbye")),
TaskBundle(StringAppendTask(test, " or something"))
))
))
result map { _ =>
val output = test.mkString
assert(
output.equals("[successful task undo][successful task undo]"),
s"async result, formerly successful, was written as if it had failed - $output"
)
//see implementation of StringAppendTask.undo
}
}
}
object TaskWorkflowTest {
/** placeholder */
}

View file

@ -212,7 +212,6 @@ class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActor
override def SetupNumberPools(): Unit = {} override def SetupNumberPools(): Unit = {}
override def VehicleEvents = vehicleProbe.ref override def VehicleEvents = vehicleProbe.ref
override def tasks = catchall.ref
} }
zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor") zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
// crappy workaround but without it the zone doesn't get initialized in time // crappy workaround but without it the zone doesn't get initialized in time

View file

@ -130,7 +130,7 @@ class ZoneActorTest extends ActorTest {
zone.actor = system.spawn(ZoneActor(zone), "test-add-pool-actor-init") zone.actor = system.spawn(ZoneActor(zone), "test-add-pool-actor-init")
expectNoMessage(Duration.create(500, "ms")) expectNoMessage(Duration.create(500, "ms"))
assert(!zone.AddPool("test1", 1 to 2)) assert(zone.AddPool("test1", 1 to 2).isEmpty)
} }
"refuse to remove number pools after the Actor is started" in { "refuse to remove number pools after the Actor is started" in {

View file

@ -3,19 +3,21 @@ package objects.guidtask
import base.ActorTest import base.ActorTest
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
import scala.concurrent.duration._
class GUIDTaskRegisterAmmoTest extends ActorTest { class GUIDTaskRegisterAmmoTest extends ActorTest {
"RegisterEquipment -> RegisterObjectTask" in { "RegisterEquipment -> RegisterObjectTask" in {
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val (_, uns, probe) = GUIDTaskTest.CommonTestSetup
val obj = AmmoBox(GlobalDefinitions.energy_cell) val obj = AmmoBox(GlobalDefinitions.energy_cell)
assert(!obj.HasGUID) assert(!obj.HasGUID)
taskResolver ! TaskResolver.GiveTask( TaskWorkflow.execute(TaskBundle(
new GUIDTaskTest.RegisterTestTask(probe.ref), new GUIDTaskTest.RegisterTestTask(probe.ref),
List(GUIDTask.RegisterEquipment(obj)(uns)) GUIDTask.registerEquipment(uns, obj)
) ))
probe.expectMsg(scala.util.Success) probe.expectMsg(5.second, scala.util.Success(true))
assert(obj.HasGUID) assert(obj.HasGUID)
} }
} }

View file

@ -4,15 +4,17 @@ package objects.guidtask
import base.ActorTest import base.ActorTest
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.avatar.Avatar import net.psforever.objects.avatar.Avatar
import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
import net.psforever.objects.locker.LockerEquipment import net.psforever.objects.locker.LockerEquipment
import net.psforever.types.{CharacterSex, CharacterVoice, PlanetSideEmpire} import net.psforever.types.{CharacterSex, CharacterVoice, PlanetSideEmpire}
import scala.concurrent.duration._
class GUIDTaskRegisterAvatarTest extends ActorTest { class GUIDTaskRegisterAvatarTest extends ActorTest {
"RegisterAvatar" in { "RegisterAvatar" in {
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val (_, uns, probe) = GUIDTaskTest.CommonTestSetup
val obj = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) val obj = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute))
val obj_wep = Tool(GlobalDefinitions.beamer) val obj_wep = Tool(GlobalDefinitions.beamer)
obj.Slot(0).Equipment = obj_wep obj.Slot(0).Equipment = obj_wep
val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell) val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell)
obj_wep.AmmoSlots.head.Box = obj_wep_ammo obj_wep.AmmoSlots.head.Box = obj_wep_ammo
@ -28,11 +30,11 @@ class GUIDTaskRegisterAvatarTest extends ActorTest {
assert(!obj_inv_ammo.HasGUID) assert(!obj_inv_ammo.HasGUID)
assert(!obj_locker.HasGUID) assert(!obj_locker.HasGUID)
assert(obj_locker_ammo.HasGUID) assert(obj_locker_ammo.HasGUID)
taskResolver ! TaskResolver.GiveTask( TaskWorkflow.execute(TaskBundle(
new GUIDTaskTest.RegisterTestTask(probe.ref), new GUIDTaskTest.RegisterTestTask(probe.ref),
List(GUIDTask.RegisterAvatar(obj)(uns)) GUIDTask.registerAvatar(uns, obj)
) ))
probe.expectMsg(scala.util.Success) probe.expectMsg(5.second, scala.util.Success(true))
assert(obj.HasGUID) assert(obj.HasGUID)
assert(obj_wep.HasGUID) assert(obj_wep.HasGUID)
assert(obj_wep_ammo.HasGUID) assert(obj_wep_ammo.HasGUID)

View file

@ -2,19 +2,21 @@
package objects.guidtask package objects.guidtask
import base.ActorTest import base.ActorTest
import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
import scala.concurrent.duration._
class GUIDTaskRegisterObjectTest extends ActorTest { class GUIDTaskRegisterObjectTest extends ActorTest {
"RegisterObjectTask" in { "RegisterObjectTask" in {
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val (_, uns, probe) = GUIDTaskTest.CommonTestSetup
val obj = new GUIDTaskTest.TestObject val obj = new GUIDTaskTest.TestObject
assert(!obj.HasGUID) assert(!obj.HasGUID)
taskResolver ! TaskResolver.GiveTask( TaskWorkflow.execute(TaskBundle(
new GUIDTaskTest.RegisterTestTask(probe.ref), new GUIDTaskTest.RegisterTestTask(probe.ref),
List(GUIDTask.RegisterObjectTask(obj)(uns)) GUIDTask.registerObject(uns, obj)
) ))
probe.expectMsg(scala.util.Success) probe.expectMsg(5.second, scala.util.Success(true))
assert(obj.HasGUID) assert(obj.HasGUID)
} }
} }

View file

@ -4,15 +4,17 @@ package objects.guidtask
import base.ActorTest import base.ActorTest
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.avatar.Avatar import net.psforever.objects.avatar.Avatar
import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
import net.psforever.objects.locker.LockerEquipment import net.psforever.objects.locker.LockerEquipment
import net.psforever.types.{CharacterSex, CharacterVoice, PlanetSideEmpire} import net.psforever.types.{CharacterSex, CharacterVoice, PlanetSideEmpire}
import scala.concurrent.duration._
class GUIDTaskRegisterPlayerTest extends ActorTest { class GUIDTaskRegisterPlayerTest extends ActorTest {
"RegisterPlayer" in { "RegisterPlayer" in {
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val (_, uns, probe) = GUIDTaskTest.CommonTestSetup
val obj = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) val obj = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute))
val obj_wep = Tool(GlobalDefinitions.beamer) val obj_wep = Tool(GlobalDefinitions.beamer)
obj.Slot(0).Equipment = obj_wep obj.Slot(0).Equipment = obj_wep
val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell) val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell)
obj_wep.AmmoSlots.head.Box = obj_wep_ammo obj_wep.AmmoSlots.head.Box = obj_wep_ammo
@ -28,11 +30,11 @@ class GUIDTaskRegisterPlayerTest extends ActorTest {
assert(!obj_inv_ammo.HasGUID) assert(!obj_inv_ammo.HasGUID)
assert(!obj_locker.HasGUID) assert(!obj_locker.HasGUID)
assert(obj_locker_ammo.HasGUID) assert(obj_locker_ammo.HasGUID)
taskResolver ! TaskResolver.GiveTask( TaskWorkflow.execute(TaskBundle(
new GUIDTaskTest.RegisterTestTask(probe.ref), new GUIDTaskTest.RegisterTestTask(probe.ref),
List(GUIDTask.RegisterPlayer(obj)(uns)) GUIDTask.registerPlayer(uns, obj)
) ))
probe.expectMsg(scala.util.Success) probe.expectMsg(5.second, scala.util.Success(true))
assert(obj.HasGUID) assert(obj.HasGUID)
assert(obj_wep.HasGUID) assert(obj_wep.HasGUID)
assert(obj_wep_ammo.HasGUID) assert(obj_wep_ammo.HasGUID)

View file

@ -3,21 +3,23 @@ package objects.guidtask
import base.ActorTest import base.ActorTest
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
import scala.concurrent.duration._
class GUIDTaskRegisterToolTest extends ActorTest { class GUIDTaskRegisterToolTest extends ActorTest {
"RegisterEquipment -> RegisterTool" in { "RegisterEquipment -> RegisterTool" in {
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val (_, uns, probe) = GUIDTaskTest.CommonTestSetup
val obj = Tool(GlobalDefinitions.beamer) val obj = Tool(GlobalDefinitions.beamer)
obj.AmmoSlots.head.Box = AmmoBox(GlobalDefinitions.energy_cell) obj.AmmoSlots.head.Box = AmmoBox(GlobalDefinitions.energy_cell)
assert(!obj.HasGUID) assert(!obj.HasGUID)
assert(!obj.AmmoSlots.head.Box.HasGUID) assert(!obj.AmmoSlots.head.Box.HasGUID)
taskResolver ! TaskResolver.GiveTask( TaskWorkflow.execute(TaskBundle(
new GUIDTaskTest.RegisterTestTask(probe.ref), new GUIDTaskTest.RegisterTestTask(probe.ref),
List(GUIDTask.RegisterEquipment(obj)(uns)) GUIDTask.registerEquipment(uns, obj)
) ))
probe.expectMsg(scala.util.Success) probe.expectMsg(5.second, scala.util.Success(true))
assert(obj.HasGUID) assert(obj.HasGUID)
assert(obj.AmmoSlots.head.Box.HasGUID) assert(obj.AmmoSlots.head.Box.HasGUID)
} }

View file

@ -3,25 +3,27 @@ package objects.guidtask
import base.ActorTest import base.ActorTest
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
import scala.concurrent.duration._
class GUIDTaskRegisterTurretTest extends ActorTest { class GUIDTaskRegisterTurretTest extends ActorTest {
"RegisterDeployableTurret" in { "RegisterDeployableTurret" in {
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val (_, uns, probe) = GUIDTaskTest.CommonTestSetup
val obj = new TurretDeployable(GlobalDefinitions.portable_manned_turret_vs) val obj = new TurretDeployable(GlobalDefinitions.portable_manned_turret_vs)
val obj_wep = obj.Weapons(1).Equipment.get val obj_wep = obj.Weapons(1).Equipment.get
val obj_ammo = obj_wep.asInstanceOf[Tool].AmmoSlot.Box val obj_ammo = obj_wep.asInstanceOf[Tool].AmmoSlot.Box
val obj_res = obj.Inventory.Items.map(_.obj) val obj_res = obj.Inventory.Items.map(_.obj)
assert(!obj.HasGUID) assert(!obj.HasGUID)
assert(!obj_wep.HasGUID) assert(!obj_wep.HasGUID)
assert(!obj_ammo.HasGUID) assert(!obj_ammo.HasGUID)
obj_res.foreach(box => !box.HasGUID) obj_res.foreach(box => !box.HasGUID)
taskResolver ! TaskResolver.GiveTask( TaskWorkflow.execute(TaskBundle(
new GUIDTaskTest.RegisterTestTask(probe.ref), new GUIDTaskTest.RegisterTestTask(probe.ref),
List(GUIDTask.RegisterDeployableTurret(obj)(uns)) GUIDTask.registerDeployableTurret(uns, obj)
) ))
probe.expectMsg(scala.util.Success) probe.expectMsg(5.second, scala.util.Success(true))
assert(obj.HasGUID) assert(obj.HasGUID)
assert(obj_wep.HasGUID) assert(obj_wep.HasGUID)
assert(obj_ammo.HasGUID) assert(obj_ammo.HasGUID)

View file

@ -3,13 +3,15 @@ package objects.guidtask
import base.ActorTest import base.ActorTest
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
import scala.concurrent.duration._
class GUIDTaskRegisterVehicleTest extends ActorTest { class GUIDTaskRegisterVehicleTest extends ActorTest {
"RegisterVehicle" in { "RegisterVehicle" in {
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val (_, uns, probe) = GUIDTaskTest.CommonTestSetup
val obj = Vehicle(GlobalDefinitions.fury) val obj = Vehicle(GlobalDefinitions.fury)
val obj_wep = obj.WeaponControlledFromSeat(0).get val obj_wep = obj.WeaponControlledFromSeat(0).get
val obj_wep_ammo = (obj.WeaponControlledFromSeat(0).get.asInstanceOf[Tool].AmmoSlots.head.Box = val obj_wep_ammo = (obj.WeaponControlledFromSeat(0).get.asInstanceOf[Tool].AmmoSlots.head.Box =
AmmoBox(GlobalDefinitions.hellfire_ammo)).get AmmoBox(GlobalDefinitions.hellfire_ammo)).get
obj.Trunk += 30 -> AmmoBox(GlobalDefinitions.hellfire_ammo) obj.Trunk += 30 -> AmmoBox(GlobalDefinitions.hellfire_ammo)
@ -19,11 +21,11 @@ class GUIDTaskRegisterVehicleTest extends ActorTest {
assert(!obj_wep.HasGUID) assert(!obj_wep.HasGUID)
assert(!obj_wep_ammo.HasGUID) assert(!obj_wep_ammo.HasGUID)
assert(!obj_trunk_ammo.HasGUID) assert(!obj_trunk_ammo.HasGUID)
taskResolver ! TaskResolver.GiveTask( TaskWorkflow.execute(TaskBundle(
new GUIDTaskTest.RegisterTestTask(probe.ref), new GUIDTaskTest.RegisterTestTask(probe.ref),
List(GUIDTask.RegisterVehicle(obj)(uns)) GUIDTask.registerVehicle(uns, obj)
) ))
probe.expectMsg(scala.util.Success) probe.expectMsg(5.second, scala.util.Success(true))
assert(obj.HasGUID) assert(obj.HasGUID)
assert(obj_wep.HasGUID) assert(obj_wep.HasGUID)
assert(obj_wep_ammo.HasGUID) assert(obj_wep_ammo.HasGUID)

View file

@ -2,50 +2,54 @@
package objects.guidtask package objects.guidtask
import java.util.logging.LogManager import java.util.logging.LogManager
import scala.util.Success import scala.util.Success
import akka.actor.{ActorRef, ActorSystem, Props} import akka.actor.{ActorRef, ActorSystem, Props}
import akka.testkit.TestProbe import akka.testkit.TestProbe
import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.guid.actor.{NumberPoolActor, UniqueNumberSystem}
import net.psforever.objects.guid.selector.RandomSelector import net.psforever.objects.guid.selector.RandomSelector
import net.psforever.objects.guid.source.MaxNumberSource import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.guid.{NumberPoolHub, Task, TaskResolver} import net.psforever.objects.guid.uns.NumberPoolActor
import net.psforever.objects.guid.{NumberPoolHub, StraightforwardTask, UniqueNumberOps}
import scala.concurrent.Future
object GUIDTaskTest { object GUIDTaskTest {
class TestObject extends IdentifiableEntity class TestObject extends IdentifiableEntity
class RegisterTestTask(probe: ActorRef) extends Task { class RegisterTestTask(probe: ActorRef) extends StraightforwardTask {
def Execute(resolver: ActorRef): Unit = { def action(): Future[Any] = {
probe ! Success probe ! Success(true)
resolver ! Success(this) Future(this)(scala.concurrent.ExecutionContext.Implicits.global)
} }
} }
def CommonTestSetup(implicit system: ActorSystem): (NumberPoolHub, ActorRef, ActorRef, TestProbe) = { def CommonTestSetup(implicit system: ActorSystem): (NumberPoolHub, UniqueNumberOps, TestProbe) = {
import akka.actor.Props
import akka.routing.RandomPool
import akka.testkit.TestProbe import akka.testkit.TestProbe
val guid: NumberPoolHub = new NumberPoolHub(new MaxNumberSource(110)) val guid: NumberPoolHub = new NumberPoolHub(new MaxNumberSource(90))
guid.AddPool("dynamic", (1 to 100).toList).Selector = new RandomSelector //TODO name is hardcoded for now guid.AddPool("players", (1 to 10).toList).Selector = new RandomSelector
val uns = system.actorOf( guid.AddPool("lockers", (11 to 20).toList).Selector = new RandomSelector
RandomPool(25).props(Props(classOf[UniqueNumberSystem], guid, GUIDTaskTest.AllocateNumberPoolActors(guid))), guid.AddPool("ammo", (21 to 30).toList).Selector = new RandomSelector
"uns" guid.AddPool("tools", (31 to 40).toList).Selector = new RandomSelector
) guid.AddPool("vehicles", (41 to 50).toList).Selector = new RandomSelector
val taskResolver = system.actorOf(RandomPool(15).props(Props[TaskResolver]()), "resolver") guid.AddPool("terminals", (51 to 60).toList).Selector = new RandomSelector
guid.AddPool("items", (61 to 70).toList).Selector = new RandomSelector
guid.AddPool("deployables", (71 to 80).toList).Selector = new RandomSelector
val uns = new UniqueNumberOps(guid, AllocateNumberPoolActors(guid)(system))
LogManager.getLogManager.reset() //suppresses any internal loggers created by the above elements LogManager.getLogManager.reset() //suppresses any internal loggers created by the above elements
(guid, uns, taskResolver, TestProbe()) (guid, uns, TestProbe())
} }
/** /**
* @see `UniqueNumberSystem.AllocateNumberPoolActors(NumberPoolHub)(implicit ActorContext)` * @see `UniqueNumberSetup.AllocateNumberPoolActors(NumberPoolHub)(implicit ActorContext)`
*/ */
def AllocateNumberPoolActors(poolSource: NumberPoolHub)(implicit system: ActorSystem): Map[String, ActorRef] = { def AllocateNumberPoolActors(poolSource: NumberPoolHub)(implicit system: ActorSystem): Map[String, ActorRef] = {
poolSource.Pools poolSource.Pools
.map({ .map {
case ((pname, pool)) => case (pname, pool) =>
pname -> system.actorOf(Props(classOf[NumberPoolActor], pool), pname) pname -> system.actorOf(Props(classOf[NumberPoolActor], pool), pname)
}) }
.toMap .toMap
} }
} }

View file

@ -3,20 +3,22 @@ package objects.guidtask
import base.ActorTest import base.ActorTest
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
import scala.concurrent.duration._
class GUIDTaskUnregisterAmmoTest extends ActorTest { class GUIDTaskUnregisterAmmoTest extends ActorTest {
"UnregisterEquipment -> UnregisterObjectTask" in { "UnregisterEquipment -> UnregisterObjectTask" in {
val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val (guid, uns, probe) = GUIDTaskTest.CommonTestSetup
val obj = AmmoBox(GlobalDefinitions.energy_cell) val obj = AmmoBox(GlobalDefinitions.energy_cell)
guid.register(obj, "dynamic") guid.register(obj, name = "ammo")
assert(obj.HasGUID) assert(obj.HasGUID)
taskResolver ! TaskResolver.GiveTask( TaskWorkflow.execute(TaskBundle(
new GUIDTaskTest.RegisterTestTask(probe.ref), new GUIDTaskTest.RegisterTestTask(probe.ref),
List(GUIDTask.UnregisterEquipment(obj)(uns)) GUIDTask.unregisterEquipment(uns, obj)
) ))
probe.expectMsg(scala.util.Success) probe.expectMsg(5.second, scala.util.Success(true))
assert(!obj.HasGUID) assert(!obj.HasGUID)
} }
} }

Some files were not shown because too many files have changed in this diff Show more