mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
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:
parent
ce2a3f5422
commit
9841b7e97d
|
|
@ -491,11 +491,7 @@ class AvatarStowEquipmentTest extends ActorTest {
|
|||
|
||||
/*
|
||||
Preparation for these three Release tests is involved.
|
||||
The ServiceManager must not only be set up correctly, but must be given a TaskResolver.
|
||||
The AvatarService is started and that starts CorpseRemovalActor, an essential part of this test.
|
||||
The CorpseRemovalActor needs that TaskResolver created by the ServiceManager;
|
||||
but, another independent TaskResolver will be needed for manual parts of the test.
|
||||
(The ServiceManager's TaskResolver can be "borrowed" but that requires writing code to intercept it.)
|
||||
The ServiceManager must be set up correctly.
|
||||
The Zone needs to be set up and initialized properly with a ZoneActor.
|
||||
The ZoneActor builds the GUID Actor and the ZonePopulationActor.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
8
src/main/resources/guid-pools/c1.json
Normal file
8
src/main/resources/guid-pools/c1.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 937,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/c2.json
Normal file
8
src/main/resources/guid-pools/c2.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 1615,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/c3.json
Normal file
8
src/main/resources/guid-pools/c3.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 1128,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/c4.json
Normal file
8
src/main/resources/guid-pools/c4.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 904,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/c5.json
Normal file
8
src/main/resources/guid-pools/c5.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 635,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/c6.json
Normal file
8
src/main/resources/guid-pools/c6.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 908,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
74
src/main/resources/guid-pools/default.json
Normal file
74
src/main/resources/guid-pools/default.json
Normal 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"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/home1.json
Normal file
8
src/main/resources/guid-pools/home1.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 1161,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/home2.json
Normal file
8
src/main/resources/guid-pools/home2.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 1096,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/home3.json
Normal file
8
src/main/resources/guid-pools/home3.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 1074,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/i1.json
Normal file
8
src/main/resources/guid-pools/i1.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 778,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/i2.json
Normal file
8
src/main/resources/guid-pools/i2.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 918,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/i3.json
Normal file
8
src/main/resources/guid-pools/i3.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 699,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/i4.json
Normal file
8
src/main/resources/guid-pools/i4.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 318,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/z1.json
Normal file
8
src/main/resources/guid-pools/z1.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 2088,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/z10.json
Normal file
8
src/main/resources/guid-pools/z10.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 2657,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/z2.json
Normal file
8
src/main/resources/guid-pools/z2.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 2515,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
44
src/main/resources/guid-pools/z3.json
Normal file
44
src/main/resources/guid-pools/z3.json
Normal 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"
|
||||
}
|
||||
]
|
||||
44
src/main/resources/guid-pools/z4.json
Normal file
44
src/main/resources/guid-pools/z4.json
Normal 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"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/z5.json
Normal file
8
src/main/resources/guid-pools/z5.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 2112,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/z6.json
Normal file
8
src/main/resources/guid-pools/z6.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 2422,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/z7.json
Normal file
8
src/main/resources/guid-pools/z7.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 2816,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/z8.json
Normal file
8
src/main/resources/guid-pools/z8.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 2139,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
8
src/main/resources/guid-pools/z9.json
Normal file
8
src/main/resources/guid-pools/z9.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"Name": "environment",
|
||||
"Start": 0,
|
||||
"Max": 2921,
|
||||
"Selector": "specific"
|
||||
}
|
||||
]
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 => ;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 _ => ;
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ abstract class DeployableDefinition(objectId: Int)
|
|||
DamageUsing = DamageCalculations.AgainstVehicle
|
||||
ResistUsing = NoResistanceSelection
|
||||
Packet = new SmallDeployableConverter
|
||||
registerAs = "deployables"
|
||||
|
||||
def Item: DeployedItem.Value = item
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class KitDefinition(objectId: Int) extends EquipmentDefinition(objectId) {
|
|||
Tile = InventoryTile.Tile42
|
||||
Name = "kit"
|
||||
Packet = KitDefinition.converter
|
||||
registerAs = "kits"
|
||||
}
|
||||
|
||||
object KitDefinition {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ class ProjectileDefinition(objectId: Int)
|
|||
private var finalVelocity: Float = 0f
|
||||
Name = "projectile"
|
||||
Modifiers = DistanceDegrade
|
||||
registerAs = "projectiles"
|
||||
|
||||
def ProjectileType: Projectiles.Value = projectileType
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ class VehicleDefinition(objectId: Int)
|
|||
Model = VehicleResolutions.calculate
|
||||
RepairDistance = 10
|
||||
RepairRestoresAt = 1
|
||||
registerAs = "vehicles"
|
||||
|
||||
def MaxShields: Int = maxShields
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
215
src/main/scala/net/psforever/objects/guid/TaskWorkflow.scala
Normal file
215
src/main/scala/net/psforever/objects/guid/TaskWorkflow.scala
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
454
src/main/scala/net/psforever/objects/guid/UniqueNumberOps.scala
Normal file
454
src/main/scala/net/psforever/objects/guid/UniqueNumberOps.scala
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
116
src/test/scala/objects/TaskWorkflowTest.scala
Normal file
116
src/test/scala/objects/TaskWorkflowTest.scala
Normal 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 */
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,20 +3,22 @@ package objects.guidtask
|
|||
|
||||
import base.ActorTest
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class GUIDTaskUnregisterAmmoTest extends ActorTest {
|
||||
"UnregisterEquipment -> UnregisterObjectTask" in {
|
||||
val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
|
||||
val obj = AmmoBox(GlobalDefinitions.energy_cell)
|
||||
guid.register(obj, "dynamic")
|
||||
val (guid, uns, probe) = GUIDTaskTest.CommonTestSetup
|
||||
val obj = AmmoBox(GlobalDefinitions.energy_cell)
|
||||
guid.register(obj, name = "ammo")
|
||||
|
||||
assert(obj.HasGUID)
|
||||
taskResolver ! TaskResolver.GiveTask(
|
||||
TaskWorkflow.execute(TaskBundle(
|
||||
new GUIDTaskTest.RegisterTestTask(probe.ref),
|
||||
List(GUIDTask.UnregisterEquipment(obj)(uns))
|
||||
)
|
||||
probe.expectMsg(scala.util.Success)
|
||||
GUIDTask.unregisterEquipment(uns, obj)
|
||||
))
|
||||
probe.expectMsg(5.second, scala.util.Success(true))
|
||||
assert(!obj.HasGUID)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue