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.
The ServiceManager must not only be set up correctly, but must be given a TaskResolver.
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 ServiceManager must be set up correctly.
The Zone needs to be set up and initialized properly with a ZoneActor.
The ZoneActor builds the GUID Actor and the ZonePopulationActor.

View file

@ -46,11 +46,6 @@ akka.actor.deployment {
dispatcher = galaxy-service
}
# Isolate tasks
"/service/taskResolver*" {
dispatcher = task-dispatcher
}
# Bottleneck (dedicated thread)
"/service/cluster" {
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.entity.{SimpleWorldEntity, WorldEntity}
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.locker.LockerContainer
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
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
case Zone.Deployable.IsDismissed(obj) =>
continent.tasks ! GUIDTask.UnregisterObjectTask(obj)(continent.GUID)
TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, obj))
case ICS.ZonesResponse(zones) =>
zones.foreach { zone =>
@ -1156,9 +1156,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
player.avatar = avatar
interstellarFerry match {
case Some(vehicle) if vehicle.PassengerInSeat(player).contains(0) =>
continent.tasks ! RegisterDrivenVehicle(vehicle, player)
TaskWorkflow.execute(registerDrivenVehicle(vehicle, player))
case _ =>
continent.tasks ! RegisterNewAvatar(player)
TaskWorkflow.execute(registerNewAvatar(player))
}
case NewPlayerLoaded(tplayer) =>
@ -1997,13 +1997,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case (_, guid) => sendResponse(ObjectDeleteMessage(guid, 0))
}
//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
if (maxhand) {
continent.tasks ! HoldNewEquipmentUp(player)(
TaskWorkflow.execute(HoldNewEquipmentUp(player)(
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
0
)
))
}
//draw free hand
player.FreeHand.Equipment match {
@ -2075,14 +2075,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
(old_holsters ++ old_inventory).foreach {
case (obj, guid) =>
sendResponse(ObjectDeleteMessage(guid, 0))
continent.tasks ! GUIDTask.UnregisterEquipment(obj)(continent.GUID)
TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj))
}
//redraw
if (maxhand) {
continent.tasks ! HoldNewEquipmentUp(player)(
TaskWorkflow.execute(HoldNewEquipmentUp(player)(
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
0
)
))
}
ApplyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory)
DropLeftovers(player)(drops)
@ -2166,7 +2166,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
if (Avatar.purchaseCooldowns.contains(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))
case None =>
avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition)
continent.tasks ! BuyNewEquipmentPutInInventory(
TaskWorkflow.execute(BuyNewEquipmentPutInInventory(
continent.GUID(tplayer.VehicleSeated) match { case Some(v : Vehicle) => v; case _ => player },
tplayer,
msg.terminal_guid
)(item)
)(item))
}
case Terminal.SellEquipment() =>
@ -2637,7 +2637,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
entry.obj.Faction = tplayer.Faction
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))
case _ =>
log.error(
@ -2921,7 +2921,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
(old_weapons ++ old_inventory).foreach {
case (obj, eguid) =>
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)
} else if (accessedContainer.contains(target)) {
@ -4406,7 +4406,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
continent.id,
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.Faction = player.Faction
dObj.AssignOwnership(player)
val tasking: TaskResolver.GiveTask = dObj match {
val tasking: TaskBundle = dObj match {
case turret: TurretDeployable =>
GUIDTask.RegisterDeployableTurret(turret)(continent.GUID)
GUIDTask.registerDeployableTurret(continent.GUID, turret)
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) =>
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.
* `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`
* @return a `TaskResolver.GiveTask` message
* @return a `TaskBundle` message
*/
private def RegisterNewAvatar(tplayer: Player): TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private def registerNewAvatar(tplayer: Player): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
private val localPlayer = tplayer
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 = {
if (localPlayer.HasGUID) {
Task.Resolution.Success
} 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
def action(): Future[Any] = {
localAnnounce ! NewPlayerLoaded(localPlayer)
Future(true)
}
},
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.
* `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`
* @return a `TaskResolver.GiveTask` message
* @return a `TaskBundle` message
*/
private def RegisterAvatar(tplayer: Player): TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private def registerAvatar(tplayer: Player): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
private val localPlayer = tplayer
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 = {
if (localPlayer.HasGUID) {
Task.Resolution.Success
} 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
def action(): Future[Any] = {
localAnnounce ! PlayerLoaded(localPlayer)
Future(true)
}
},
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.
* @param vehicle the `Vehicle` object
* @see `RegisterVehicleFromSpawnPad`
* @return a `TaskResolver.GiveTask` message
* @return a `TaskBundle` message
*/
def RegisterVehicle(vehicle: Vehicle): TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private def registerVehicle(vehicle: Vehicle): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
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 = {
if (localVehicle.HasGUID) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
log.trace(s"Vehicle $localVehicle is registered")
resolver ! Success(this)
def action(): Future[Any] = {
Future(true)
}
},
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
* but the said driver does not know about the vehicle through his usual convention - `VehicleSeated` - yet.
* @see `GlobalDefinitions.droppod`
* @see `GUIDTask.RegisterObjectTask`
* @see `GUIDTask.registerObject`
* @see `interstellarFerry`
* @see `Player.VehicleSeated`
* @see `PlayerLoaded`
* @see `TaskResolver.GiveTask`
* @see `TaskBundle`
* @see `Vehicles.Own`
* @param vehicle the unregistered droppod
* @param tplayer the player using the droppod for instant action;
* 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 = {
TaskResolver.GiveTask(
new Task() {
private def registerDroppod(vehicle: Vehicle, tplayer: Player): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
private val localDriver = tplayer
private val localVehicle = vehicle
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 = {
if (localVehicle.HasGUID) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
log.trace(s"Vehicle $localVehicle is registered")
def action(): Future[Any] = {
localDriver.VehicleSeated = localVehicle.GUID
Vehicles.Own(localVehicle, 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`.
* @param obj the `Vehicle` object
* @see `RegisterVehicle`
* @return a `TaskResolver.GiveTask` message
* @return a `TaskBundle` message
*/
def RegisterVehicleFromSpawnPad(obj: Vehicle, pad: VehicleSpawnPad, terminal: Terminal): TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private val localVehicle = obj
private def registerVehicleFromSpawnPad(vehicle: Vehicle, pad: VehicleSpawnPad, terminal: Terminal): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
private val localVehicle = vehicle
private val localPad = pad.Actor
private val localTerminal = terminal
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 = {
if (localVehicle.HasGUID) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
def action(): Future[Any] = {
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 = {
TaskResolver.GiveTask(
new Task() {
private val localVehicle = obj
private def registerDrivenVehicle(vehicle: Vehicle, driver: Player): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
private val localVehicle = vehicle
private val localDriver = driver
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 = {
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")
def action(): Future[Any] = {
localDriver.VehicleSeated = localVehicle.GUID
Vehicles.Own(localVehicle, localDriver)
localAnnounce ! NewPlayerLoaded(localDriver) //alerts WorldSessionActor
resolver ! Success(this)
}
override def onFailure(ex: Throwable): Unit = {
localAnnounce ! PlayerFailedToLoad(localDriver) //alerts SessionActor
localAnnounce ! NewPlayerLoaded(localDriver)
Future(true)
}
},
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,
* 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
* @return a `TaskResolver.GiveTask` message
* @return a `TaskBundle` message
*/
def RegisterProjectile(obj: Projectile): TaskResolver.GiveTask = {
val definition = obj.Definition
TaskResolver.GiveTask(
new Task() {
private def registerProjectile(obj: Projectile): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
private val globalProjectile = obj
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 = {
if (globalProjectile.HasGUID) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
def action(): Future[Any] = {
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 = {
TaskResolver.GiveTask(
new Task() {
private val localVehicle = obj
private val localDriver = driver
private def unregisterDrivenVehicle(vehicle: Vehicle, driver: Player): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
private val localVehicle = vehicle
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 = {
if (!localVehicle.HasGUID && !localDriver.HasGUID) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
resolver ! Success(this)
def action(): Future[Any] = {
Future(true)
}
},
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,
* 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
* @return a `TaskResolver.GiveTask` message
* @return a `TaskBundle` message
*/
def UnregisterProjectile(obj: Projectile): TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private def unregisterProjectile(obj: Projectile): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
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))
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 = {
if (!globalProjectile.HasGUID) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
def action(): Future[Any] = {
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 already registered, unregister it and then register it again.
* @see `RegisterProjectile(Projectile)`
* @see `UnregisterProjectile(Projectile)`
* @see `registerProjectile(Projectile)`
* @see `unregisterProjectile(Projectile)`
* @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 = {
val reg = RegisterProjectile(obj)
def reregisterProjectile(obj: Projectile): TaskBundle = {
val reg = registerProjectile(obj)
if (obj.HasGUID) {
TaskResolver.GiveTask(
reg.task,
List(
TaskResolver.GiveTask(
reg.subs(0).task,
List(UnregisterProjectile(obj))
)
TaskBundle(
reg.mainTask,
TaskBundle(
reg.subTasks(0).mainTask,
unregisterProjectile(obj)
)
)
} else {
@ -6371,13 +6281,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case veh: Vehicle => ModifyAmmunitionInVehicle(veh)
case _ => ModifyAmmunition(obj)
}
val stowNewFunc: Equipment => TaskResolver.GiveTask = PutNewEquipmentInInventoryOrDrop(obj)
val stowFunc: Equipment => Future[Any] = PutEquipmentInInventoryOrDrop(obj)
val stowNewFunc: Equipment => TaskBundle = PutNewEquipmentInInventoryOrDrop(obj)
val stowFunc: Equipment => Future[Any] = PutEquipmentInInventoryOrDrop(obj)
xs.foreach(item => {
obj.Inventory -= item.start
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
@ -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"
)
val boxForInventory = AmmoBox(box.Definition, splitReloadAmmo)
continent.tasks ! stowNewFunc(boxForInventory)
TaskWorkflow.execute(stowNewFunc(boxForInventory))
fullMagazine
}
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 _ :: toUpdate =>
modifyFunc(previousBox, 0) //update to changed capacity value
toUpdate.foreach(box => { continent.tasks ! stowNewFunc(box) })
toUpdate.foreach(box => { TaskWorkflow.execute(stowNewFunc(box)) })
}
} 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)
}
/**
* 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.
* @param tool a weapon
@ -7274,7 +7158,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* @see `AvatarAction.Release`
* @see `AvatarServiceMessage`
* @see `FriskDeadBody`
* @see `GUIDTask.UnregisterPlayer`
* @see `GUIDTask.unregisterPlayer`
* @see `ObjectDeleteMessage`
* @see `WellLootedDeadBody`
* @see `Zone.Corpse.Add`
@ -7291,7 +7175,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
zone.Population ! Zone.Population.Release(avatar)
sendResponse(ObjectDeleteMessage(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)
} else if (projectile.tool_def.Size == EquipmentSize.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 =>
avatarActor ! AvatarActor.ConsumeStamina(10)
ProjectileQuality.Modified(25f)
@ -8210,7 +8094,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
if (!zoneReload && zoneId == continent.id) {
if (player.isBackpack) { // important! test the actor-wide player ref, not the parameter
// respawning from unregistered player
continent.tasks ! RegisterAvatar(targetPlayer)
TaskWorkflow.execute(registerAvatar(targetPlayer))
} else {
// move existing player; this is the one case where the original GUID is retained by the player
self ! PlayerLoaded(targetPlayer)
@ -8220,15 +8104,15 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
val original = player
if (player.isBackpack) {
session = session.copy(player = targetPlayer)
taskThenZoneChange(
GUIDTask.UnregisterObjectTask(original.avatar.locker)(continent.GUID),
TaskWorkflow.execute(taskThenZoneChange(
GUIDTask.unregisterObject(continent.GUID, original.avatar.locker),
ICS.FindZone(_.id == zoneId, context.self)
)
))
} else if (player.HasGUID) {
taskThenZoneChange(
GUIDTask.UnregisterAvatar(original)(continent.GUID),
TaskWorkflow.execute(taskThenZoneChange(
GUIDTask.unregisterAvatar(continent.GUID, original),
ICS.FindZone(_.id == zoneId, context.self)
)
))
} else {
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 (vehicle.Definition == GlobalDefinitions.droppod) {
//instant action droppod in the same zone
continent.tasks ! RegisterDroppod(vehicle, player)
TaskWorkflow.execute(registerDroppod(vehicle, player))
} else {
//transferring a vehicle between spawn points (warp gates) in the same zone
self ! PlayerLoaded(player)
@ -8325,10 +8209,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
} else if (vehicle.Definition == GlobalDefinitions.droppod) {
LoadZoneCommonTransferActivity()
player.Continent = zoneId //forward-set the continent id to perform a test
taskThenZoneChange(
GUIDTask.UnregisterAvatar(player)(continent.GUID),
TaskWorkflow.execute(taskThenZoneChange(
GUIDTask.unregisterAvatar(continent.GUID, player),
ICS.FindZone(_.id == zoneId, context.self)
)
))
} else {
UnaccessContainer(vehicle)
LoadZoneCommonTransferActivity()
@ -8347,10 +8231,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
//unregister vehicle and driver whole + GiveWorld
continent.Transport ! Zone.Vehicle.Despawn(vehicle)
taskThenZoneChange(
UnregisterDrivenVehicle(vehicle, player),
TaskWorkflow.execute(taskThenZoneChange(
unregisterDrivenVehicle(vehicle, player),
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.
* This vehicle can be deleted for everyone if no more work can be detected.
*
* @see `GUIDTask.UnregisterPlayer`
* @see `GUIDTask.unregisterPlayer`
* @see `LoadZoneCommonTransferActivity`
* @see `Vehicles.AllGatedOccupantsInSameZone`
* @see `PlayerLoaded`
@ -8393,10 +8277,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
val continentId = continent.id
interstellarFerryTopLevelGUID = None
taskThenZoneChange(
GUIDTask.UnregisterAvatar(player)(continent.GUID),
TaskWorkflow.execute(taskThenZoneChange(
GUIDTask.unregisterAvatar(continent.GUID, player),
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). */
def taskThenZoneChange(
task: TaskResolver.GiveTask,
zoneMessage: ICS.FindZone
): Unit = {
continent.tasks ! TaskResolver.GiveTask(
new Task() {
override def isComplete: Task.Resolution.Value = task.task.isComplete
task: TaskBundle,
zoneMessage: ICS.FindZone
): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
val localAvatar = avatar
val localZone = continent
val localCluster = cluster
def Execute(resolver: ActorRef): Unit = {
continent.Population ! Zone.Population.Leave(avatar)
override def description() : String = s"doing ${task.description()} before transferring zones"
def action(): Future[Any] = {
continent.Population ! Zone.Population.Leave(localAvatar)
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)
}
RemoveBoomerTriggersFromInventory().foreach(obj => {
continent.tasks ! GUIDTask.UnregisterObjectTask(obj)(continent.GUID)
TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, obj))
})
Deployables.Disown(continent, avatar, self)
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,
AvatarAction.ProjectileExplodes(player.GUID, projectile_guid, projectile)
)
continent.tasks ! UnregisterProjectile(projectile)
TaskWorkflow.execute(unregisterProjectile(projectile))
projectiles(local_index) match {
case Some(obj) if !obj.isResolved => obj.Miss()
case _ => ;
@ -8933,7 +8821,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
tplayer.VehicleSeated = None
zone.Population ! Zone.Population.Release(avatar)
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(
s"WeaponFireMessage: ${player.Name}'s ${projectile_info.Name} is a remote projectile"
)
continent.tasks ! (if (projectile.HasGUID) {
if (projectile.HasGUID) {
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.ProjectileExplodes(player.GUID, projectile.GUID, projectile)
)
ReregisterProjectile(projectile)
TaskWorkflow.execute(reregisterProjectile(projectile))
} else {
RegisterProjectile(projectile)
})
TaskWorkflow.execute(registerProjectile(projectile))
}
}
projectilesToCleanUp(projectileIndex) = false

View file

@ -4,7 +4,7 @@ import akka.actor.ActorRef
import akka.pattern.{AskTimeoutException, ask}
import akka.util.Timeout
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.locker.LockerContainer
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.
* Item swapping during the placement is not allowed.
* @see `ChangeAmmoMessage`
* @see `GUIDTask.RegisterEquipment`
* @see `GUIDTask.registerEquipment`
* @see `PutEquipmentInInventoryOrDrop`
* @see `Task`
* @see `TaskResolver.GiveTask`
* @see `TaskBundle`
* @param obj the container
* @param item the item being manipulated
* @return a `TaskResolver` object
* @return a `TaskBundle` object
*/
def PutNewEquipmentInInventorySlot(
obj: PlanetSideServerObject with Container
)(item: Equipment, slot: Int): TaskResolver.GiveTask = {
)(item: Equipment, slot: Int): TaskBundle = {
val localZone = obj.Zone
TaskResolver.GiveTask(
new Task() {
TaskBundle(
new StraightforwardTask() {
private val localContainer = obj
private val localItem = item
private val localSlot = slot
override def isComplete: Task.Resolution.Value = Task.Resolution.Success
def Execute(resolver: ActorRef): Unit = {
def action(): Future[Any] = {
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.
* Item swapping during the placement is not allowed.
* @see `ChangeAmmoMessage`
* @see `GUIDTask.RegisterEquipment`
* @see `GUIDTask.registerEquipment`
* @see `PutEquipmentInInventoryOrDrop`
* @see `Task`
* @see `TaskResolver.GiveTask`
* @see `TaskBundle`
* @param obj the container
* @param item the item being manipulated
* @return a `TaskResolver` object
* @return a `TaskBundle` object
*/
def PutNewEquipmentInInventoryOrDrop(
obj: PlanetSideServerObject with Container
)(item: Equipment): TaskResolver.GiveTask = {
)(item: Equipment): TaskBundle = {
val localZone = obj.Zone
TaskResolver.GiveTask(
new Task() {
TaskBundle(
new StraightforwardTask() {
private val localContainer = obj
private val localItem = item
override def isComplete: Task.Resolution.Value = Task.Resolution.Success
def Execute(resolver: ActorRef): Unit = {
def action(): Future[Any] = {
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 `Future.onComplete`
* @see `Future.recover`
* @see `GUIDTask.UnregisterEquipment`
* @see `GUIDTask.unregisterEquipment`
* @see `tell`
* @see `Zone.AvatarEvents`
* @param obj the container
@ -165,7 +159,7 @@ object WorldSession {
val result = ask(localContainer.Actor, Containable.PutItemInSlotOnly(localItem, slot))
result.onComplete {
case Failure(_) | Success(_: Containable.CanNotPutItemInSlot) =>
localContainer.Zone.tasks ! GUIDTask.UnregisterEquipment(localItem)(localContainer.Zone.GUID)
TaskWorkflow.execute(GUIDTask.unregisterEquipment(localContainer.Zone.GUID, localItem))
case _ => ;
}
result
@ -177,43 +171,33 @@ object WorldSession {
* 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.
* Item swapping during the placement is not allowed.
* @see `GUIDTask.RegisterEquipment`
* @see `GUIDTask.registerEquipment`
* @see `PutEquipmentInInventorySlot`
* @see `Task`
* @see `TaskResolver.GiveTask`
* @see `TaskBundle`
* @param obj the container
* @param item the item being manipulated
* @param slot where the item will be placed in the container
* @return a `TaskResolver` object
* @return a `TaskBundle` object
*/
def PutLoadoutEquipmentInInventory(
obj: PlanetSideServerObject with Container
)(item: Equipment, slot: Int): TaskResolver.GiveTask = {
)(item: Equipment, slot: Int): TaskBundle = {
val localZone = obj.Zone
TaskResolver.GiveTask(
new Task() {
TaskBundle(
new StraightforwardTask() {
private val localContainer = obj
private val localItem = item
private val localSlot = slot
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 = {
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 = {
def action(): Future[Any] = {
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 `Containable.CanNotPutItemInSlot`
* @see `Containable.PutItemInSlotOnly`
* @see `GUIDTask.RegisterEquipment`
* @see `GUIDTask.UnregisterEquipment`
* @see `GUIDTask.registerEquipment`
* @see `GUIDTask.unregisterEquipment`
* @see `Future.onComplete`
* @see `PutEquipmentInInventorySlot`
* @see `TerminalMessageOnTimeout`
@ -236,31 +220,22 @@ object WorldSession {
* @param player na
* @param term na
* @param item the item being manipulated
* @return a `TaskResolver` object
* @return a `TaskBundle` object
*/
def BuyNewEquipmentPutInInventory(
obj: PlanetSideServerObject with Container,
player: Player,
term: PlanetSideGUID
)(item: Equipment): TaskResolver.GiveTask = {
)(item: Equipment): TaskBundle = {
val localZone = obj.Zone
TaskResolver.GiveTask(
new Task() {
TaskBundle(
new StraightforwardTask() {
private val localContainer = obj
private val localItem = item
private val localPlayer = player
private val localTermMsg: Boolean => Unit = TerminalResult(term, localPlayer, TransactionType.Buy)
override def Timeout: Long = 1000
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 = {
def action(): Future[Any] = {
TerminalMessageOnTimeout(
ask(localContainer.Actor, Containable.PutItemAway(localItem)),
localTermMsg
@ -279,16 +254,16 @@ object WorldSession {
localTermMsg(true)
}
} else {
localContainer.Zone.tasks ! GUIDTask.UnregisterEquipment(localItem)(localContainer.Zone.GUID)
TaskWorkflow.execute(GUIDTask.unregisterEquipment(localContainer.Zone.GUID, localItem))
localTermMsg(false)
}
case _ =>
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 `Containable.CanNotPutItemInSlot`
* @see `Containable.PutItemInSlotOnly`
* @see `GUIDTask.RegisterEquipment`
* @see `GUIDTask.UnregisterEquipment`
* @see `GUIDTask.registerEquipment`
* @see `GUIDTask.unregisterEquipment`
* @see `Future.onComplete`
* @see `ObjectHeldMessage`
* @see `Player.DrawnSlot`
* @see `Player.LastDrawnSlot`
* @see `Service.defaultPlayerGUID`
* @see `TaskResolver.GiveTask`
* @see `TaskBundle`
* @see `Zone.AvatarEvents`
* @param player the player whose visible slot will be equipped and drawn
* @param item the item to equip
* @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)) {
val localZone = player.Zone
TaskResolver.GiveTask(
new Task() {
TaskBundle(
new StraightforwardTask() {
private val localPlayer = player
private val localGUID = player.GUID
private val localItem = item
private val localSlot = slot
override def Timeout: Long = 1000
override def isComplete: Task.Resolution.Value = {
if (localPlayer.DrawnSlot == localSlot)
Task.Resolution.Success
else
Task.Resolution.Incomplete
}
def Execute(resolver: ActorRef): Unit = {
def action(): Future[Any] = {
ask(localPlayer.Actor, Containable.PutItemInSlotOnly(localItem, localSlot))
.onComplete {
case Failure(_) | Success(_: Containable.CanNotPutItemInSlot) =>
localPlayer.Zone.tasks ! GUIDTask.UnregisterEquipment(localItem)(localZone.GUID)
TaskWorkflow.execute(GUIDTask.unregisterEquipment(localZone.GUID, localItem))
case _ =>
if (localPlayer.DrawnSlot != Player.HandsDownSlot) {
localPlayer.DrawnSlot = Player.HandsDownSlot
@ -365,10 +331,10 @@ object WorldSession {
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 {
//TODO log.error
@ -460,7 +426,7 @@ object WorldSession {
* @see `Containable.RemoveItemFromSlot`
* @see `Future.onComplete`
* @see `Future.recover`
* @see `GUIDTask.UnregisterEquipment`
* @see `GUIDTask.unregisterEquipment`
* @see `Zone.AvatarEvents`
* @param obj the container to search
* @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))
result.onComplete {
case Success(Containable.ItemFromSlot(_, Some(_), Some(_))) =>
localContainer.Zone.tasks ! GUIDTask.UnregisterEquipment(localItem)(localContainer.Zone.GUID)
TaskWorkflow.execute(GUIDTask.unregisterEquipment(localContainer.Zone.GUID, localItem))
case _ =>
}
result
@ -492,7 +458,7 @@ object WorldSession {
* @see `Containable.RemoveItemFromSlot`
* @see `Future.onComplete`
* @see `Future.recover`
* @see `GUIDTask.UnregisterEquipment`
* @see `GUIDTask.unregisterEquipment`
* @see `RemoveOldEquipmentFromInventory`
* @see `TerminalMessageOnTimeout`
* @see `TerminalResult`
@ -517,7 +483,7 @@ object WorldSession {
)
result.onComplete {
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)
case _ =>
localTermMsg(false)
@ -538,7 +504,7 @@ object WorldSession {
* @see `LockerContainer`
* @see `RemoveEquipmentFromLockerContainer`
* @see `StowEquipmentInLockerContainer`
* @see `TaskResolver`
* @see `TaskBundle`
* @param toChannel broadcast channel name for a manual packet callback
* @param source the container in which the item is to be removed
* @param destination the container into which the item is to be placed
@ -573,14 +539,14 @@ object WorldSession {
* @see `Container`
* @see `Equipment`
* @see `GridInventory.CheckCollisionsVar`
* @see `GUIDTask.RegisterEquipment`
* @see `GUIDTask.UnregisterEquipment`
* @see `GUIDTask.registerEquipment`
* @see `GUIDTask.unregisterEquipment`
* @see `IdentifiableEntity.Invalidate`
* @see `LockerContainer`
* @see `Service`
* @see `Task`
* @see `TaskResolver`
* @see `TaskResolver.GiveTask`
* @see `TaskBundle`
* @see `TaskBundle`
* @see `Zone.AvatarEvents`
* @param toChannel broadcast channel name for a manual packet callback
* @param source the container in which the item is to be removed
@ -610,7 +576,7 @@ object WorldSession {
(false, None)
}
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 localChannel = toChannel
val localSource = source
@ -621,21 +587,13 @@ object WorldSession {
val localMoveOnComplete: Try[Any] => Unit = {
case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) =>
//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 _ => ;
}
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 = {
if (localItem.HasGUID && localDestination.Find(localItem).contains(localDestSlot)) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
def action(): Future[Any] = {
localGUID match {
case Some(guid) =>
//see LockerContainerControl.RemoveItemFromSlotCallback
@ -647,15 +605,15 @@ object WorldSession {
}
val moveResult = ask(localDestination.Actor, Containable.PutItemInSlotOrAway(localItem, Some(localDestSlot)))
moveResult.onComplete(localMoveOnComplete)
resolver ! Success(this)
moveResult
}
}
val resultOnComplete: Try[Any] => Unit = {
case Success(Containable.ItemFromSlot(fromSource, Some(itemToMove), Some(fromSlot))) =>
destination.Zone.tasks ! TaskResolver.GiveTask(
TaskWorkflow.execute(TaskBundle(
moveItemTaskFunc(fromSlot),
List(GUIDTask.UnregisterEquipment(itemToMove)(fromSource.Zone.GUID))
)
GUIDTask.unregisterEquipment(fromSource.Zone.GUID, itemToMove)
))
case _ => ;
}
val result = ask(source.Actor, Containable.RemoveItemFromSlot(item))
@ -673,14 +631,14 @@ object WorldSession {
* @see `Container`
* @see `Equipment`
* @see `GridInventory.CheckCollisionsVar`
* @see `GUIDTask.RegisterEquipment`
* @see `GUIDTask.UnregisterEquipment`
* @see `GUIDTask.registerEquipment`
* @see `GUIDTask.unregisterEquipment`
* @see `IdentifiableEntity.Invalidate`
* @see `LockerContainer`
* @see `Service`
* @see `Task`
* @see `TaskResolver`
* @see `TaskResolver.GiveTask`
* @see `TaskBundle`
* @see `TaskBundle`
* @see `Zone.AvatarEvents`
* @param toChannel broadcast channel name for a manual packet callback
* @param source the container in which the item is to be removed
@ -695,8 +653,8 @@ object WorldSession {
item: Equipment,
dest: Int
): Unit = {
destination.Zone.tasks ! TaskResolver.GiveTask(
new Task() {
TaskWorkflow.execute(TaskBundle(
new StraightforwardTask() {
val localGUID = item.GUID //original GUID
val localChannel = toChannel
val localSource = source
@ -717,25 +675,16 @@ object WorldSession {
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 = {
if (localItem.HasGUID && localDestination.Find(localItem).isEmpty) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
}
def Execute(resolver: ActorRef): Unit = {
def action(): Future[Any] = {
val zone = localSource.Zone
//see LockerContainerControl.RemoveItemFromSlotCallback
zone.AvatarEvents ! AvatarServiceMessage(localChannel, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, localGUID))
localSource.Actor ! Containable.MoveItem(localDestination, localItem, localSlot)
resolver ! Success(this)
ask(localSource.Actor, Containable.MoveItem(localDestination, localItem, localSlot))
}
},
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 = {
TaskResolver.GiveTask(
new Task() {
private val localDesc = task.task.Description
def CallBackForTask(task: TaskBundle, sendTo: ActorRef, pass: Any): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
private val localDesc = task.description()
private val destination = sendTo
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
resolver ! Success(this)
Future(this)
}
},
List(task)
task
)
}
}

View file

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

View file

@ -9,6 +9,7 @@ import net.psforever.objects.definition.converter._
import net.psforever.objects.equipment._
import net.psforever.objects.geometry.GeometryForm
import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.locker.LockerContainerDefinition
import net.psforever.objects.serverobject.aura.Aura
import net.psforever.objects.serverobject.doors.DoorDefinition
import net.psforever.objects.serverobject.generator.GeneratorDefinition
@ -426,11 +427,7 @@ object GlobalDefinitions {
Equipment (locker_container, kits, ammunition, weapons)
*/
import net.psforever.packet.game.objectcreate.ObjectClass
val locker_container = new EquipmentDefinition(456) {
Name = "locker_container"
Size = EquipmentSize.Inventory
Packet = new LockerContainerConverter()
}
val locker_container = new LockerContainerDefinition()
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.definition.ExoSuitDefinition
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.loadouts.InfantryLoadout
import net.psforever.objects.zones.Zone
@ -341,7 +341,7 @@ object Players {
def commonDestroyConstructionItem(player: Player, tool: ConstructionItem, index: Int): Unit = {
val zone = player.Zone
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.converter.SmallTurretConverter
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.affinity.FactionAffinityBehavior
import net.psforever.objects.serverobject.damage.Damageable.Target
@ -132,6 +132,6 @@ class TurretControl(turret: TurretDeployable)
override def unregisterDeployable(obj: Deployable): Unit = {
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.definition.DeployAnimation
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.loadouts.Loadout
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
avatarActor ! AvatarActor.UpdateUseTime(kdef)
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.id,
AvatarAction.PlanetsideAttributeToAll(player.GUID, attribute, value)
@ -482,17 +482,17 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
obj.Trigger = trigger
//TODO sufficiently delete the tool
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 {
case Some(index) if player.VisibleSlots.contains(index) =>
player.Slot(index).Equipment = None
zone.tasks ! HoldNewEquipmentUp(player)(trigger, index)
TaskWorkflow.execute(HoldNewEquipmentUp(player)(trigger, index))
case Some(index) =>
player.Slot(index).Equipment = None
zone.tasks ! PutNewEquipmentInInventoryOrDrop(player)(trigger)
TaskWorkflow.execute(PutNewEquipmentInInventoryOrDrop(player)(trigger))
case None =>
//don't know where boomer trigger "should" go
zone.tasks ! PutNewEquipmentInInventoryOrDrop(player)(trigger)
TaskWorkflow.execute(PutNewEquipmentInInventoryOrDrop(player)(trigger))
}
Players.buildCooldownReset(zone, player.Name, obj)
case _ => ;

View file

@ -2,7 +2,7 @@
package net.psforever.objects.ce
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.definition.DeployAnimation
import net.psforever.objects.zones.Zone
@ -280,7 +280,7 @@ trait DeployableBehavior {
*/
def unregisterDeployable(obj: Deployable): Unit = {
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
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) {
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 var capacity: Int = 1
var repairAmount: Float = 0
registerAs = "ammo"
Name = "ammo box"
Size = EquipmentSize.Inventory
Packet = AmmoBoxDefinition.converter
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
Packet = AvatarDefinition.converter
Geometry = GeometryForm.representPlayerByCylinder(radius = 1.6f)
registerAs = "players"
}
object AvatarDefinition {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -61,6 +61,7 @@ class VehicleDefinition(objectId: Int)
Model = VehicleResolutions.calculate
RepairDistance = 10
RepairRestoresAt = 1
registerAs = "vehicles"
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
package net.psforever.objects.guid
import akka.actor.ActorRef
import akka.util.Timeout
import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.objects._
@ -10,6 +10,8 @@ import net.psforever.objects.locker.{LockerContainer, LockerEquipment}
import net.psforever.objects.serverobject.turret.WeaponTurret
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>
@ -22,43 +24,63 @@ import scala.annotation.tailrec
* 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>
* <br>
* All functions produce a `TaskResolver.GiveTask` container object
* or a list of `TaskResolver.GiveTask` container objects that is expected to be used by a `TaskResolver` `Actor`.
* All functions produce a `TaskBundle` container object
* 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,
* 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.
*/
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>
* <br>
* Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers.
* 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.
* @param obj the object being registered
* @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 = {
TaskResolver.GiveTask(new Task() {
private val localObject = obj
private val localAccessor = guid
def registerObject(guid: UniqueNumberOps, obj: IdentifiableEntity): TaskBundle =
TaskBundle(RegisterObjectTask(guid, obj, "generic"))
override def Description: String = s"register $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.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 specific pool of numbers.
* Regardless of the complexity of the object provided to this function, only the current depth will be assigned a GUID.
* @param obj the object being registered
* @param guid implicit reference to a unique number system
* @return a `TaskBundle` message
*/
def registerObject(guid: UniqueNumberOps, obj: PlanetSideGameObject): TaskBundle =
TaskBundle(RegisterObjectTask(guid, obj, obj.Definition.registerAs))
/**
* 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.
* @param obj the `Tool` object being registered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterEquipment`
* @return a `TaskResolver.GiveTask` message
* @see `GUIDTask.registerEquipment`
* @return a `TaskBundle` message
*/
def RegisterTool(obj: Tool)(implicit guid: ActorRef): TaskResolver.GiveTask = {
val ammoTasks: List[TaskResolver.GiveTask] =
(0 until obj.MaxAmmoSlot).map(ammoIndex => RegisterObjectTask(obj.AmmoSlots(ammoIndex).Box)).toList
TaskResolver.GiveTask(RegisterObjectTask(obj).task, ammoTasks)
}
/**
* 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) })
def registerTool(guid: UniqueNumberOps, obj: Tool): TaskBundle = {
TaskBundle(
RegisterObjectTask(guid, obj),
(0 until obj.MaxAmmoSlot).map(ammoIndex => registerObject(guid, obj.AmmoSlots(ammoIndex).Box))
)
}
/**
@ -125,17 +120,52 @@ object GUIDTask {
* 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 `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 {
case tool: Tool =>
RegisterTool(tool)
case _ =>
RegisterObjectTask(obj)
case tool: Tool => registerTool(guid, tool)
case _ => registerObject(guid, 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>
* <br>
@ -150,13 +180,13 @@ object GUIDTask {
* a task built of lesser registration tasks and supporting tasks should be written instead.
* @param tplayer the `Player` object being registered
* @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 = {
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), RegisterEquipment)
val lockerTask = List(RegisterObjectTask(tplayer.avatar.locker))
val inventoryTasks = RegisterInventory(tplayer)
TaskResolver.GiveTask(RegisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks)
def registerAvatar(guid: UniqueNumberOps, tplayer: Player): TaskBundle = {
val holsterTasks = visibleSlotTaskBuilding(guid, tplayer.Holsters(), registerEquipment)
val lockerTask = List(registerObject(guid, tplayer.avatar.locker))
val inventoryTasks = registerInventory(guid, tplayer)
TaskBundle(RegisterObjectTask(guid, tplayer), holsterTasks ++ lockerTask ++ inventoryTasks)
}
/**
@ -165,12 +195,12 @@ object GUIDTask {
* Similar to `RegisterAvatar` but the locker components are skipped.
* @param tplayer the `Player` object being registered
* @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 = {
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), RegisterEquipment)
val inventoryTasks = RegisterInventory(tplayer)
TaskResolver.GiveTask(GUIDTask.RegisterObjectTask(tplayer)(guid).task, holsterTasks ++ inventoryTasks)
def registerPlayer(guid: UniqueNumberOps, tplayer: Player): TaskBundle = {
val holsterTasks = visibleSlotTaskBuilding(guid, tplayer.Holsters(), registerEquipment)
val inventoryTasks = registerInventory(guid, tplayer)
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.
* @param vehicle the `Vehicle` object being registered
* @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 = {
val weaponTasks = VisibleSlotTaskBuilding(vehicle.Weapons.values, RegisterEquipment)
def registerVehicle(guid: UniqueNumberOps, vehicle: Vehicle): TaskBundle = {
val weaponTasks = visibleSlotTaskBuilding(guid, vehicle.Weapons.values, registerEquipment)
val utilTasks =
Vehicle.EquipmentUtilities(vehicle.Utilities).values.map(util => { RegisterObjectTask(util()) }).toList
val inventoryTasks = RegisterInventory(vehicle)
TaskResolver.GiveTask(RegisterObjectTask(vehicle).task, weaponTasks ++ utilTasks ++ inventoryTasks)
Vehicle.EquipmentUtilities(vehicle.Utilities).values.map(util => { registerObject(guid, util()) }).toList
val inventoryTasks = registerInventory(guid, vehicle)
TaskBundle(RegisterObjectTask(guid, vehicle), weaponTasks ++ utilTasks ++ inventoryTasks)
}
def RegisterDeployableTurret(
obj: PlanetSideGameObject with WeaponTurret
)(implicit guid: ActorRef): TaskResolver.GiveTask = {
TaskResolver.GiveTask(
RegisterObjectTask(obj).task,
VisibleSlotTaskBuilding(obj.Weapons.values, GUIDTask.RegisterEquipment) ++ RegisterInventory(obj)
def registerDeployableTurret(guid: UniqueNumberOps, obj: PlanetSideGameObject with WeaponTurret): TaskBundle = {
TaskBundle(
RegisterObjectTask(guid, obj),
visibleSlotTaskBuilding(guid, obj.Weapons.values, registerEquipment) ++ registerInventory(guid, 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>
* <br>
@ -214,73 +258,10 @@ object GUIDTask {
* It is the most basic operation that all objects that can have their GUIDs revoked must perform.
* @param obj the object being unregistered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterObjectTask`
* @return a `TaskResolver.GiveTask` message
* @see `GUIDTask.registerObjectTask`
* @return a `TaskBundle` message
*/
def UnregisterObjectTask(obj: IdentifiableEntity)(implicit guid: ActorRef): TaskResolver.GiveTask = {
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) })
}
def unregisterObject(guid: UniqueNumberOps, obj: IdentifiableEntity): TaskBundle = TaskBundle(UnregisterObjectTask(guid, obj))
/**
* 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`.
* @param obj the `Equipment` object being unregistered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterEquipment`
* @return a `TaskResolver.GiveTask` message
* @see `GUIDTask.registerEquipment`
* @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 {
case tool: Tool =>
UnregisterTool(tool)
case _ =>
UnregisterObjectTask(obj)
case tool: Tool => unregisterTool(guid, tool)
case _ => unregisterObject(guid, 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>
* <br>
* This task performs an operation that reverses the effect of `RegisterAvatar`.
* @param tplayer the `Player` object being unregistered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterAvatar`
* @return a `TaskResolver.GiveTask` message
* @see `GUIDTask.registerAvatar`
* @return a `TaskBundle` message
*/
def UnregisterAvatar(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = {
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), UnregisterEquipment)
val lockerTask = List(UnregisterObjectTask(tplayer.avatar.locker))
val inventoryTasks = UnregisterInventory(tplayer)
TaskResolver.GiveTask(UnregisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks)
def unregisterAvatar(guid: UniqueNumberOps, tplayer: Player): TaskBundle = {
val holsterTasks = visibleSlotTaskBuilding(guid, tplayer.Holsters(), unregisterEquipment)
val lockerTask = List(unregisterObject(guid, tplayer.avatar.locker))
val inventoryTasks = unregisterInventory(guid, tplayer)
TaskBundle(UnregisterObjectTask(guid, tplayer), holsterTasks ++ lockerTask ++ inventoryTasks)
}
/**
@ -324,13 +361,13 @@ object GUIDTask {
* This task performs an operation that reverses the effect of `RegisterPlayer`.
* @param tplayer the `Player` object being unregistered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterAvatar`
* @return a `TaskResolver.GiveTask` message
* @see `GUIDTask.registerAvatar`
* @return a `TaskBundle` message
*/
def UnregisterPlayer(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = {
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), UnregisterEquipment)
val inventoryTasks = UnregisterInventory(tplayer)
TaskResolver.GiveTask(GUIDTask.UnregisterObjectTask(tplayer).task, holsterTasks ++ inventoryTasks)
def unregisterPlayer(guid: UniqueNumberOps, tplayer: Player): TaskBundle = {
val holsterTasks = visibleSlotTaskBuilding(guid, tplayer.Holsters(), unregisterEquipment)
val inventoryTasks = unregisterInventory(guid, tplayer)
TaskBundle(UnregisterObjectTask(guid, tplayer), holsterTasks ++ inventoryTasks)
}
/**
@ -339,26 +376,25 @@ object GUIDTask {
* This task performs an operation that reverses the effect of `RegisterVehicle`.
* @param vehicle the `Vehicle` object being unregistered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterVehicle`
* @return a `TaskResolver.GiveTask` message
* @see `GUIDTask.registerVehicle`
* @return a `TaskBundle` message
*/
def UnregisterVehicle(vehicle: Vehicle)(implicit guid: ActorRef): TaskResolver.GiveTask = {
val weaponTasks = VisibleSlotTaskBuilding(vehicle.Weapons.values, UnregisterEquipment)
def unregisterVehicle(guid: UniqueNumberOps, vehicle: Vehicle): TaskBundle = {
val weaponTasks = visibleSlotTaskBuilding(guid, vehicle.Weapons.values, unregisterEquipment)
val utilTasks =
Vehicle.EquipmentUtilities(vehicle.Utilities).values.map(util => { UnregisterObjectTask(util()) }).toList
val inventoryTasks = UnregisterInventory(vehicle)
TaskResolver.GiveTask(UnregisterObjectTask(vehicle).task, weaponTasks ++ utilTasks ++ inventoryTasks)
Vehicle.EquipmentUtilities(vehicle.Utilities).values.map(util => { unregisterObject(guid, util()) }).toList
val inventoryTasks = unregisterInventory(guid, vehicle)
TaskBundle(UnregisterObjectTask(guid, vehicle), weaponTasks ++ utilTasks ++ inventoryTasks)
}
def UnregisterDeployableTurret(
obj: PlanetSideGameObject with WeaponTurret
)(implicit guid: ActorRef): TaskResolver.GiveTask = {
TaskResolver.GiveTask(
UnregisterObjectTask(obj).task,
VisibleSlotTaskBuilding(obj.Weapons.values, GUIDTask.UnregisterEquipment) ++ UnregisterInventory(obj)
def unregisterDeployableTurret(guid: UniqueNumberOps, obj: PlanetSideGameObject with WeaponTurret): TaskBundle = {
TaskBundle(
UnregisterObjectTask(guid, obj),
visibleSlotTaskBuilding(guid, obj.Weapons.values, unregisterEquipment) ++ unregisterInventory(guid, obj)
)
}
//support
/**
* Construct tasking that allocates work upon encountered `Equipment` objects
* 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`;
* strictly either `RegisterEquipment` or `UnregisterEquipment`
* @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
guid: ActorRef
): List[TaskResolver.GiveTask] = {
recursiveVisibleSlotTaskBuilding(list.iterator, func)
private def visibleSlotTaskBuilding(
guid: UniqueNumberOps,
list: Iterable[EquipmentSlot],
func: (UniqueNumberOps, Equipment) => TaskBundle
): List[TaskBundle] = {
recursiveVisibleSlotTaskBuilding(guid, list.iterator, func)
}
/**
@ -386,18 +424,17 @@ object GUIDTask {
* @return a `List` of `Equipment` tasking
*/
@tailrec private def recursiveVisibleSlotTaskBuilding(
iter: Iterator[EquipmentSlot],
func: Equipment => TaskResolver.GiveTask,
list: List[TaskResolver.GiveTask] = Nil
)(implicit guid: ActorRef): List[TaskResolver.GiveTask] = {
guid: UniqueNumberOps,
iter: Iterator[EquipmentSlot],
func: (UniqueNumberOps, Equipment) => TaskBundle,
list: List[TaskBundle] = Nil
): List[TaskBundle] = {
if (!iter.hasNext) {
list
} else {
iter.next().Equipment match {
case Some(item) =>
recursiveVisibleSlotTaskBuilding(iter, func, list :+ func(item))
case None =>
recursiveVisibleSlotTaskBuilding(iter, func, list)
case Some(item) => recursiveVisibleSlotTaskBuilding(guid, iter, func, list :+ func(guid, item))
case None => recursiveVisibleSlotTaskBuilding(guid, iter, func, list)
}
}
}

View file

@ -2,7 +2,7 @@
package net.psforever.objects.guid
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.source.NumberSource
import net.psforever.types.PlanetSideGUID
@ -24,10 +24,7 @@ class NumberPoolHub(private val source: NumberSource) {
import scala.collection.mutable
private val hash: mutable.HashMap[String, NumberPool] = mutable.HashMap[String, NumberPool]()
private val bigpool: mutable.LongMap[String] = mutable.LongMap[String]()
hash += "generic" -> new GenericPool(bigpool, source.size)
source.finalizeRestrictions.foreach(i =>
bigpool += i.toLong -> ""
) //these numbers can never be pooled; the source can no longer restrict numbers
hash += "generic" -> GenericPool(bigpool, source.size, poolName = "generic")
/**
* 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
* @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 {
case Success(monitor) =>
monitor.Object = obj
@ -459,7 +456,7 @@ class NumberPoolHub(private val source: NumberSource) {
* @param number the number to return.
* @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>

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
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.
@ -12,7 +11,7 @@ import net.psforever.objects.guid.AvailabilityPolicy
class LoanedKey(private val guid: Int, private val key: Monitor) {
def GUID: Int = guid
def Policy: AvailabilityPolicy.Value = key.policy
def Policy: AvailabilityPolicy = key.policy
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
*/
def Object_=(obj: Option[IdentifiableEntity]): Option[IdentifiableEntity] = {
if (
key.policy == AvailabilityPolicy.Leased || (key.policy == AvailabilityPolicy.Restricted && key.obj.isEmpty)
) {
if (key.policy == AvailabilityPolicy.Leased) {
if (key.obj.isDefined) {
key.obj.get.Invalidate()
key.obj = None

View file

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

View file

@ -1,8 +1,6 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.guid.key
import net.psforever.objects.guid.AvailabilityPolicy
/**
* An unmodifiable reference to an active number monitor object (`Key`).
* @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) {
def GUID: Int = guid
def Policy: AvailabilityPolicy.Value = key.policy
def Policy: AvailabilityPolicy = key.policy
import net.psforever.objects.entity.IdentifiableEntity
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.util.{Failure, Success, Try}
class GenericPool(private val hub: mutable.LongMap[String], private val max: Int) extends NumberPool {
val numbers: mutable.ListBuffer[Int] = mutable.ListBuffer[Int]()
class GenericPool(
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
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_=(slctr: NumberSelector): Unit = {} //intentionally blank
def Selector_=(slctr: NumberSelector): Unit = { /* intentionally blank */ }
def Get(): Try[Int] = {
val specific = selector.SelectionIndex
selector.SelectionIndex = -1 //clear
if (specific == -1) {
val number = GenericPool.rand(hub.keys.toList, max)
hub += number.toLong -> "generic"
numbers += number
Success(number)
val number = selectionFunc(hub.keys.toList, max)
if (number > -1) {
hub += number.toLong -> poolName
numbers += number
Success(number)
} else {
Failure(new Exception("no numbers available in this pool"))
}
} else if (hub.get(specific).isEmpty) {
hub += specific.toLong -> "generic"
hub += specific.toLong -> poolName
numbers += specific
Success(specific)
} 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 {
/**
* 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>
@ -63,7 +109,7 @@ object GenericPool {
* @param domainSize how many numbers can be supported
* @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) {
//get a list of all assigned numbers with an appended min and max
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
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

View file

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

View file

@ -2,8 +2,7 @@
package net.psforever.objects.guid.source
import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.guid.key.{LoanedKey, SecureKey}
import net.psforever.objects.guid.AvailabilityPolicy
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.
@ -18,13 +17,14 @@ class MaxNumberSource(val max: Int) extends NumberSource {
}
private val ary: Array[Key] = Array.ofDim[Key](max + 1)
(0 to max).foreach(x => { ary(x) = new Key })
private var allowRestrictions: Boolean = true
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
@ -78,39 +78,14 @@ class MaxNumberSource(val max: Int) extends NumberSource {
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] = {
val leased = ary.filter(_.policy != AvailabilityPolicy.Available)
leased collect { case key if key.obj.isEmpty =>
key.policy = AvailabilityPolicy.Available
}
leased.toList collect { case key if key.obj.nonEmpty =>
key.policy = AvailabilityPolicy.Available
val out = key.obj.get
key.obj = None
out
}
ary.foreach { _.policy = AvailabilityPolicy.Available }
ary.collect {
case key if key.obj.nonEmpty =>
val obj = key.obj.get
key.obj = None
obj
}.toList
}
}

View file

@ -2,7 +2,7 @@
package net.psforever.objects.guid.source
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.
@ -30,6 +30,20 @@ trait NumberSource {
*/
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.
* @return the count
@ -42,6 +56,13 @@ trait NumberSource {
*/
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?
* @param number the number
@ -97,26 +118,9 @@ trait NumberSource {
*/
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.
* 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`
*/
def clear(): List[IdentifiableEntity]

View file

@ -2,8 +2,7 @@
package net.psforever.objects.guid.source
import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.guid.AvailabilityPolicy
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.
@ -27,9 +26,11 @@ class SpecificNumberSource(values: Iterable[Int]) extends NumberSource {
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
@ -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] = {
val leased = ary.values.filter(_.policy != AvailabilityPolicy.Available)
leased collect { case key if key.obj.isEmpty =>
key.policy = AvailabilityPolicy.Available
}
leased.toList collect { case key if key.obj.nonEmpty =>
key.policy = AvailabilityPolicy.Available
val out = key.obj.get
key.obj = None
out
}
ary.values.foreach { _.policy = AvailabilityPolicy.Available }
ary.values.collect {
case key if key.obj.nonEmpty =>
val obj = key.obj.get
key.obj = None
obj
}.toList
}
}

View file

@ -1,5 +1,5 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.guid.actor
package net.psforever.objects.guid.uns
import akka.actor.Actor
import net.psforever.objects.guid.pool.NumberPool
@ -21,27 +21,27 @@ class NumberPoolActor(pool: NumberPool) extends Actor {
private[this] val log = org.log4s.getLogger
def receive: Receive = {
case NumberPoolActor.GetAnyNumber(id) =>
case NumberPoolActor.GetAnyNumber() =>
sender() ! (pool.Get() match {
case Success(value) =>
NumberPoolActor.GiveNumber(value, id)
case Failure(ex) => ;
NumberPoolActor.NoNumber(ex, id)
NumberPoolActor.GiveNumber(value)
case Failure(ex) =>
NumberPoolActor.NoNumber(ex)
})
case NumberPoolActor.GetSpecificNumber(number, id) =>
case NumberPoolActor.GetSpecificNumber(number) =>
sender() ! (NumberPoolActor.GetSpecificNumber(pool, number) match {
case Success(value) =>
NumberPoolActor.GiveNumber(value, id)
NumberPoolActor.GiveNumber(value)
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 ex: Option[Throwable] = if (!result) { Some(new Exception("number was not returned")) }
else { None }
sender() ! NumberPoolActor.ReturnNumberResult(number, ex, id)
sender() ! NumberPoolActor.ReturnNumberResult(number, ex)
case msg =>
log.warn(s"Received an unexpected message - ${msg.toString}")
@ -52,36 +52,36 @@ object NumberPoolActor {
/**
* 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.
* @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.
* @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`.
* @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.
* 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 number the number requested
* @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 net.psforever.objects.avatar.SpecialCarry
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.pad.process.{VehicleSpawnControlBase, VehicleSpawnControlConcealPlayer}
import net.psforever.objects.zones.{Zone, ZoneAware, Zoning}
@ -516,7 +516,7 @@ object VehicleSpawnControl {
if (zone.Vehicles.contains(vehicle)) { //already added to zone
vehicle.Actor ! Vehicle.Deconstruct(Some(0.seconds))
} 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
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.serverobject.PlanetSideServerObject
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.types.ChatMessageType
import scala.util.Success
import scala.concurrent.Future
/**
* 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.Orientation = pad.Orientation
newShuttle.Faction = pad.Faction
zone.tasks ! OrbitalShuttlePadControl.registerShuttle(zone, newShuttle, self)
TaskWorkflow.execute(OrbitalShuttlePadControl.registerShuttle(zone, newShuttle, self))
context.become(shuttleTime)
case _ => ;
@ -127,33 +127,23 @@ object OrbitalShuttlePadControl {
* @param zone the zone the shuttle and the pad will occupy
* @param shuttle the vehicle that will be the shuttle
* @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 = {
TaskResolver.GiveTask(
new Task() {
def registerShuttle(zone: Zone, shuttle: Vehicle, ref: ActorRef): TaskBundle = {
import scala.concurrent.ExecutionContext.Implicits.global
TaskBundle(
new StraightforwardTask() {
private val localZone = zone
private val localShuttle = shuttle
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) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
def Execute(resolver : ActorRef) : Unit = {
def action() : Future[Any] = {
localZone.Transport.tell(Zone.Vehicle.Spawn(localShuttle), localSelf)
resolver ! Success(true)
Future(this)
}
override def onFailure(ex : Throwable) : Unit = {
super.onFailure(ex)
localSelf ! Zone.Vehicle.CanNotSpawn(localZone, localShuttle, ex.getMessage)
}
}, List(GUIDTask.RegisterVehicle(shuttle)(zone.GUID))
}, GUIDTask.registerVehicle(zone.GUID, shuttle)
)
}

View file

@ -13,6 +13,7 @@ import net.psforever.objects.serverobject.structures.AmenityDefinition
abstract class TerminalDefinition(objectId: Int) extends AmenityDefinition(objectId) {
Name = "terminal"
Packet = new TerminalConverter
registerAs = "terminals"
/**
* 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 {
Packet = new SpawnTubeConverter
registerAs = "terminals"
}
object SpawnTubeDefinition {

View file

@ -228,6 +228,7 @@ object Utility {
extends AmenityDefinition(DeployedItem.router_telepad_deployable.id)
with BaseDeployableDefinition {
Packet = new SmallDeployableConverter
registerAs = "terminals"
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.entity.WorldEntity
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.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
@ -407,7 +407,7 @@ class VehicleControl(vehicle: Vehicle)
CancelJammeredSound(vehicle)
CancelJammeredStatus(vehicle)
//unregister
zone.tasks ! GUIDTask.UnregisterVehicle(vehicle)(zone.GUID)
TaskWorkflow.execute(GUIDTask.unregisterVehicle(zone.GUID, vehicle))
//banished to the shadow realm
vehicle.Position = Vector3.Zero
//queue final deletion

View file

@ -2,16 +2,12 @@
package net.psforever.objects.zones
import akka.actor.{ActorContext, ActorRef, Props}
import akka.routing.RandomPool
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.entity.IdentifiableEntity
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.guid.{NumberPoolHub, TaskResolver}
import net.psforever.objects.guid.actor.UniqueNumberSystem
import net.psforever.objects.guid.{NumberPoolHub, UniqueNumberOps, UniqueNumberSetup}
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.inventory.Container
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.objects.avatar.Avatar
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.affinity.FactionAffinity
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. */
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
/** 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`. */
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.
* 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. */
private var ground: ActorRef = Default.Actor
private var taskResolver: ActorRef = Default.Actor
/**
*/
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.
* Second, all supporting `Actor` agents are created, e.g., `ground`.
* 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>
* Execution of this operation should be fail-safe.
* 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`
*/
def init(implicit context: ActorContext): Unit = {
if (accessor == ActorRef.noSender) {
if (unops == null) {
SetupNumberPools()
taskResolver = CreateTaskResolvers(context)
accessor = context.actorOf(
RandomPool(25).props(
Props(classOf[UniqueNumberSystem], this.guid, UniqueNumberSystem.AllocateNumberPoolActors(this.guid))
),
s"zone-$id-uns"
)
context.actorOf(Props(classOf[UniqueNumberSys], this, this.guid), s"zone-$id-uns")
ground = context.actorOf(Props(classOf[ZoneGroundActor], this, equipmentOnGround), s"zone-$id-ground")
deployables = context.actorOf(Props(classOf[ZoneDeployableActor], this, constructions), s"zone-$id-deployables")
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 = {
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 SetupNumberPools(): Unit = { /* override to tailor to suit requirements of zone */ }
def findSpawns(
faction: PlanetSideEmpire.Value,
@ -436,14 +408,14 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
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.
* @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 primary use of this function should be testing.
* 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;
* `false`, if the new pool can not be created because the system has already been started
*/
def AddPool(name: String, pool: Seq[Int]): Boolean = {
if (accessor == Default.Actor || accessor == null) {
guid.AddPool(name, pool.toList)
true
def AddPool(name: String, pool: Seq[Int]): Option[NumberPool] = {
if (unops == null) {
guid.AddPool(name, pool.toList) match {
case _: Exception => None
case out => Some(out)
}
} 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.
* @see `NumberPoolHub.RemovePool`
* @param name the name of the pool
* @return `true`, if the new pool is un-made;
* `false`, if the new pool can not be removed because the system has already been started
* @return `true`, if the pool is un-made;
* `false`, if the pool can not be removed (because the system has already been started?)
*/
def RemovePool(name: String): Boolean = {
if (accessor == Default.Actor) {
guid.RemovePool(name)
true
if (unops == null) {
guid.RemovePool(name).nonEmpty
} else {
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.
* 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
* @return the associated object, if it exists
* @see `NumberPoolHub(Int)`
@ -606,7 +579,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
private def BuildSupportObjects(): Unit = {
//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
map.turretToWeapon.foreach({
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 ...
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] = {
@ -836,11 +809,26 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
vehicleEvents = bus
VehicleEvents
}
}
def tasks: ActorRef = taskResolver
protected def CreateTaskResolvers(context: ActorContext, numberCreated: Int = 20): ActorRef = {
context.actorOf(RandomPool(numberCreated).props(Props[TaskResolver]()), s"zone-$id-taskResolver")
/**
* A local class for spawning `Actor`s to manage the number pools for this zone,
* create a number system operations class to access those pools within the context of registering and unregistering,
* 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
package net.psforever.services
import akka.actor.{ActorRef, Cancellable}
import net.psforever.objects.guid.TaskResolver
import akka.actor.Cancellable
import net.psforever.objects.guid.{StraightforwardTask, TaskBundle, TaskWorkflow}
import net.psforever.objects.zones.Zone
import net.psforever.objects.{Default, PlanetSideGameObject}
import net.psforever.types.Vector3
import net.psforever.services.support.{SimilarityComparator, SupportActor, SupportActorCaseConversions}
import scala.concurrent.Future
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.
@ -30,7 +30,7 @@ import scala.util.Success
* and finally unregistering it.
* 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.
@ -249,30 +249,23 @@ abstract class RemoverActor(val taskResolver: ActorRef) extends SupportActor[Rem
def SecondJob(entry: RemoverActor.Entry): Unit = {
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 = {
import net.psforever.objects.guid.Task
TaskResolver.GiveTask(
new Task() {
private val localEntry = entry
private val localAnnounce = self
def FinalTask(entry: RemoverActor.Entry): TaskBundle = {
import scala.concurrent.ExecutionContext.Implicits.global
TaskBundle(
new StraightforwardTask() {
// private val localEntry = entry
// private val localAnnounce = self
override def isComplete: Task.Resolution.Value =
if (!localEntry.obj.HasGUID) {
Task.Resolution.Success
} else {
Task.Resolution.Incomplete
}
def Execute(resolver: ActorRef): Unit = {
resolver ! Success(this)
def action(): Future[Any] = {
Future(this)
}
override def onFailure(ex: Throwable): Unit = {
localAnnounce ! RemoverActor.FailureToWork(localEntry, ex)
}
// override def onFailure(ex: Throwable): Unit = {
// localAnnounce ! RemoverActor.FailureToWork(localEntry, ex)
// }
},
List(DeletionTask(entry))
)
@ -319,7 +312,7 @@ abstract class RemoverActor(val taskResolver: ActorRef) extends SupportActor[Rem
* @see `GUIDTask`
* @param entry the entry
*/
def DeletionTask(entry: RemoverActor.Entry): TaskResolver.GiveTask
def DeletionTask(entry: RemoverActor.Entry): TaskBundle
}
object RemoverActor extends SupportActorCaseConversions {

View file

@ -6,7 +6,7 @@ import akka.actor.{Actor, ActorRef, Cancellable, Props}
import scala.collection.mutable
import scala.concurrent.duration._
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.avatar.Avatar
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.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)
}
@ -408,7 +409,8 @@ class PersistenceMonitor(name: String, squadService: ActorRef) extends Actor {
squadService.tell(Service.Leave(Some(avatar.id.toString)), context.parent)
Deployables.Disown(inZone, 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}")
}
}

View file

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

View file

@ -1,8 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.services.avatar.support
import akka.actor.ActorRef
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.objects.guid.{GUIDTask, TaskBundle}
import net.psforever.objects.Player
import net.psforever.types.ExoSuitType
import net.psforever.services.{RemoverActor, Service}
@ -10,7 +9,7 @@ import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import scala.concurrent.duration._
class CorpseRemovalActor(taskResolver: ActorRef) extends RemoverActor(taskResolver) {
class CorpseRemovalActor extends RemoverActor() {
final val FirstStandardDuration: FiniteDuration = 1 minute
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 DeletionTask(entry: RemoverActor.Entry): TaskResolver.GiveTask = {
def DeletionTask(entry: RemoverActor.Entry): TaskBundle = {
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
task
}

View file

@ -1,15 +1,14 @@
// Copyright (c) 2017 PSForever
package net.psforever.services.avatar.support
import akka.actor.ActorRef
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.avatar.{AvatarAction, AvatarServiceMessage}
import scala.concurrent.duration._
class DroppedItemRemover(taskResolver: ActorRef) extends RemoverActor(taskResolver) {
class DroppedItemRemover extends RemoverActor() {
final val FirstStandardDuration: FiniteDuration = 3 minutes
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 DeletionTask(entry: RemoverActor.Entry): TaskResolver.GiveTask = {
GUIDTask.UnregisterEquipment(entry.obj.asInstanceOf[Equipment])(entry.zone.GUID)
def DeletionTask(entry: RemoverActor.Entry): TaskBundle = {
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"
)
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(
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

View file

@ -1,8 +1,9 @@
package net.psforever.services.local.support
import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.login.WorldSession
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.structures.Building
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 scala.concurrent.duration.DurationInt
import scala.util.Success
/**
* 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)
var galaxyService: ActorRef = ActorRef.noSender
@ -105,14 +103,14 @@ class CaptureFlagManager(val taskResolver: ActorRef, zone: Zone) extends Actor{
socket.captureFlag = flag
TrackFlag(flag)
taskResolver ! CallBackForTask(
TaskResolver.GiveTask(GUIDTask.RegisterObjectTask(flag)(socket.Zone.GUID).task),
TaskWorkflow.execute(WorldSession.CallBackForTask(
GUIDTask.registerObject(socket.Zone.GUID, flag),
socket.Zone.LocalEvents,
LocalServiceMessage(
socket.Zone.id,
LocalAction.LluSpawned(PlanetSideGUID(-1), flag)
)
)
))
// Broadcast chat message for LLU spawn
val owner = flag.Owner.asInstanceOf[Building]
@ -184,7 +182,7 @@ class CaptureFlagManager(val taskResolver: ActorRef, zone: Zone) extends Actor{
// Unregister LLU from clients,
flag.Zone.LocalEvents ! LocalServiceMessage(flag.Zone.id, LocalAction.LluDespawned(PlanetSideGUID(-1), flag))
// 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 = {
@ -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 {

View file

@ -1,6 +1,6 @@
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.objects.serverobject.CommonMessages
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.
*/
class HackCaptureActor(val taskResolver: ActorRef) extends Actor {
class HackCaptureActor extends Actor {
private[this] val log = org.log4s.getLogger
private var clearTrigger: Cancellable = Default.Cancellable

View file

@ -1,10 +1,10 @@
// Copyright (c) 2017 PSForever
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.{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.turret.{FacilityTurret, TurretUpgrade, WeaponTurret}
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.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.Future
import scala.concurrent.duration._
import scala.util.Success
class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] {
var task: Cancellable = Default.Cancellable
@ -172,39 +172,34 @@ class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] {
val oldBoxesTask = oldBoxes
.filterNot { box => newBoxes.exists(_ eq box) }
.map(box => GUIDTask.UnregisterEquipment(box)(guid))
.map(box => GUIDTask.unregisterEquipment(guid, box))
.toList
val newBoxesTask = TaskResolver.GiveTask(
new Task() {
import scala.concurrent.ExecutionContext.Implicits.global
val newBoxesTask = TaskBundle(
new StraightforwardTask() {
private val localFunc: () => Unit = FinishUpgradingTurret(entry)
override def isComplete = Task.Resolution.Success
def Execute(resolver: ActorRef): Unit = {
resolver ! Success(this)
}
override def onSuccess(): Unit = {
super.onSuccess()
def action(): Future[Any] = {
localFunc()
Future(this)
}
},
newBoxes
.filterNot { box => oldBoxes.exists(_ eq box) }
.map(box => GUIDTask.RegisterEquipment(box)(guid))
.map(box => GUIDTask.registerEquipment(guid, box))
.toList
)
target.Zone.tasks ! TaskResolver.GiveTask(
new Task() {
TaskWorkflow.execute(TaskBundle(
new StraightforwardTask() {
private val tasks = oldBoxesTask
def Execute(resolver: ActorRef): Unit = {
tasks.foreach { resolver ! _ }
resolver ! Success(this)
def action(): Future[Any] = {
tasks.foreach { TaskWorkflow.execute }
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.ballistics.Projectile
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.generator.Generator
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(
"Id",
"ObjectName",
@ -585,44 +605,84 @@ object Zones {
}
}
lazy val zones: Seq[Zone] = ZoneInfo.values.map { info =>
new Zone(info.id, zoneMaps.find(_.name == info.map.value).get, info.value) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
lazy val zones: Seq[Zone] = {
val defaultGuids =
try {
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")) {
this.HotSpotCoordinateFunction = Zones.HotSpots.standardRemapping(info.map.scale, 80, 80)
this.HotSpotTimeFunction = Zones.HotSpots.standardTimeRules
Zones.initZoneAmenities(this)
ZoneInfo.values.map { info =>
val guids =
try {
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 {
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 _ => ()
}
new Zone(info.id, zoneMaps.find(_.name.equals(info.map.value)).get, info.value) {
private val addPoolsFunc: () => Unit = addPools(guids, zone = this)
// 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 _ => ()
override def SetupNumberPools() : Unit = addPoolsFunc()
override def init(implicit context: ActorContext): Unit = {
super.init(context)
if (!info.id.startsWith("tz")) {
this.HotSpotCoordinateFunction = Zones.HotSpots.standardRemapping(info.map.scale, 80, 80)
this.HotSpotTimeFunction = Zones.HotSpots.standardTimeRules
Zones.initZoneAmenities(this)
}
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 = {
initResourceSilos(zone)
initWarpGates(zone)

View file

@ -1614,7 +1614,6 @@ class DamageableVehicleDestroyMountedTest extends FreedContextActorTest {
override def Activity = activityProbe.ref
override def AvatarEvents = avatarProbe.ref
override def VehicleEvents = vehicleProbe.ref
override def tasks = catchall.ref
import akka.actor.typed.scaladsl.adapter._
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.{ConstructionItem, Deployables, GlobalDefinitions, Player}
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.zones.{Zone, ZoneDeployableActor, ZoneMap}
import net.psforever.packet.game._
@ -141,7 +141,6 @@ class DeployableBehaviorSetupOwnedP2Test extends FreedContextActorTest {
override def Deployables: ActorRef = deployables
override def Players = List(avatar)
override def LivePlayers = List(player)
override def tasks: ActorRef = eventsProbe.ref
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")
zone.Deployables ! Zone.Deployable.BuildByOwner(jmine, player, citem)
//assert(false, "test needs to be fixed")
val eventsMsgs = eventsProbe.receiveN(8, 10.seconds)
val eventsMsgs = eventsProbe.receiveN(7, 10.seconds)
eventsMsgs.head match {
case AvatarServiceMessage(
"TestCharacter1",
@ -220,11 +219,6 @@ class DeployableBehaviorSetupOwnedP2Test extends FreedContextActorTest {
case _ =>
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(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")
@ -244,7 +238,6 @@ class DeployableBehaviorDeconstructTest extends ActorTest {
GUID(guid)
override def AvatarEvents: ActorRef = eventsProbe.ref
override def LocalEvents: ActorRef = eventsProbe.ref
override def tasks: ActorRef = eventsProbe.ref
override def Deployables: ActorRef = deployables
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")
jmine.Actor ! Deployable.Deconstruct()
val eventsMsgs = eventsProbe.receiveN(3, 10.seconds)
val eventsMsgs = eventsProbe.receiveN(2, 10.seconds)
eventsMsgs.head match {
case LocalServiceMessage("test", LocalAction.EliminateDeployable(`jmine`, PlanetSideGUID(1), Vector3(1,2,3), 2)) => ;
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")
}
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")
}
}
@ -304,7 +293,6 @@ class DeployableBehaviorDeconstructOwnedTest extends FreedContextActorTest {
override def Deployables: ActorRef = deployables
override def Players = List(avatar)
override def LivePlayers = List(player)
override def tasks: ActorRef = eventsProbe.ref
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
}
@ -324,12 +312,12 @@ class DeployableBehaviorDeconstructOwnedTest extends FreedContextActorTest {
"DeployableBehavior" should {
"deconstruct and alert owner" in {
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(avatar.deployables.Contains(jmine), "owned deconstruct test - avatar does not own deployable")
jmine.Actor ! Deployable.Deconstruct()
val eventsMsgs = eventsProbe.receiveN(4, 10.seconds)
val eventsMsgs = eventsProbe.receiveN(3, 10.seconds)
eventsMsgs.head match {
case LocalServiceMessage("test", LocalAction.EliminateDeployable(`jmine`, PlanetSideGUID(1), Vector3(1,2,3), 2)) => ;
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")
}
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(!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 Players = List(avatar1, avatar2)
override def LivePlayers = List(player1, player2)
override def tasks: ActorRef = eventsProbe.ref
}
player1.Spawn()
player2.Spawn()
@ -408,7 +407,6 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest {
override def Deployables: ActorRef = deployables
override def Players = List(avatar1, avatar2)
override def LivePlayers = List(player1, player2)
override def tasks: ActorRef = eventsProbe.ref
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
}
@ -518,7 +516,6 @@ class ExplosiveDeployableDestructionTest extends ActorTest {
override def Deployables: ActorRef = deployables
override def Players = List(avatar1, avatar2)
override def LivePlayers = List(player1, player2)
override def tasks: ActorRef = eventsProbe.ref
}
player1.Spawn()
player1.Actor = player1Probe.ref

View file

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

View file

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

View file

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

View file

@ -2,19 +2,21 @@
package objects.guidtask
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 {
"RegisterObjectTask" in {
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
val obj = new GUIDTaskTest.TestObject
val (_, uns, probe) = GUIDTaskTest.CommonTestSetup
val obj = new GUIDTaskTest.TestObject
assert(!obj.HasGUID)
taskResolver ! TaskResolver.GiveTask(
TaskWorkflow.execute(TaskBundle(
new GUIDTaskTest.RegisterTestTask(probe.ref),
List(GUIDTask.RegisterObjectTask(obj)(uns))
)
probe.expectMsg(scala.util.Success)
GUIDTask.registerObject(uns, obj)
))
probe.expectMsg(5.second, scala.util.Success(true))
assert(obj.HasGUID)
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -2,50 +2,54 @@
package objects.guidtask
import java.util.logging.LogManager
import scala.util.Success
import akka.actor.{ActorRef, ActorSystem, Props}
import akka.testkit.TestProbe
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.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 {
class TestObject extends IdentifiableEntity
class RegisterTestTask(probe: ActorRef) extends Task {
def Execute(resolver: ActorRef): Unit = {
probe ! Success
resolver ! Success(this)
class RegisterTestTask(probe: ActorRef) extends StraightforwardTask {
def action(): Future[Any] = {
probe ! Success(true)
Future(this)(scala.concurrent.ExecutionContext.Implicits.global)
}
}
def CommonTestSetup(implicit system: ActorSystem): (NumberPoolHub, ActorRef, ActorRef, TestProbe) = {
import akka.actor.Props
import akka.routing.RandomPool
def CommonTestSetup(implicit system: ActorSystem): (NumberPoolHub, UniqueNumberOps, TestProbe) = {
import akka.testkit.TestProbe
val guid: NumberPoolHub = new NumberPoolHub(new MaxNumberSource(110))
guid.AddPool("dynamic", (1 to 100).toList).Selector = new RandomSelector //TODO name is hardcoded for now
val uns = system.actorOf(
RandomPool(25).props(Props(classOf[UniqueNumberSystem], guid, GUIDTaskTest.AllocateNumberPoolActors(guid))),
"uns"
)
val taskResolver = system.actorOf(RandomPool(15).props(Props[TaskResolver]()), "resolver")
val guid: NumberPoolHub = new NumberPoolHub(new MaxNumberSource(90))
guid.AddPool("players", (1 to 10).toList).Selector = new RandomSelector
guid.AddPool("lockers", (11 to 20).toList).Selector = new RandomSelector
guid.AddPool("ammo", (21 to 30).toList).Selector = new RandomSelector
guid.AddPool("tools", (31 to 40).toList).Selector = new RandomSelector
guid.AddPool("vehicles", (41 to 50).toList).Selector = new RandomSelector
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
(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] = {
poolSource.Pools
.map({
case ((pname, pool)) =>
.map {
case (pname, pool) =>
pname -> system.actorOf(Props(classOf[NumberPoolActor], pool), pname)
})
}
.toMap
}
}

View file

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

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