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

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

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 GUIDTaskUnregisterAvatarTest extends ActorTest {
"UnregisterAvatar" in {
val (guid, 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 (guid, 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
@ -21,12 +23,12 @@ class GUIDTaskUnregisterAvatarTest extends ActorTest {
val obj_locker = obj.Slot(5).Equipment.get
val obj_locker_ammo = AmmoBox(GlobalDefinitions.energy_cell)
obj_locker.asInstanceOf[LockerEquipment].Inventory += 0 -> obj_locker_ammo
guid.register(obj, "dynamic")
guid.register(obj_wep, "dynamic")
guid.register(obj_wep_ammo, "dynamic")
guid.register(obj_inv_ammo, "dynamic")
guid.register(obj_locker, "dynamic")
guid.register(obj_locker_ammo, "dynamic")
guid.register(obj, name = "players")
guid.register(obj_wep, name = "tools")
guid.register(obj_wep_ammo, name = "ammo")
guid.register(obj_inv_ammo, name = "ammo")
guid.register(obj_locker, name = "lockers")
guid.register(obj_locker_ammo, name = "amoo")
assert(obj.HasGUID)
assert(obj_wep.HasGUID)
@ -34,11 +36,11 @@ class GUIDTaskUnregisterAvatarTest 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.UnregisterAvatar(obj)(uns))
)
probe.expectMsg(scala.util.Success)
GUIDTask.unregisterAvatar(uns, obj)
))
probe.expectMsg(5.second, scala.util.Success(true))
assert(!obj.HasGUID)
assert(!obj_wep.HasGUID)
assert(!obj_wep_ammo.HasGUID)

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