From 9841b7e97d18a590b909080fb5ae3658abd10f0d Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Sun, 15 Aug 2021 21:27:45 -0400 Subject: [PATCH] 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 --- .../actor/service/AvatarServiceTest.scala | 6 +- src/main/resources/akka.conf | 5 - src/main/resources/guid-pools/c1.json | 8 + src/main/resources/guid-pools/c2.json | 8 + src/main/resources/guid-pools/c3.json | 8 + src/main/resources/guid-pools/c4.json | 8 + src/main/resources/guid-pools/c5.json | 8 + src/main/resources/guid-pools/c6.json | 8 + src/main/resources/guid-pools/default.json | 74 +++ src/main/resources/guid-pools/home1.json | 8 + src/main/resources/guid-pools/home2.json | 8 + src/main/resources/guid-pools/home3.json | 8 + src/main/resources/guid-pools/i1.json | 8 + src/main/resources/guid-pools/i2.json | 8 + src/main/resources/guid-pools/i3.json | 8 + src/main/resources/guid-pools/i4.json | 8 + src/main/resources/guid-pools/z1.json | 8 + src/main/resources/guid-pools/z10.json | 8 + src/main/resources/guid-pools/z2.json | 8 + src/main/resources/guid-pools/z3.json | 44 ++ src/main/resources/guid-pools/z4.json | 44 ++ src/main/resources/guid-pools/z5.json | 8 + src/main/resources/guid-pools/z6.json | 8 + src/main/resources/guid-pools/z7.json | 8 + src/main/resources/guid-pools/z8.json | 8 + src/main/resources/guid-pools/z9.json | 8 + .../actors/session/SessionActor.scala | 436 +++++--------- .../net/psforever/login/WorldSession.scala | 219 +++---- .../psforever/objects/BoomerDeployable.scala | 4 +- .../psforever/objects/GlobalDefinitions.scala | 7 +- .../scala/net/psforever/objects/Players.scala | 4 +- .../psforever/objects/TurretDeployable.scala | 4 +- .../objects/avatar/PlayerControl.scala | 12 +- .../objects/ce/DeployableBehavior.scala | 4 +- .../definition/AmmoBoxDefinition.scala | 10 +- .../objects/definition/AvatarDefinition.scala | 1 + .../ConstructionItemDefinition.scala | 1 + .../definition/DeployableDefinition.scala | 1 + .../objects/definition/KitDefinition.scala | 1 + .../objects/definition/ObjectDefinition.scala | 3 +- .../definition/ProjectileDefinition.scala | 1 + .../definition/SimpleItemDefinition.scala | 3 +- .../objects/definition/ToolDefinition.scala | 1 + .../definition/VehicleDefinition.scala | 1 + .../objects/guid/AvailabilityPolicy.scala | 16 - .../net/psforever/objects/guid/GUIDTask.scala | 437 +++++++------- .../objects/guid/NumberPoolHub.scala | 11 +- .../net/psforever/objects/guid/Task.scala | 27 - .../psforever/objects/guid/TaskResolver.scala | 507 ---------------- .../psforever/objects/guid/TaskWorkflow.scala | 215 +++++++ .../objects/guid/UniqueNumberOps.scala | 454 ++++++++++++++ .../objects/guid/actor/Register.scala | 86 --- .../guid/actor/UniqueNumberSystem.scala | 348 ----------- .../objects/guid/actor/Unregister.scala | 23 - .../objects/guid/key/AvailabilityPolicy.scala | 19 + .../objects/guid/key/LoanedKey.scala | 7 +- .../psforever/objects/guid/key/Monitor.scala | 3 +- .../objects/guid/key/SecureKey.scala | 4 +- .../objects/guid/pool/GenericPool.scala | 69 ++- .../psforever/objects/guid/source/Key.scala | 5 +- .../objects/guid/source/MaxNumberSource.scala | 49 +- .../objects/guid/source/NumberSource.scala | 40 +- .../guid/source/SpecificNumberSource.scala | 43 +- .../guid/{actor => uns}/NumberPoolActor.scala | 38 +- .../objects/guid/uns/RegisteredEntity.scala | 14 + .../objects/guid/uns/UnregisteredEntity.scala | 14 + .../locker/LockerContainerDefinition.scala | 17 + .../pad/VehicleSpawnControl.scala | 4 +- .../shuttle/OrbitalShuttlePadControl.scala | 34 +- .../terminals/TerminalDefinition.scala | 1 + .../tube/SpawnTubeDefinition.scala | 1 + .../psforever/objects/vehicles/Utility.scala | 1 + .../vehicles/control/VehicleControl.scala | 4 +- .../net/psforever/objects/zones/Zone.scala | 112 ++-- .../net/psforever/services/RemoverActor.scala | 41 +- .../account/AccountPersistenceService.scala | 8 +- .../services/avatar/AvatarService.scala | 4 +- .../avatar/support/CorpseRemovalActor.scala | 9 +- .../avatar/support/DroppedItemRemover.scala | 9 +- .../services/local/LocalService.scala | 4 +- .../local/support/CaptureFlagManager.scala | 35 +- .../local/support/HackCaptureActor.scala | 4 +- .../vehicle/support/TurretUpgrader.scala | 39 +- .../scala/net/psforever/zones/Zones.scala | 118 +++- src/test/scala/objects/DamageableTest.scala | 1 - .../objects/DeployableBehaviorTest.scala | 26 +- src/test/scala/objects/DeployableTest.scala | 3 - .../scala/objects/OrbitalShuttlePadTest.scala | 22 +- src/test/scala/objects/TaskWorkflowTest.scala | 116 ++++ .../scala/objects/VehicleControlTest.scala | 1 - src/test/scala/objects/ZoneTest.scala | 2 +- .../guidtask/GUIDTaskRegisterAmmoTest.scala | 16 +- .../guidtask/GUIDTaskRegisterAvatarTest.scala | 18 +- .../guidtask/GUIDTaskRegisterObjectTest.scala | 16 +- .../guidtask/GUIDTaskRegisterPlayerTest.scala | 18 +- .../guidtask/GUIDTaskRegisterToolTest.scala | 16 +- .../guidtask/GUIDTaskRegisterTurretTest.scala | 22 +- .../GUIDTaskRegisterVehicleTest.scala | 18 +- .../scala/objects/guidtask/GUIDTaskTest.scala | 46 +- .../guidtask/GUIDTaskUnregisterAmmoTest.scala | 18 +- .../GUIDTaskUnregisterAvatarTest.scala | 30 +- .../GUIDTaskUnregisterObjectTest.scala | 18 +- .../GUIDTaskUnregisterPlayerTest.scala | 30 +- .../guidtask/GUIDTaskUnregisterToolTest.scala | 20 +- .../GUIDTaskUnregisterTurretTest.scala | 30 +- .../GUIDTaskUnregisterVehicleTest.scala | 26 +- .../objects/number/NumberPoolActorTest.scala | 6 +- .../objects/number/NumberPoolHubTest.scala | 59 +- .../scala/objects/number/NumberPoolTest.scala | 20 +- .../objects/number/NumberSourceTest.scala | 117 +--- .../scala/objects/number/RegisterTest.scala | 60 -- .../objects/number/UniqueNumberOpsTest.scala | 53 ++ .../number/UniqueNumberSystemTest.scala | 386 ------------ src/test/scala/service/RemoverActorTest.scala | 555 ------------------ 114 files changed, 2323 insertions(+), 3374 deletions(-) create mode 100644 src/main/resources/guid-pools/c1.json create mode 100644 src/main/resources/guid-pools/c2.json create mode 100644 src/main/resources/guid-pools/c3.json create mode 100644 src/main/resources/guid-pools/c4.json create mode 100644 src/main/resources/guid-pools/c5.json create mode 100644 src/main/resources/guid-pools/c6.json create mode 100644 src/main/resources/guid-pools/default.json create mode 100644 src/main/resources/guid-pools/home1.json create mode 100644 src/main/resources/guid-pools/home2.json create mode 100644 src/main/resources/guid-pools/home3.json create mode 100644 src/main/resources/guid-pools/i1.json create mode 100644 src/main/resources/guid-pools/i2.json create mode 100644 src/main/resources/guid-pools/i3.json create mode 100644 src/main/resources/guid-pools/i4.json create mode 100644 src/main/resources/guid-pools/z1.json create mode 100644 src/main/resources/guid-pools/z10.json create mode 100644 src/main/resources/guid-pools/z2.json create mode 100644 src/main/resources/guid-pools/z3.json create mode 100644 src/main/resources/guid-pools/z4.json create mode 100644 src/main/resources/guid-pools/z5.json create mode 100644 src/main/resources/guid-pools/z6.json create mode 100644 src/main/resources/guid-pools/z7.json create mode 100644 src/main/resources/guid-pools/z8.json create mode 100644 src/main/resources/guid-pools/z9.json delete mode 100644 src/main/scala/net/psforever/objects/guid/AvailabilityPolicy.scala delete mode 100644 src/main/scala/net/psforever/objects/guid/Task.scala delete mode 100644 src/main/scala/net/psforever/objects/guid/TaskResolver.scala create mode 100644 src/main/scala/net/psforever/objects/guid/TaskWorkflow.scala create mode 100644 src/main/scala/net/psforever/objects/guid/UniqueNumberOps.scala delete mode 100644 src/main/scala/net/psforever/objects/guid/actor/Register.scala delete mode 100644 src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala delete mode 100644 src/main/scala/net/psforever/objects/guid/actor/Unregister.scala create mode 100644 src/main/scala/net/psforever/objects/guid/key/AvailabilityPolicy.scala rename src/main/scala/net/psforever/objects/guid/{actor => uns}/NumberPoolActor.scala (72%) create mode 100644 src/main/scala/net/psforever/objects/guid/uns/RegisteredEntity.scala create mode 100644 src/main/scala/net/psforever/objects/guid/uns/UnregisteredEntity.scala create mode 100644 src/main/scala/net/psforever/objects/locker/LockerContainerDefinition.scala create mode 100644 src/test/scala/objects/TaskWorkflowTest.scala delete mode 100644 src/test/scala/objects/number/RegisterTest.scala create mode 100644 src/test/scala/objects/number/UniqueNumberOpsTest.scala delete mode 100644 src/test/scala/objects/number/UniqueNumberSystemTest.scala delete mode 100644 src/test/scala/service/RemoverActorTest.scala diff --git a/server/src/test/scala/actor/service/AvatarServiceTest.scala b/server/src/test/scala/actor/service/AvatarServiceTest.scala index ec324027..448d4a27 100644 --- a/server/src/test/scala/actor/service/AvatarServiceTest.scala +++ b/server/src/test/scala/actor/service/AvatarServiceTest.scala @@ -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. diff --git a/src/main/resources/akka.conf b/src/main/resources/akka.conf index 01953b2e..b05cd94e 100644 --- a/src/main/resources/akka.conf +++ b/src/main/resources/akka.conf @@ -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 diff --git a/src/main/resources/guid-pools/c1.json b/src/main/resources/guid-pools/c1.json new file mode 100644 index 00000000..cb02ee06 --- /dev/null +++ b/src/main/resources/guid-pools/c1.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 937, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/c2.json b/src/main/resources/guid-pools/c2.json new file mode 100644 index 00000000..8c547436 --- /dev/null +++ b/src/main/resources/guid-pools/c2.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 1615, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/c3.json b/src/main/resources/guid-pools/c3.json new file mode 100644 index 00000000..5a231211 --- /dev/null +++ b/src/main/resources/guid-pools/c3.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 1128, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/c4.json b/src/main/resources/guid-pools/c4.json new file mode 100644 index 00000000..2f3ba16d --- /dev/null +++ b/src/main/resources/guid-pools/c4.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 904, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/c5.json b/src/main/resources/guid-pools/c5.json new file mode 100644 index 00000000..903a5213 --- /dev/null +++ b/src/main/resources/guid-pools/c5.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 635, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/c6.json b/src/main/resources/guid-pools/c6.json new file mode 100644 index 00000000..6a64fcbd --- /dev/null +++ b/src/main/resources/guid-pools/c6.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 908, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/default.json b/src/main/resources/guid-pools/default.json new file mode 100644 index 00000000..7d6ec482 --- /dev/null +++ b/src/main/resources/guid-pools/default.json @@ -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" + } +] diff --git a/src/main/resources/guid-pools/home1.json b/src/main/resources/guid-pools/home1.json new file mode 100644 index 00000000..cfa7d5c8 --- /dev/null +++ b/src/main/resources/guid-pools/home1.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 1161, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/home2.json b/src/main/resources/guid-pools/home2.json new file mode 100644 index 00000000..56527608 --- /dev/null +++ b/src/main/resources/guid-pools/home2.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 1096, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/home3.json b/src/main/resources/guid-pools/home3.json new file mode 100644 index 00000000..4cfd0cf0 --- /dev/null +++ b/src/main/resources/guid-pools/home3.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 1074, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/i1.json b/src/main/resources/guid-pools/i1.json new file mode 100644 index 00000000..e2dc15bf --- /dev/null +++ b/src/main/resources/guid-pools/i1.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 778, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/i2.json b/src/main/resources/guid-pools/i2.json new file mode 100644 index 00000000..7669a98c --- /dev/null +++ b/src/main/resources/guid-pools/i2.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 918, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/i3.json b/src/main/resources/guid-pools/i3.json new file mode 100644 index 00000000..3650c32a --- /dev/null +++ b/src/main/resources/guid-pools/i3.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 699, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/i4.json b/src/main/resources/guid-pools/i4.json new file mode 100644 index 00000000..fe78825b --- /dev/null +++ b/src/main/resources/guid-pools/i4.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 318, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/z1.json b/src/main/resources/guid-pools/z1.json new file mode 100644 index 00000000..affb1102 --- /dev/null +++ b/src/main/resources/guid-pools/z1.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 2088, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/z10.json b/src/main/resources/guid-pools/z10.json new file mode 100644 index 00000000..34dc0424 --- /dev/null +++ b/src/main/resources/guid-pools/z10.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 2657, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/z2.json b/src/main/resources/guid-pools/z2.json new file mode 100644 index 00000000..df1f8cf3 --- /dev/null +++ b/src/main/resources/guid-pools/z2.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 2515, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/z3.json b/src/main/resources/guid-pools/z3.json new file mode 100644 index 00000000..6a4c3bf5 --- /dev/null +++ b/src/main/resources/guid-pools/z3.json @@ -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" + } +] diff --git a/src/main/resources/guid-pools/z4.json b/src/main/resources/guid-pools/z4.json new file mode 100644 index 00000000..bf027122 --- /dev/null +++ b/src/main/resources/guid-pools/z4.json @@ -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" + } +] diff --git a/src/main/resources/guid-pools/z5.json b/src/main/resources/guid-pools/z5.json new file mode 100644 index 00000000..819821d6 --- /dev/null +++ b/src/main/resources/guid-pools/z5.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 2112, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/z6.json b/src/main/resources/guid-pools/z6.json new file mode 100644 index 00000000..89266080 --- /dev/null +++ b/src/main/resources/guid-pools/z6.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 2422, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/z7.json b/src/main/resources/guid-pools/z7.json new file mode 100644 index 00000000..5d8e90cd --- /dev/null +++ b/src/main/resources/guid-pools/z7.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 2816, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/z8.json b/src/main/resources/guid-pools/z8.json new file mode 100644 index 00000000..2e66c514 --- /dev/null +++ b/src/main/resources/guid-pools/z8.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 2139, + "Selector": "specific" + } +] diff --git a/src/main/resources/guid-pools/z9.json b/src/main/resources/guid-pools/z9.json new file mode 100644 index 00000000..76f52c09 --- /dev/null +++ b/src/main/resources/guid-pools/z9.json @@ -0,0 +1,8 @@ +[ + { + "Name": "environment", + "Start": 0, + "Max": 2921, + "Selector": "specific" + } +] diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index c1f0f817..1bba1e7d 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -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 diff --git a/src/main/scala/net/psforever/login/WorldSession.scala b/src/main/scala/net/psforever/login/WorldSession.scala index a7bd88b2..d8af343e 100644 --- a/src/main/scala/net/psforever/login/WorldSession.scala +++ b/src/main/scala/net/psforever/login/WorldSession.scala @@ -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 ) } } diff --git a/src/main/scala/net/psforever/objects/BoomerDeployable.scala b/src/main/scala/net/psforever/objects/BoomerDeployable.scala index e40a1993..e18d5c24 100644 --- a/src/main/scala/net/psforever/objects/BoomerDeployable.scala +++ b/src/main/scala/net/psforever/objects/BoomerDeployable.scala @@ -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 => ; } } diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index c67beb24..f98bef47 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -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) diff --git a/src/main/scala/net/psforever/objects/Players.scala b/src/main/scala/net/psforever/objects/Players.scala index 9317937a..c577822e 100644 --- a/src/main/scala/net/psforever/objects/Players.scala +++ b/src/main/scala/net/psforever/objects/Players.scala @@ -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)) } } diff --git a/src/main/scala/net/psforever/objects/TurretDeployable.scala b/src/main/scala/net/psforever/objects/TurretDeployable.scala index b2616885..c363e625 100644 --- a/src/main/scala/net/psforever/objects/TurretDeployable.scala +++ b/src/main/scala/net/psforever/objects/TurretDeployable.scala @@ -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)) } } diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index 24f420cd..4f653bf7 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -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 _ => ; diff --git a/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala b/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala index 4d6e512d..dba3e149 100644 --- a/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala +++ b/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala @@ -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)) } /** diff --git a/src/main/scala/net/psforever/objects/definition/AmmoBoxDefinition.scala b/src/main/scala/net/psforever/objects/definition/AmmoBoxDefinition.scala index 9be5296c..76ba00ba 100644 --- a/src/main/scala/net/psforever/objects/definition/AmmoBoxDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/AmmoBoxDefinition.scala @@ -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 diff --git a/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala b/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala index f99de884..91b6ae93 100644 --- a/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala @@ -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 { diff --git a/src/main/scala/net/psforever/objects/definition/ConstructionItemDefinition.scala b/src/main/scala/net/psforever/objects/definition/ConstructionItemDefinition.scala index 77ba0f78..e46208ce 100644 --- a/src/main/scala/net/psforever/objects/definition/ConstructionItemDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/ConstructionItemDefinition.scala @@ -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 } diff --git a/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala b/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala index 78ffb641..8600bdff 100644 --- a/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala @@ -59,6 +59,7 @@ abstract class DeployableDefinition(objectId: Int) DamageUsing = DamageCalculations.AgainstVehicle ResistUsing = NoResistanceSelection Packet = new SmallDeployableConverter + registerAs = "deployables" def Item: DeployedItem.Value = item } diff --git a/src/main/scala/net/psforever/objects/definition/KitDefinition.scala b/src/main/scala/net/psforever/objects/definition/KitDefinition.scala index 5653fadf..3aa4ae07 100644 --- a/src/main/scala/net/psforever/objects/definition/KitDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/KitDefinition.scala @@ -16,6 +16,7 @@ class KitDefinition(objectId: Int) extends EquipmentDefinition(objectId) { Tile = InventoryTile.Tile42 Name = "kit" Packet = KitDefinition.converter + registerAs = "kits" } object KitDefinition { diff --git a/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala b/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala index b4cc26d3..c42e53ab 100644 --- a/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala @@ -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. diff --git a/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala b/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala index 75f916ca..cb72bf8d 100644 --- a/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala @@ -60,6 +60,7 @@ class ProjectileDefinition(objectId: Int) private var finalVelocity: Float = 0f Name = "projectile" Modifiers = DistanceDegrade + registerAs = "projectiles" def ProjectileType: Projectiles.Value = projectileType diff --git a/src/main/scala/net/psforever/objects/definition/SimpleItemDefinition.scala b/src/main/scala/net/psforever/objects/definition/SimpleItemDefinition.scala index 6633469c..173dda92 100644 --- a/src/main/scala/net/psforever/objects/definition/SimpleItemDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/SimpleItemDefinition.scala @@ -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 { diff --git a/src/main/scala/net/psforever/objects/definition/ToolDefinition.scala b/src/main/scala/net/psforever/objects/definition/ToolDefinition.scala index 08e4205e..b5513bd3 100644 --- a/src/main/scala/net/psforever/objects/definition/ToolDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/ToolDefinition.scala @@ -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 diff --git a/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala b/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala index 790b2307..feddf851 100644 --- a/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala @@ -61,6 +61,7 @@ class VehicleDefinition(objectId: Int) Model = VehicleResolutions.calculate RepairDistance = 10 RepairRestoresAt = 1 + registerAs = "vehicles" def MaxShields: Int = maxShields diff --git a/src/main/scala/net/psforever/objects/guid/AvailabilityPolicy.scala b/src/main/scala/net/psforever/objects/guid/AvailabilityPolicy.scala deleted file mode 100644 index 6b50a026..00000000 --- a/src/main/scala/net/psforever/objects/guid/AvailabilityPolicy.scala +++ /dev/null @@ -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 -} diff --git a/src/main/scala/net/psforever/objects/guid/GUIDTask.scala b/src/main/scala/net/psforever/objects/guid/GUIDTask.scala index b8143a91..cd86cfaf 100644 --- a/src/main/scala/net/psforever/objects/guid/GUIDTask.scala +++ b/src/main/scala/net/psforever/objects/guid/GUIDTask.scala @@ -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.
@@ -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.
*
- * 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.
- *
+ * 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`.
@@ -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`
- * `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`
+ * `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`.
*
@@ -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.
*
@@ -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.
- *
- * 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`
- * `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.
+ *
+ * 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.
+ *
+ * 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`
+ * `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.
*
* 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) } } } diff --git a/src/main/scala/net/psforever/objects/guid/NumberPoolHub.scala b/src/main/scala/net/psforever/objects/guid/NumberPoolHub.scala index fff77a2a..f4f425e2 100644 --- a/src/main/scala/net/psforever/objects/guid/NumberPoolHub.scala +++ b/src/main/scala/net/psforever/objects/guid/NumberPoolHub.scala @@ -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.
@@ -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.
diff --git a/src/main/scala/net/psforever/objects/guid/Task.scala b/src/main/scala/net/psforever/objects/guid/Task.scala deleted file mode 100644 index f4c73412..00000000 --- a/src/main/scala/net/psforever/objects/guid/Task.scala +++ /dev/null @@ -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 - } -} diff --git a/src/main/scala/net/psforever/objects/guid/TaskResolver.scala b/src/main/scala/net/psforever/objects/guid/TaskResolver.scala deleted file mode 100644 index e1b6fd91..00000000 --- a/src/main/scala/net/psforever/objects/guid/TaskResolver.scala +++ /dev/null @@ -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.
- *
- * 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) - } - } - } -} diff --git a/src/main/scala/net/psforever/objects/guid/TaskWorkflow.scala b/src/main/scala/net/psforever/objects/guid/TaskWorkflow.scala new file mode 100644 index 00000000..85b13429 --- /dev/null +++ b/src/main/scala/net/psforever/objects/guid/TaskWorkflow.scala @@ -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 + } + } +} diff --git a/src/main/scala/net/psforever/objects/guid/UniqueNumberOps.scala b/src/main/scala/net/psforever/objects/guid/UniqueNumberOps.scala new file mode 100644 index 00000000..279255dc --- /dev/null +++ b/src/main/scala/net/psforever/objects/guid/UniqueNumberOps.scala @@ -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.
+ *
+ * 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.
+ *
+ * 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 + } +} diff --git a/src/main/scala/net/psforever/objects/guid/actor/Register.scala b/src/main/scala/net/psforever/objects/guid/actor/Register.scala deleted file mode 100644 index adc37280..00000000 --- a/src/main/scala/net/psforever/objects/guid/actor/Register.scala +++ /dev/null @@ -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.
- *
- * 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)) - } -} diff --git a/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala b/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala deleted file mode 100644 index 06f0eca9..00000000 --- a/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala +++ /dev/null @@ -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.
- *
- * 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.
- *
- * 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 - } -} diff --git a/src/main/scala/net/psforever/objects/guid/actor/Unregister.scala b/src/main/scala/net/psforever/objects/guid/actor/Unregister.scala deleted file mode 100644 index c11c7212..00000000 --- a/src/main/scala/net/psforever/objects/guid/actor/Unregister.scala +++ /dev/null @@ -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.
- *
- * 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)) - } -} diff --git a/src/main/scala/net/psforever/objects/guid/key/AvailabilityPolicy.scala b/src/main/scala/net/psforever/objects/guid/key/AvailabilityPolicy.scala new file mode 100644 index 00000000..b2d3f11e --- /dev/null +++ b/src/main/scala/net/psforever/objects/guid/key/AvailabilityPolicy.scala @@ -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 +} diff --git a/src/main/scala/net/psforever/objects/guid/key/LoanedKey.scala b/src/main/scala/net/psforever/objects/guid/key/LoanedKey.scala index 7af04d9c..00ee381b 100644 --- a/src/main/scala/net/psforever/objects/guid/key/LoanedKey.scala +++ b/src/main/scala/net/psforever/objects/guid/key/LoanedKey.scala @@ -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 diff --git a/src/main/scala/net/psforever/objects/guid/key/Monitor.scala b/src/main/scala/net/psforever/objects/guid/key/Monitor.scala index e7cc1506..feb999b5 100644 --- a/src/main/scala/net/psforever/objects/guid/key/Monitor.scala +++ b/src/main/scala/net/psforever/objects/guid/key/Monitor.scala @@ -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] } diff --git a/src/main/scala/net/psforever/objects/guid/key/SecureKey.scala b/src/main/scala/net/psforever/objects/guid/key/SecureKey.scala index 8c333e2e..2c238047 100644 --- a/src/main/scala/net/psforever/objects/guid/key/SecureKey.scala +++ b/src/main/scala/net/psforever/objects/guid/key/SecureKey.scala @@ -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 diff --git a/src/main/scala/net/psforever/objects/guid/pool/GenericPool.scala b/src/main/scala/net/psforever/objects/guid/pool/GenericPool.scala index 4ea78a5c..9a77aa7c 100644 --- a/src/main/scala/net/psforever/objects/guid/pool/GenericPool.scala +++ b/src/main/scala/net/psforever/objects/guid/pool/GenericPool.scala @@ -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.
+ *
+ * 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.
@@ -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 diff --git a/src/main/scala/net/psforever/objects/guid/source/Key.scala b/src/main/scala/net/psforever/objects/guid/source/Key.scala index ae6be828..c518eb38 100644 --- a/src/main/scala/net/psforever/objects/guid/source/Key.scala +++ b/src/main/scala/net/psforever/objects/guid/source/Key.scala @@ -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 } diff --git a/src/main/scala/net/psforever/objects/guid/source/MaxNumberSource.scala b/src/main/scala/net/psforever/objects/guid/source/MaxNumberSource.scala index 387185fa..47bcd229 100644 --- a/src/main/scala/net/psforever/objects/guid/source/MaxNumberSource.scala +++ b/src/main/scala/net/psforever/objects/guid/source/MaxNumberSource.scala @@ -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 } } diff --git a/src/main/scala/net/psforever/objects/guid/source/NumberSource.scala b/src/main/scala/net/psforever/objects/guid/source/NumberSource.scala index d2e342da..81d7c0a2 100644 --- a/src/main/scala/net/psforever/objects/guid/source/NumberSource.scala +++ b/src/main/scala/net/psforever/objects/guid/source/NumberSource.scala @@ -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] diff --git a/src/main/scala/net/psforever/objects/guid/source/SpecificNumberSource.scala b/src/main/scala/net/psforever/objects/guid/source/SpecificNumberSource.scala index c0284b27..979b77d5 100644 --- a/src/main/scala/net/psforever/objects/guid/source/SpecificNumberSource.scala +++ b/src/main/scala/net/psforever/objects/guid/source/SpecificNumberSource.scala @@ -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 } } diff --git a/src/main/scala/net/psforever/objects/guid/actor/NumberPoolActor.scala b/src/main/scala/net/psforever/objects/guid/uns/NumberPoolActor.scala similarity index 72% rename from src/main/scala/net/psforever/objects/guid/actor/NumberPoolActor.scala rename to src/main/scala/net/psforever/objects/guid/uns/NumberPoolActor.scala index edc0408e..211ae802 100644 --- a/src/main/scala/net/psforever/objects/guid/actor/NumberPoolActor.scala +++ b/src/main/scala/net/psforever/objects/guid/uns/NumberPoolActor.scala @@ -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 diff --git a/src/main/scala/net/psforever/objects/guid/uns/RegisteredEntity.scala b/src/main/scala/net/psforever/objects/guid/uns/RegisteredEntity.scala new file mode 100644 index 00000000..1499b953 --- /dev/null +++ b/src/main/scala/net/psforever/objects/guid/uns/RegisteredEntity.scala @@ -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) diff --git a/src/main/scala/net/psforever/objects/guid/uns/UnregisteredEntity.scala b/src/main/scala/net/psforever/objects/guid/uns/UnregisteredEntity.scala new file mode 100644 index 00000000..715cf7f0 --- /dev/null +++ b/src/main/scala/net/psforever/objects/guid/uns/UnregisteredEntity.scala @@ -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) diff --git a/src/main/scala/net/psforever/objects/locker/LockerContainerDefinition.scala b/src/main/scala/net/psforever/objects/locker/LockerContainerDefinition.scala new file mode 100644 index 00000000..3d902f56 --- /dev/null +++ b/src/main/scala/net/psforever/objects/locker/LockerContainerDefinition.scala @@ -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() +} diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala index 4cde293a..bc0526a8 100644 --- a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala @@ -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)) } } } diff --git a/src/main/scala/net/psforever/objects/serverobject/shuttle/OrbitalShuttlePadControl.scala b/src/main/scala/net/psforever/objects/serverobject/shuttle/OrbitalShuttlePadControl.scala index 83f218cb..18f2294c 100644 --- a/src/main/scala/net/psforever/objects/serverobject/shuttle/OrbitalShuttlePadControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/shuttle/OrbitalShuttlePadControl.scala @@ -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`.
@@ -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) ) } diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala index 9259d48e..59f881cd 100644 --- a/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala @@ -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 diff --git a/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala index 485bebb8..b01e31f2 100644 --- a/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala +++ b/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala @@ -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 { diff --git a/src/main/scala/net/psforever/objects/vehicles/Utility.scala b/src/main/scala/net/psforever/objects/vehicles/Utility.scala index 80e1b77b..27a57b5d 100644 --- a/src/main/scala/net/psforever/objects/vehicles/Utility.scala +++ b/src/main/scala/net/psforever/objects/vehicles/Utility.scala @@ -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 } diff --git a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala index f001da23..438fec3f 100644 --- a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala @@ -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 diff --git a/src/main/scala/net/psforever/objects/zones/Zone.scala b/src/main/scala/net/psforever/objects/zones/Zone.scala index 14a62af1..f775935f 100644 --- a/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -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.
+ * To avoid being called more than once, there is a test whether the globally unique identifier system has been changed.
*
* 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 } } diff --git a/src/main/scala/net/psforever/services/RemoverActor.scala b/src/main/scala/net/psforever/services/RemoverActor.scala index 78c57747..0348a3c1 100644 --- a/src/main/scala/net/psforever/services/RemoverActor.scala +++ b/src/main/scala/net/psforever/services/RemoverActor.scala @@ -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 { diff --git a/src/main/scala/net/psforever/services/account/AccountPersistenceService.scala b/src/main/scala/net/psforever/services/account/AccountPersistenceService.scala index b314a69c..76993e10 100644 --- a/src/main/scala/net/psforever/services/account/AccountPersistenceService.scala +++ b/src/main/scala/net/psforever/services/account/AccountPersistenceService.scala @@ -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}") } } diff --git a/src/main/scala/net/psforever/services/avatar/AvatarService.scala b/src/main/scala/net/psforever/services/avatar/AvatarService.scala index 3863a806..4d1abe34 100644 --- a/src/main/scala/net/psforever/services/avatar/AvatarService.scala +++ b/src/main/scala/net/psforever/services/avatar/AvatarService.scala @@ -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 diff --git a/src/main/scala/net/psforever/services/avatar/support/CorpseRemovalActor.scala b/src/main/scala/net/psforever/services/avatar/support/CorpseRemovalActor.scala index b97b707c..062c7f15 100644 --- a/src/main/scala/net/psforever/services/avatar/support/CorpseRemovalActor.scala +++ b/src/main/scala/net/psforever/services/avatar/support/CorpseRemovalActor.scala @@ -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 } diff --git a/src/main/scala/net/psforever/services/avatar/support/DroppedItemRemover.scala b/src/main/scala/net/psforever/services/avatar/support/DroppedItemRemover.scala index 19d63969..80a0ce87 100644 --- a/src/main/scala/net/psforever/services/avatar/support/DroppedItemRemover.scala +++ b/src/main/scala/net/psforever/services/avatar/support/DroppedItemRemover.scala @@ -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]) } } diff --git a/src/main/scala/net/psforever/services/local/LocalService.scala b/src/main/scala/net/psforever/services/local/LocalService.scala index 4c9b5606..33dadf70 100644 --- a/src/main/scala/net/psforever/services/local/LocalService.scala +++ b/src/main/scala/net/psforever/services/local/LocalService.scala @@ -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 diff --git a/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala b/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala index d6d97263..e1d07145 100644 --- a/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala +++ b/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala @@ -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 { diff --git a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala index f511fadb..a9e991a0 100644 --- a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala +++ b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala @@ -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 diff --git a/src/main/scala/net/psforever/services/vehicle/support/TurretUpgrader.scala b/src/main/scala/net/psforever/services/vehicle/support/TurretUpgrader.scala index d3709e71..fa93a129 100644 --- a/src/main/scala/net/psforever/services/vehicle/support/TurretUpgrader.scala +++ b/src/main/scala/net/psforever/services/vehicle/support/TurretUpgrader.scala @@ -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 + )) } /** diff --git a/src/main/scala/net/psforever/zones/Zones.scala b/src/main/scala/net/psforever/zones/Zones.scala index 3e9a4169..d35e6787 100644 --- a/src/main/scala/net/psforever/zones/Zones.scala +++ b/src/main/scala/net/psforever/zones/Zones.scala @@ -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) diff --git a/src/test/scala/objects/DamageableTest.scala b/src/test/scala/objects/DamageableTest.scala index cd0ee589..8c2719c4 100644 --- a/src/test/scala/objects/DamageableTest.scala +++ b/src/test/scala/objects/DamageableTest.scala @@ -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] } diff --git a/src/test/scala/objects/DeployableBehaviorTest.scala b/src/test/scala/objects/DeployableBehaviorTest.scala index 38e4703b..ada501a3 100644 --- a/src/test/scala/objects/DeployableBehaviorTest.scala +++ b/src/test/scala/objects/DeployableBehaviorTest.scala @@ -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") diff --git a/src/test/scala/objects/DeployableTest.scala b/src/test/scala/objects/DeployableTest.scala index 7a9713f7..38562f44 100644 --- a/src/test/scala/objects/DeployableTest.scala +++ b/src/test/scala/objects/DeployableTest.scala @@ -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 diff --git a/src/test/scala/objects/OrbitalShuttlePadTest.scala b/src/test/scala/objects/OrbitalShuttlePadTest.scala index 877bef4e..82db9ac3 100644 --- a/src/test/scala/objects/OrbitalShuttlePadTest.scala +++ b/src/test/scala/objects/OrbitalShuttlePadTest.scala @@ -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] diff --git a/src/test/scala/objects/TaskWorkflowTest.scala b/src/test/scala/objects/TaskWorkflowTest.scala new file mode 100644 index 00000000..2ab736a4 --- /dev/null +++ b/src/test/scala/objects/TaskWorkflowTest.scala @@ -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 */ +} diff --git a/src/test/scala/objects/VehicleControlTest.scala b/src/test/scala/objects/VehicleControlTest.scala index 8379868f..43645b8f 100644 --- a/src/test/scala/objects/VehicleControlTest.scala +++ b/src/test/scala/objects/VehicleControlTest.scala @@ -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 diff --git a/src/test/scala/objects/ZoneTest.scala b/src/test/scala/objects/ZoneTest.scala index 77a5bd78..6c4f4dfe 100644 --- a/src/test/scala/objects/ZoneTest.scala +++ b/src/test/scala/objects/ZoneTest.scala @@ -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 { diff --git a/src/test/scala/objects/guidtask/GUIDTaskRegisterAmmoTest.scala b/src/test/scala/objects/guidtask/GUIDTaskRegisterAmmoTest.scala index 089f3d53..34bd51bd 100644 --- a/src/test/scala/objects/guidtask/GUIDTaskRegisterAmmoTest.scala +++ b/src/test/scala/objects/guidtask/GUIDTaskRegisterAmmoTest.scala @@ -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) } } diff --git a/src/test/scala/objects/guidtask/GUIDTaskRegisterAvatarTest.scala b/src/test/scala/objects/guidtask/GUIDTaskRegisterAvatarTest.scala index 4f4171fa..09138409 100644 --- a/src/test/scala/objects/guidtask/GUIDTaskRegisterAvatarTest.scala +++ b/src/test/scala/objects/guidtask/GUIDTaskRegisterAvatarTest.scala @@ -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) diff --git a/src/test/scala/objects/guidtask/GUIDTaskRegisterObjectTest.scala b/src/test/scala/objects/guidtask/GUIDTaskRegisterObjectTest.scala index 7f424f30..8377378e 100644 --- a/src/test/scala/objects/guidtask/GUIDTaskRegisterObjectTest.scala +++ b/src/test/scala/objects/guidtask/GUIDTaskRegisterObjectTest.scala @@ -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) } } diff --git a/src/test/scala/objects/guidtask/GUIDTaskRegisterPlayerTest.scala b/src/test/scala/objects/guidtask/GUIDTaskRegisterPlayerTest.scala index bb9ba900..bbb945d1 100644 --- a/src/test/scala/objects/guidtask/GUIDTaskRegisterPlayerTest.scala +++ b/src/test/scala/objects/guidtask/GUIDTaskRegisterPlayerTest.scala @@ -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) diff --git a/src/test/scala/objects/guidtask/GUIDTaskRegisterToolTest.scala b/src/test/scala/objects/guidtask/GUIDTaskRegisterToolTest.scala index 9bf0a1ff..243e6afc 100644 --- a/src/test/scala/objects/guidtask/GUIDTaskRegisterToolTest.scala +++ b/src/test/scala/objects/guidtask/GUIDTaskRegisterToolTest.scala @@ -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) } diff --git a/src/test/scala/objects/guidtask/GUIDTaskRegisterTurretTest.scala b/src/test/scala/objects/guidtask/GUIDTaskRegisterTurretTest.scala index c53cd747..0c5f2131 100644 --- a/src/test/scala/objects/guidtask/GUIDTaskRegisterTurretTest.scala +++ b/src/test/scala/objects/guidtask/GUIDTaskRegisterTurretTest.scala @@ -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) diff --git a/src/test/scala/objects/guidtask/GUIDTaskRegisterVehicleTest.scala b/src/test/scala/objects/guidtask/GUIDTaskRegisterVehicleTest.scala index f33e7b71..a0416c15 100644 --- a/src/test/scala/objects/guidtask/GUIDTaskRegisterVehicleTest.scala +++ b/src/test/scala/objects/guidtask/GUIDTaskRegisterVehicleTest.scala @@ -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) diff --git a/src/test/scala/objects/guidtask/GUIDTaskTest.scala b/src/test/scala/objects/guidtask/GUIDTaskTest.scala index 935f91cd..d2d94a61 100644 --- a/src/test/scala/objects/guidtask/GUIDTaskTest.scala +++ b/src/test/scala/objects/guidtask/GUIDTaskTest.scala @@ -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 } } diff --git a/src/test/scala/objects/guidtask/GUIDTaskUnregisterAmmoTest.scala b/src/test/scala/objects/guidtask/GUIDTaskUnregisterAmmoTest.scala index fb15d1b1..4dc90d7b 100644 --- a/src/test/scala/objects/guidtask/GUIDTaskUnregisterAmmoTest.scala +++ b/src/test/scala/objects/guidtask/GUIDTaskUnregisterAmmoTest.scala @@ -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) } } diff --git a/src/test/scala/objects/guidtask/GUIDTaskUnregisterAvatarTest.scala b/src/test/scala/objects/guidtask/GUIDTaskUnregisterAvatarTest.scala index 9121e317..53c7d919 100644 --- a/src/test/scala/objects/guidtask/GUIDTaskUnregisterAvatarTest.scala +++ b/src/test/scala/objects/guidtask/GUIDTaskUnregisterAvatarTest.scala @@ -4,15 +4,17 @@ package objects.guidtask import base.ActorTest import net.psforever.objects._ import net.psforever.objects.avatar.Avatar -import net.psforever.objects.guid.{GUIDTask, TaskResolver} +import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow} import net.psforever.objects.locker.LockerEquipment import net.psforever.types.{CharacterSex, CharacterVoice, PlanetSideEmpire} +import scala.concurrent.duration._ + class GUIDTaskUnregisterAvatarTest extends ActorTest { "UnregisterAvatar" in { - val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup - val obj = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) - val obj_wep = Tool(GlobalDefinitions.beamer) + val (guid, uns, probe) = GUIDTaskTest.CommonTestSetup + val obj = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) + val obj_wep = Tool(GlobalDefinitions.beamer) obj.Slot(0).Equipment = obj_wep val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell) obj_wep.AmmoSlots.head.Box = obj_wep_ammo @@ -21,12 +23,12 @@ class GUIDTaskUnregisterAvatarTest extends ActorTest { val obj_locker = obj.Slot(5).Equipment.get val obj_locker_ammo = AmmoBox(GlobalDefinitions.energy_cell) obj_locker.asInstanceOf[LockerEquipment].Inventory += 0 -> obj_locker_ammo - guid.register(obj, "dynamic") - guid.register(obj_wep, "dynamic") - guid.register(obj_wep_ammo, "dynamic") - guid.register(obj_inv_ammo, "dynamic") - guid.register(obj_locker, "dynamic") - guid.register(obj_locker_ammo, "dynamic") + guid.register(obj, name = "players") + guid.register(obj_wep, name = "tools") + guid.register(obj_wep_ammo, name = "ammo") + guid.register(obj_inv_ammo, name = "ammo") + guid.register(obj_locker, name = "lockers") + guid.register(obj_locker_ammo, name = "amoo") assert(obj.HasGUID) assert(obj_wep.HasGUID) @@ -34,11 +36,11 @@ class GUIDTaskUnregisterAvatarTest extends ActorTest { assert(obj_inv_ammo.HasGUID) assert(obj_locker.HasGUID) assert(obj_locker_ammo.HasGUID) - taskResolver ! TaskResolver.GiveTask( + TaskWorkflow.execute(TaskBundle( new GUIDTaskTest.RegisterTestTask(probe.ref), - List(GUIDTask.UnregisterAvatar(obj)(uns)) - ) - probe.expectMsg(scala.util.Success) + GUIDTask.unregisterAvatar(uns, obj) + )) + probe.expectMsg(5.second, scala.util.Success(true)) assert(!obj.HasGUID) assert(!obj_wep.HasGUID) assert(!obj_wep_ammo.HasGUID) diff --git a/src/test/scala/objects/guidtask/GUIDTaskUnregisterObjectTest.scala b/src/test/scala/objects/guidtask/GUIDTaskUnregisterObjectTest.scala index 3318b579..af8c4084 100644 --- a/src/test/scala/objects/guidtask/GUIDTaskUnregisterObjectTest.scala +++ b/src/test/scala/objects/guidtask/GUIDTaskUnregisterObjectTest.scala @@ -2,20 +2,22 @@ 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 GUIDTaskUnregisterObjectTest extends ActorTest { "UnregisterObjectTask" in { - val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup - val obj = new GUIDTaskTest.TestObject - guid.register(obj, "dynamic") + val (guid, uns, probe) = GUIDTaskTest.CommonTestSetup + val obj = new GUIDTaskTest.TestObject + guid.register(obj) assert(obj.HasGUID) - taskResolver ! TaskResolver.GiveTask( + TaskWorkflow.execute(TaskBundle( new GUIDTaskTest.RegisterTestTask(probe.ref), - List(GUIDTask.UnregisterObjectTask(obj)(uns)) - ) - probe.expectMsg(scala.util.Success) + GUIDTask.unregisterObject(uns, obj) + )) + probe.expectMsg(5.second, scala.util.Success(true)) assert(!obj.HasGUID) } } diff --git a/src/test/scala/objects/guidtask/GUIDTaskUnregisterPlayerTest.scala b/src/test/scala/objects/guidtask/GUIDTaskUnregisterPlayerTest.scala index a10df710..7192ec3a 100644 --- a/src/test/scala/objects/guidtask/GUIDTaskUnregisterPlayerTest.scala +++ b/src/test/scala/objects/guidtask/GUIDTaskUnregisterPlayerTest.scala @@ -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 GUIDTaskUnregisterPlayerTest extends ActorTest { "UnregisterPlayer" in { - val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup - val obj = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) - val obj_wep = Tool(GlobalDefinitions.beamer) + val (guid, uns, probe) = GUIDTaskTest.CommonTestSetup + val obj = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) + val obj_wep = Tool(GlobalDefinitions.beamer) obj.Slot(0).Equipment = obj_wep val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell) obj_wep.AmmoSlots.head.Box = obj_wep_ammo @@ -21,12 +23,12 @@ class GUIDTaskUnregisterPlayerTest extends ActorTest { val obj_locker = obj.Slot(5).Equipment.get val obj_locker_ammo = AmmoBox(GlobalDefinitions.energy_cell) obj_locker.asInstanceOf[LockerEquipment].Inventory += 0 -> obj_locker_ammo - guid.register(obj, "dynamic") - guid.register(obj_wep, "dynamic") - guid.register(obj_wep_ammo, "dynamic") - guid.register(obj_inv_ammo, "dynamic") - guid.register(obj_locker, "dynamic") - guid.register(obj_locker_ammo, "dynamic") + guid.register(obj, name = "players") + guid.register(obj_wep, name = "tools") + guid.register(obj_wep_ammo, name = "ammo") + guid.register(obj_inv_ammo, name = "ammo") + guid.register(obj_locker, name = "lockers") + guid.register(obj_locker_ammo, name = "ammo") assert(obj.HasGUID) assert(obj_wep.HasGUID) @@ -34,11 +36,11 @@ class GUIDTaskUnregisterPlayerTest 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.UnregisterPlayer(obj)(uns)) - ) - probe.expectMsg(scala.util.Success) + GUIDTask.unregisterPlayer(uns, obj) + )) + probe.expectMsg(5.second, scala.util.Success(true)) assert(!obj.HasGUID) assert(!obj_wep.HasGUID) assert(!obj_wep_ammo.HasGUID) diff --git a/src/test/scala/objects/guidtask/GUIDTaskUnregisterToolTest.scala b/src/test/scala/objects/guidtask/GUIDTaskUnregisterToolTest.scala index 7f9fc5f4..9a30dd1f 100644 --- a/src/test/scala/objects/guidtask/GUIDTaskUnregisterToolTest.scala +++ b/src/test/scala/objects/guidtask/GUIDTaskUnregisterToolTest.scala @@ -3,23 +3,25 @@ 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 GUIDTaskUnregisterToolTest extends ActorTest { "UnregisterEquipment -> UnregisterTool" in { - val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup - val obj = Tool(GlobalDefinitions.beamer) + val (guid, uns, probe) = GUIDTaskTest.CommonTestSetup + val obj = Tool(GlobalDefinitions.beamer) obj.AmmoSlots.head.Box = AmmoBox(GlobalDefinitions.energy_cell) - guid.register(obj, "dynamic") - guid.register(obj.AmmoSlots.head.Box, "dynamic") + guid.register(obj, name = "tools") + guid.register(obj.AmmoSlots.head.Box, name ="ammo") assert(obj.HasGUID) assert(obj.AmmoSlots.head.Box.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) assert(!obj.AmmoSlots.head.Box.HasGUID) } diff --git a/src/test/scala/objects/guidtask/GUIDTaskUnregisterTurretTest.scala b/src/test/scala/objects/guidtask/GUIDTaskUnregisterTurretTest.scala index a8e3629a..00d40952 100644 --- a/src/test/scala/objects/guidtask/GUIDTaskUnregisterTurretTest.scala +++ b/src/test/scala/objects/guidtask/GUIDTaskUnregisterTurretTest.scala @@ -3,29 +3,31 @@ 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 GUIDTaskUnregisterTurretTest extends ActorTest { "UnregisterDeployableTurret" in { - val (guid, 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) - guid.register(obj, "dynamic") - guid.register(obj_wep, "dynamic") - guid.register(obj_ammo, "dynamic") - obj_res.foreach(box => guid.register(box, "dynamic")) + val (guid, 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) + guid.register(obj, name = "deployables") + guid.register(obj_wep, name = "tools") + guid.register(obj_ammo, name = "ammo") + obj_res.foreach(box => guid.register(box, name = "ammo")) 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.UnregisterDeployableTurret(obj)(uns)) - ) - probe.expectMsg(scala.util.Success) + GUIDTask.unregisterDeployableTurret(uns, obj) + )) + probe.expectMsg(5.second, scala.util.Success(true)) assert(!obj.HasGUID) assert(!obj_wep.HasGUID) assert(!obj_ammo.HasGUID) diff --git a/src/test/scala/objects/guidtask/GUIDTaskUnregisterVehicleTest.scala b/src/test/scala/objects/guidtask/GUIDTaskUnregisterVehicleTest.scala index 11317a94..850cea31 100644 --- a/src/test/scala/objects/guidtask/GUIDTaskUnregisterVehicleTest.scala +++ b/src/test/scala/objects/guidtask/GUIDTaskUnregisterVehicleTest.scala @@ -3,31 +3,33 @@ 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 GUIDTaskUnregisterVehicleTest extends ActorTest { "RegisterVehicle" in { - val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup - val obj = Vehicle(GlobalDefinitions.fury) - val obj_wep = obj.WeaponControlledFromSeat(0).get + val (guid, 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) val obj_trunk_ammo = obj.Trunk.Items(0).obj - guid.register(obj, "dynamic") - guid.register(obj_wep, "dynamic") - guid.register(obj_wep_ammo, "dynamic") - guid.register(obj_trunk_ammo, "dynamic") + guid.register(obj, name = "vehicles") + guid.register(obj_wep, name = "tools") + guid.register(obj_wep_ammo, name = "ammo") + guid.register(obj_trunk_ammo, name = "ammo") assert(obj.HasGUID) 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.UnregisterVehicle(obj)(uns)) - ) - probe.expectMsg(scala.util.Success) + GUIDTask.unregisterVehicle(uns, obj) + )) + probe.expectMsg(5.second, scala.util.Success(true)) assert(!obj.HasGUID) assert(!obj_wep.HasGUID) assert(!obj_wep_ammo.HasGUID) diff --git a/src/test/scala/objects/number/NumberPoolActorTest.scala b/src/test/scala/objects/number/NumberPoolActorTest.scala index a3357b36..51eb1627 100644 --- a/src/test/scala/objects/number/NumberPoolActorTest.scala +++ b/src/test/scala/objects/number/NumberPoolActorTest.scala @@ -3,9 +3,9 @@ package objects.number import akka.actor.Props import base.ActorTest -import net.psforever.objects.guid.actor.NumberPoolActor import net.psforever.objects.guid.pool.ExclusivePool import net.psforever.objects.guid.selector.RandomSelector +import net.psforever.objects.guid.uns.NumberPoolActor import scala.concurrent.duration.Duration @@ -29,7 +29,7 @@ class NumberPoolActorTest1 extends ActorTest { pool.Selector = new RandomSelector val poolActor = system.actorOf(Props(classOf[NumberPoolActor], pool), name = "poolActor2") poolActor ! NumberPoolActor.GetSpecificNumber(37) - expectMsg(NumberPoolActor.GiveNumber(37, None)) + expectMsg(NumberPoolActor.GiveNumber(37)) } } } @@ -41,7 +41,7 @@ class NumberPoolActorTest2 extends ActorTest { pool.Selector = new RandomSelector val poolActor = system.actorOf(Props(classOf[NumberPoolActor], pool), name = "poolActor3") poolActor ! NumberPoolActor.GetAnyNumber() - expectMsg(NumberPoolActor.GiveNumber(25, None)) + expectMsg(NumberPoolActor.GiveNumber(25)) poolActor ! NumberPoolActor.GetAnyNumber() val msg = receiveOne(Duration.create(500, "ms")) diff --git a/src/test/scala/objects/number/NumberPoolHubTest.scala b/src/test/scala/objects/number/NumberPoolHubTest.scala index 6266f214..c9ef6b0f 100644 --- a/src/test/scala/objects/number/NumberPoolHubTest.scala +++ b/src/test/scala/objects/number/NumberPoolHubTest.scala @@ -262,46 +262,6 @@ class NumberPoolHubTest extends Specification { } } - "not affect the hidden restricted pool by adding a new pool" in { - val src = new MaxNumberSource(51) - src.restrictNumber(4) - src.restrictNumber(8) //in fibonacci - src.restrictNumber(10) - src.restrictNumber(12) - val hub = new NumberPoolHub(src) - hub.AddPool("fibonacci", numberList) must throwA[IllegalArgumentException] - } - - "not register an object to a number belonging to the restricted pool" in { - val src = new MaxNumberSource(51) - src.restrictNumber(4) - val hub = new NumberPoolHub(src) - val obj = new EntityTestClass() - hub.register(obj, 4).isFailure mustEqual true - } - - "not register an object to the restricted pool directly" in { - val src = new MaxNumberSource(51) -// src.restrictNumber(4) - val hub = new NumberPoolHub(src) - val obj = new EntityTestClass() - hub.register(obj, "").isFailure mustEqual true //the empty string represents the restricted pool - } - - "not register a number belonging to the restricted pool" in { - val src = new MaxNumberSource(51) - src.restrictNumber(4) - val hub = new NumberPoolHub(src) - hub.register(4).isFailure mustEqual true - } - - "not unregister a number belonging to the restricted pool" in { - val src = new MaxNumberSource(51) - src.restrictNumber(4) - val hub = new NumberPoolHub(src) - hub.unregister(4).isFailure mustEqual true - } - "identity an object that is registered to it" in { val hub1 = new NumberPoolHub(new MaxNumberSource(10)) val hub2 = new NumberPoolHub(new MaxNumberSource(10)) @@ -316,28 +276,21 @@ class NumberPoolHubTest extends Specification { hub2.isRegistered(obj1) mustEqual false } - "identity a number that is registered to it" in { + "identity an entity that is registered to it" in { val src1 = new MaxNumberSource(5) val hub1 = new NumberPoolHub(src1) val src2 = new MaxNumberSource(10) - src2.restrictNumber(0) - src2.restrictNumber(1) - src2.restrictNumber(2) - src2.restrictNumber(3) - src2.restrictNumber(4) - src2.restrictNumber(5) val hub2 = new NumberPoolHub(src2) val obj1 = new EntityTestClass() val obj2 = new EntityTestClass() hub1.register(obj1) hub2.register(obj2) - val num1 = obj1.GUID.guid - val num2 = obj2.GUID.guid - hub1.isRegistered(num1) mustEqual true - hub2.isRegistered(num2) mustEqual true - hub1.isRegistered(num2) mustEqual false - hub2.isRegistered(num1) mustEqual false + obj1.GUID mustEqual obj2.GUID + hub1.isRegistered(obj1) mustEqual true + hub2.isRegistered(obj2) mustEqual true + hub1.isRegistered(obj2) mustEqual false + hub2.isRegistered(obj1) mustEqual false } } } diff --git a/src/test/scala/objects/number/NumberPoolTest.scala b/src/test/scala/objects/number/NumberPoolTest.scala index 628b8838..a41753be 100644 --- a/src/test/scala/objects/number/NumberPoolTest.scala +++ b/src/test/scala/objects/number/NumberPoolTest.scala @@ -135,13 +135,13 @@ class NumberPoolTest extends Specification { "GenericPool" should { "construct" in { - new GenericPool(mutable.LongMap[String](), 11) + GenericPool(mutable.LongMap[String](), max = 11, poolName = "generic") ok } "get a provided number" in { val map = mutable.LongMap[String]() - val obj = new GenericPool(map, 11) + val obj = GenericPool(map, max = 11, poolName = "generic") obj.Numbers.isEmpty mustEqual true obj.Selector.asInstanceOf[SpecificSelector].SelectionIndex = 5 obj.Get() match { @@ -157,7 +157,7 @@ class NumberPoolTest extends Specification { "return a number" in { val map = mutable.LongMap[String]() - val obj = new GenericPool(map, 11) + val obj = GenericPool(map, max = 11, poolName = "generic") obj.Selector.asInstanceOf[SpecificSelector].SelectionIndex = 5 obj.Get() map.get(5).contains("generic") mustEqual true @@ -170,7 +170,7 @@ class NumberPoolTest extends Specification { "block on numbers that are already defined" in { val map = mutable.LongMap[String]() map += 5L -> "test" //5 is defined - val obj = new GenericPool(map, 11) + val obj = GenericPool(map, max = 11, poolName = "generic") obj.Numbers.isEmpty mustEqual true obj.Selector.asInstanceOf[SpecificSelector].SelectionIndex = 5 //5 is requested obj.Get() match { @@ -183,10 +183,10 @@ class NumberPoolTest extends Specification { "get a free number on own if none provided" in { val map = mutable.LongMap[String]() - val obj = new GenericPool(map, 11) + val obj = GenericPool(map, max = 11, poolName = "generic") obj.Get() match { case Success(number) => - number mustEqual 5 + number mustEqual 1 case _ => ko } @@ -195,10 +195,10 @@ class NumberPoolTest extends Specification { "get a free number that is not already defined" in { val map = mutable.LongMap[String]() map += 5L -> "test" //5 is defined; think, -1 :: 5 :: 11 - val obj = new GenericPool(map, 11) + val obj = GenericPool(map, max = 11, poolName = "generic") obj.Get() match { case Success(number) => - number mustEqual 2 // think, -1 :: 2 :: 5 :: 11 + number mustEqual 1 // think, -1 :: 2 :: 5 :: 11 case _ => ko } @@ -208,10 +208,10 @@ class NumberPoolTest extends Specification { val map = mutable.LongMap[String]() map += 5L -> "test" //5 is defined; think, -1 :: 5 :: 11 map += 4L -> "test" //4 is defined; think, -1 :: 4 :: 5 :: 11 - val obj = new GenericPool(map, 11) + val obj = GenericPool(map, max = 11, poolName = "generic") obj.Get() match { case Success(number) => - number mustEqual 8 // think, -1 :: 4 :: 5 :: 8 :: 11 + number mustEqual 1 // think, -1 :: 4 :: 5 :: 8 :: 11 case _ => ko } diff --git a/src/test/scala/objects/number/NumberSourceTest.scala b/src/test/scala/objects/number/NumberSourceTest.scala index e69f0442..dae1140d 100644 --- a/src/test/scala/objects/number/NumberSourceTest.scala +++ b/src/test/scala/objects/number/NumberSourceTest.scala @@ -1,8 +1,7 @@ // Copyright (c) 2017 PSForever package objects.number -import net.psforever.objects.guid.AvailabilityPolicy -import net.psforever.objects.guid.key.{LoanedKey, SecureKey} +import net.psforever.objects.guid.key.{AvailabilityPolicy, LoanedKey, SecureKey} import net.psforever.types.PlanetSideGUID import org.specs2.mutable.Specification @@ -72,46 +71,6 @@ class NumberSourceTest extends Specification { obj.countUsed mustEqual 0 } - "restrict a number (unassigned)" in { - val obj = MaxNumberSource(25) - val result: Option[LoanedKey] = obj.restrictNumber(5) - result.isDefined mustEqual true - result.get.GUID mustEqual 5 - result.get.Policy mustEqual AvailabilityPolicy.Restricted - result.get.Object.isEmpty mustEqual true - } - - "restrict a number (assigned + multiple assignments)" in { - val obj = MaxNumberSource(25) - val test1 = new TestClass() - val test2 = new TestClass() - val result: Option[LoanedKey] = obj.restrictNumber(5) - result.get.GUID mustEqual 5 - result.get.Policy mustEqual AvailabilityPolicy.Restricted - result.get.Object.isEmpty mustEqual true - result.get.Object = None //assignment 1 - result.get.Object.isEmpty mustEqual true //still unassigned - result.get.Object = test1 //assignment 2 - result.get.Object.contains(test1) mustEqual true - result.get.Object = test2 //assignment 3 - result.get.Object.contains(test1) mustEqual true //same as above - } - - "return a restricted number (correctly fail)" in { - val obj = MaxNumberSource(25) - val test = new TestClass() - val result: Option[LoanedKey] = obj.restrictNumber(5) - result.get.GUID mustEqual 5 - result.get.Policy mustEqual AvailabilityPolicy.Restricted - result.get.Object = test - - obj.returnNumber(5) - val result2: Option[SecureKey] = obj.get(5) - result2.get.GUID mustEqual 5 - result2.get.Policy mustEqual AvailabilityPolicy.Restricted - result2.get.Object.contains(test) mustEqual true - } - "return a secure key" in { val obj = MaxNumberSource(25) val test = new TestClass() @@ -124,19 +83,6 @@ class NumberSourceTest extends Specification { obj.returnNumber(result2.get).contains(test) mustEqual true } - "restrict a previously-assigned number" in { - val obj = MaxNumberSource(25) - val test = new TestClass() - val result1: Option[LoanedKey] = obj.getAvailable(5) - result1.isDefined mustEqual true - result1.get.Policy mustEqual AvailabilityPolicy.Leased - result1.get.Object = test - val result2: Option[LoanedKey] = obj.restrictNumber(5) - result2.isDefined mustEqual true - result2.get.Policy mustEqual AvailabilityPolicy.Restricted - result2.get.Object.contains(test) mustEqual true - } - "check a number (not previously gotten)" in { val obj = MaxNumberSource(25) val result2: Option[SecureKey] = obj.get(5) @@ -194,9 +140,9 @@ class NumberSourceTest extends Specification { obj.getAvailable(5) //no assignment obj.getAvailable(10).get.Object = test1 obj.getAvailable(15).get.Object = test2 - obj.restrictNumber(15) - obj.restrictNumber(20).get.Object = test3 + obj.getAvailable(20).get.Object = test3 obj.countUsed mustEqual 4 + obj.countDangling mustEqual 1 val list: List[IdentifiableEntity] = obj.clear() obj.countUsed mustEqual 0 @@ -273,46 +219,6 @@ class NumberSourceTest extends Specification { obj.countUsed mustEqual 0 } - "restrict a number (unassigned)" in { - val obj = SpecificNumberSource(List(25)) - val result: Option[LoanedKey] = obj.restrictNumber(number = 25) - result.isDefined mustEqual true - result.get.GUID mustEqual 25 - result.get.Policy mustEqual AvailabilityPolicy.Restricted - result.get.Object.isEmpty mustEqual true - } - - "restrict a number (assigned + multiple assignments)" in { - val obj = SpecificNumberSource(List(25, 26)) - val test1 = new TestClass() - val test2 = new TestClass() - val result: Option[LoanedKey] = obj.restrictNumber(number = 25) - result.get.GUID mustEqual 25 - result.get.Policy mustEqual AvailabilityPolicy.Restricted - result.get.Object.isEmpty mustEqual true - result.get.Object = None //assignment 1 - result.get.Object.isEmpty mustEqual true //still unassigned - result.get.Object = test1 //assignment 2 - result.get.Object.contains(test1) mustEqual true - result.get.Object = test2 //assignment 3 - result.get.Object.contains(test1) mustEqual true //same as above - } - - "return a restricted number (correctly fail)" in { - val obj = SpecificNumberSource(List(25)) - val test = new TestClass() - val result: Option[LoanedKey] = obj.restrictNumber(number = 25) - result.get.GUID mustEqual 25 - result.get.Policy mustEqual AvailabilityPolicy.Restricted - result.get.Object = test - - obj.returnNumber(number = 25) - val result2: Option[SecureKey] = obj.get(25) - result2.get.GUID mustEqual 25 - result2.get.Policy mustEqual AvailabilityPolicy.Restricted - result2.get.Object.contains(test) mustEqual true - } - "return a secure key" in { val obj = SpecificNumberSource(List(25)) val test = new TestClass() @@ -325,19 +231,6 @@ class NumberSourceTest extends Specification { obj.returnNumber(result2.get).contains(test) mustEqual true } - "restrict a previously-assigned number" in { - val obj = SpecificNumberSource(List(25)) - val test = new TestClass() - val result1: Option[LoanedKey] = obj.getAvailable(number = 25) - result1.isDefined mustEqual true - result1.get.Policy mustEqual AvailabilityPolicy.Leased - result1.get.Object = test - val result2: Option[LoanedKey] = obj.restrictNumber(number = 25) - result2.isDefined mustEqual true - result2.get.Policy mustEqual AvailabilityPolicy.Restricted - result2.get.Object.contains(test) mustEqual true - } - "check a number (not previously gotten)" in { val obj = SpecificNumberSource(List(25)) val result2: Option[SecureKey] = obj.get(25) @@ -395,9 +288,9 @@ class NumberSourceTest extends Specification { obj.getAvailable(25) //no assignment obj.getAvailable(26).get.Object = test1 obj.getAvailable(28).get.Object = test2 - obj.restrictNumber(28) - obj.restrictNumber(30).get.Object = test3 + obj.getAvailable(30).get.Object = test3 obj.countUsed mustEqual 4 + obj.countDangling mustEqual 1 val list: List[IdentifiableEntity] = obj.clear() obj.countUsed mustEqual 0 diff --git a/src/test/scala/objects/number/RegisterTest.scala b/src/test/scala/objects/number/RegisterTest.scala deleted file mode 100644 index fbce828e..00000000 --- a/src/test/scala/objects/number/RegisterTest.scala +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2017 PSForever -package objects.number - -import akka.actor.ActorRef -import net.psforever.objects.guid.actor.Register -import org.specs2.mutable.Specification - -class RegisterTest extends Specification { - val obj = new net.psforever.objects.entity.IdentifiableEntity() {} - - "Register" should { - "construct (object)" in { - val reg = Register(obj) - reg.obj mustEqual obj - reg.number.isEmpty mustEqual true - reg.name.isEmpty mustEqual true - reg.callback.isEmpty mustEqual true - } - - "construct (object, callback)" in { - val reg = Register(obj, ActorRef.noSender) - reg.obj mustEqual obj - reg.number.isEmpty mustEqual true - reg.name.isEmpty mustEqual true - reg.callback.contains(ActorRef.noSender) mustEqual true - } - - "construct (object, suggested number)" in { - val reg = Register(obj, 5) - reg.obj mustEqual obj - reg.number.contains(5) mustEqual true - reg.name.isEmpty mustEqual true - reg.callback.isEmpty mustEqual true - } - - "construct (object, suggested number, callback)" in { - val reg = Register(obj, 5, ActorRef.noSender) - reg.obj mustEqual obj - reg.number.contains(5) mustEqual true - reg.name.isEmpty mustEqual true - reg.callback.contains(ActorRef.noSender) mustEqual true - } - - "construct (object, pool name)" in { - val reg = Register(obj, "pool") - reg.obj mustEqual obj - reg.number.isEmpty mustEqual true - reg.name.contains("pool") mustEqual true - reg.callback.isEmpty mustEqual true - } - - "construct (object, pool name, callback)" in { - val reg = Register(obj, "pool", ActorRef.noSender) - reg.obj mustEqual obj - reg.number.isEmpty mustEqual true - reg.name.contains("pool") mustEqual true - reg.callback.contains(ActorRef.noSender) mustEqual true - } - } -} diff --git a/src/test/scala/objects/number/UniqueNumberOpsTest.scala b/src/test/scala/objects/number/UniqueNumberOpsTest.scala new file mode 100644 index 00000000..f52eee57 --- /dev/null +++ b/src/test/scala/objects/number/UniqueNumberOpsTest.scala @@ -0,0 +1,53 @@ +// Copyright (c) 2017 PSForever +package objects.number + +import akka.actor.{Actor, ActorRef, ActorSystem, Props} +import net.psforever.objects.entity.IdentifiableEntity +import net.psforever.objects.guid.{NumberPoolHub, UniqueNumberOps, UniqueNumberSetup} +import net.psforever.objects.guid.source.MaxNumberSource +import org.scalatest.flatspec.AsyncFlatSpec +import akka.pattern.ask +import akka.util.Timeout + +import scala.concurrent.Promise +import scala.concurrent.duration._ +import scala.util.Success + +class UniqueNumberOpsTest extends AsyncFlatSpec { + behavior of "UniqueNumberOps" + + it should "UniqueNumberOpsTest" in { + val promise: Promise[Any] = Promise() + val sys = ActorSystem() + val source = new MaxNumberSource(max = 21) + val hub = new NumberPoolHub(source) + hub.AddPool(name = "default", List(0,1,2,3,5,8,13,21)) + val entity = new UniqueNumberOpsTest.EntityTestClass() + assert(!entity.HasGUID) + assert(source.countUsed == 0) + + ask(sys.actorOf(Props[UniqueNumberOpsTest.NumberPoolBuilder](), "test"), hub)(Timeout(2.seconds)).onComplete { + case Success(pools: Map[_,_]) => + val unops = new UniqueNumberOps(hub, pools.asInstanceOf[Map[String, ActorRef]]) + promise.completeWith { unops.Register(entity, poolName = "default") } + case _ => + promise.failure(new Exception("")) + } + promise.future map { _ => + assert(entity.HasGUID) + assert(source.countUsed == 1) + } + } +} + +object UniqueNumberOpsTest { + class EntityTestClass extends IdentifiableEntity + + class NumberPoolBuilder extends Actor { + def receive: Receive = { + case hub: NumberPoolHub => + sender() ! UniqueNumberSetup.AllocateNumberPoolActors(context, hub) + case _ => ; + } + } +} diff --git a/src/test/scala/objects/number/UniqueNumberSystemTest.scala b/src/test/scala/objects/number/UniqueNumberSystemTest.scala deleted file mode 100644 index e26bc5a7..00000000 --- a/src/test/scala/objects/number/UniqueNumberSystemTest.scala +++ /dev/null @@ -1,386 +0,0 @@ -// Copyright (c) 2017 PSForever -package objects.number - -import akka.actor.{ActorRef, ActorSystem, Props} -import base.ActorTest -import net.psforever.objects.entity.IdentifiableEntity -import net.psforever.objects.guid.NumberPoolHub -import net.psforever.objects.guid.actor.{NumberPoolActor, Register, UniqueNumberSystem, Unregister} -import net.psforever.objects.guid.selector.RandomSelector -import net.psforever.objects.guid.source.MaxNumberSource -import net.psforever.types.PlanetSideGUID - -import scala.concurrent.duration._ -import scala.util.{Failure, Success} - -class AllocateNumberPoolActors extends ActorTest { - "AllocateNumberPoolActors" in { - val src: MaxNumberSource = MaxNumberSource(6000) - val guid: NumberPoolHub = new NumberPoolHub(src) - guid.AddPool("pool1", (1001 to 2000).toList) - guid.AddPool("pool2", (3001 to 4000).toList) - guid.AddPool("pool3", (5001 to 6000).toList) - val actorMap = UniqueNumberSystemTest.AllocateNumberPoolActors(guid) - assert(actorMap.size == 4) - assert(actorMap.get("generic").isDefined) //automatically generated - assert(actorMap.get("pool1").isDefined) - assert(actorMap.get("pool2").isDefined) - assert(actorMap.get("pool3").isDefined) - } -} - -class UniqueNumberSystemTest extends ActorTest() { - "UniqueNumberSystem" should { - "constructor" in { - val src: MaxNumberSource = MaxNumberSource(6000) - val guid: NumberPoolHub = new NumberPoolHub(src) - guid.AddPool("pool1", (1001 to 2000).toList) - guid.AddPool("pool2", (3001 to 4000).toList) - guid.AddPool("pool3", (5001 to 6000).toList) - system.actorOf( - Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), - "uns" - ) - //as long as it constructs ... - } - } -} - -class UniqueNumberSystemTest1 extends ActorTest() { - class EntityTestClass extends IdentifiableEntity - - "UniqueNumberSystem" should { - "Register (success)" in { - val src: MaxNumberSource = MaxNumberSource(6000) - val guid: NumberPoolHub = new NumberPoolHub(src) - val pool1 = (1001 to 2000).toList - val pool2 = (3001 to 4000).toList - val pool3 = (5001 to 6000).toList - guid.AddPool("pool1", pool1).Selector = new RandomSelector - guid.AddPool("pool2", pool2).Selector = new RandomSelector - guid.AddPool("pool3", pool3).Selector = new RandomSelector - val uns = system.actorOf( - Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), - "uns" - ) - assert(src.countUsed == 0) - //pool1 - for (_ <- 1 to 100) { - val testObj = new EntityTestClass() - uns ! Register(testObj, "pool1") - val msg = receiveOne(Duration.create(500, "ms")) - assert(msg.isInstanceOf[Success[_]]) - assert(pool1.contains(testObj.GUID.guid)) - } - //pool2 - for (_ <- 1 to 100) { - val testObj = new EntityTestClass() - uns ! Register(testObj, "pool2") - val msg = receiveOne(Duration.create(500, "ms")) - assert(msg.isInstanceOf[Success[_]]) - assert(pool2.contains(testObj.GUID.guid)) - } - //pool3 - for (_ <- 1 to 100) { - val testObj = new EntityTestClass() - uns ! Register(testObj, "pool3") - val msg = receiveOne(Duration.create(500, "ms")) - assert(msg.isInstanceOf[Success[_]]) - assert(pool3.contains(testObj.GUID.guid)) - } - assert(src.countUsed == 300) - } - } -} - -class UniqueNumberSystemTest2 extends ActorTest() { - class EntityTestClass extends IdentifiableEntity - - "UniqueNumberSystem" should { - "Register (success; already registered)" in { - val src: MaxNumberSource = MaxNumberSource(6000) - val guid: NumberPoolHub = new NumberPoolHub(src) - guid.AddPool("pool1", (1001 to 2000).toList).Selector = new RandomSelector - guid.AddPool("pool2", (3001 to 4000).toList).Selector = new RandomSelector - guid.AddPool("pool3", (5001 to 6000).toList).Selector = new RandomSelector - val uns = system.actorOf( - Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), - "uns" - ) - val testObj = new EntityTestClass() - assert(!testObj.HasGUID) - assert(src.countUsed == 0) - - uns ! Register(testObj, "pool1") - val msg1 = receiveOne(Duration.create(500, "ms")) - assert(msg1.isInstanceOf[Success[_]]) - assert(testObj.HasGUID) - assert(src.countUsed == 1) - - val id = testObj.GUID.guid - uns ! Register(testObj, "pool2") //different pool; makes no difference - val msg2 = receiveOne(Duration.create(500, "ms")) - assert(msg2.isInstanceOf[Success[_]]) - assert(testObj.HasGUID) - assert(src.countUsed == 1) - assert(testObj.GUID.guid == id) //unchanged - } - } - //a log.warn should have been generated during this test -} - -class UniqueNumberSystemTest3 extends ActorTest() { - class EntityTestClass extends IdentifiableEntity - - "UniqueNumberSystem" should { - "Register (failure; no pool)" in { - val src: MaxNumberSource = MaxNumberSource(6000) - val guid: NumberPoolHub = new NumberPoolHub(src) - guid.AddPool("pool1", (1001 to 2000).toList).Selector = new RandomSelector - guid.AddPool("pool2", (3001 to 4000).toList).Selector = new RandomSelector - guid.AddPool("pool3", (5001 to 6000).toList).Selector = new RandomSelector - val uns = system.actorOf( - Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), - "uns" - ) - val testObj = new EntityTestClass() - assert(!testObj.HasGUID) - assert(src.countUsed == 0) - - uns ! Register(testObj, "pool4") - val msg1 = receiveOne(Duration.create(500, "ms")) - assert(msg1.isInstanceOf[Failure[_]]) - assert(!testObj.HasGUID) - assert(src.countUsed == 0) - } - } -} - -class UniqueNumberSystemTest4 extends ActorTest() { - class EntityTestClass extends IdentifiableEntity - - "UniqueNumberSystem" should { - "Register (failure; empty pool)" in { - val src: MaxNumberSource = MaxNumberSource(6000) - val guid: NumberPoolHub = new NumberPoolHub(src) - guid.AddPool("pool1", (1001 to 2000).toList).Selector = new RandomSelector - guid.AddPool("pool2", (3001 to 4000).toList).Selector = new RandomSelector - guid.AddPool("pool3", (5001 to 6000).toList).Selector = new RandomSelector - guid.AddPool("pool4", 50 :: Nil).Selector = new RandomSelector //list of one element; can not add an empty list - val uns = system.actorOf( - Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), - "uns" - ) - - val testObj1 = new EntityTestClass() - uns ! Register(testObj1, "pool4") - val msg1 = receiveOne(Duration.create(500, "ms")) - assert(msg1.isInstanceOf[Success[_]]) //pool4 is now empty - - val testObj2 = new EntityTestClass() - uns ! Register(testObj2, "pool4") - val msg2 = receiveOne(Duration.create(500, "ms")) - assert(msg2.isInstanceOf[Failure[_]]) - } - } -} - -class UniqueNumberSystemTest5 extends ActorTest() { - class EntityTestClass extends IdentifiableEntity - - "UniqueNumberSystem" should { - "Unregister (success)" in { - val src: MaxNumberSource = MaxNumberSource(6000) - val guid: NumberPoolHub = new NumberPoolHub(src) - val pool2 = (3001 to 4000).toList - guid.AddPool("pool1", (1001 to 2000).toList).Selector = new RandomSelector - guid.AddPool("pool2", pool2).Selector = new RandomSelector - guid.AddPool("pool3", (5001 to 6000).toList).Selector = new RandomSelector - val uns = system.actorOf( - Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), - "uns" - ) - val testObj = new EntityTestClass() - assert(!testObj.HasGUID) - assert(src.countUsed == 0) - - uns ! Register(testObj, "pool2") - val msg1 = receiveOne(Duration.create(2000, "ms")) - assert(msg1.isInstanceOf[Success[_]]) - assert(testObj.HasGUID) - assert(pool2.contains(testObj.GUID.guid)) - assert(src.countUsed == 1) - - uns ! Unregister(testObj) - val msg2 = receiveOne(Duration.create(2000, "ms")) - assert(msg2.isInstanceOf[Success[_]]) - assert(!testObj.HasGUID) - assert(src.countUsed == 0) - } - } -} - -class UniqueNumberSystemTest6 extends ActorTest() { - class EntityTestClass extends IdentifiableEntity - - "UniqueNumberSystem" should { - "Unregister (success; object not registered at all)" in { - val src: MaxNumberSource = MaxNumberSource(6000) - val guid: NumberPoolHub = new NumberPoolHub(src) - guid.AddPool("pool1", (1001 to 2000).toList).Selector = new RandomSelector - guid.AddPool("pool2", (3001 to 4000).toList).Selector = new RandomSelector - guid.AddPool("pool3", (5001 to 6000).toList).Selector = new RandomSelector - val uns = system.actorOf( - Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), - "uns" - ) - val testObj = new EntityTestClass() - assert(!testObj.HasGUID) - assert(src.countUsed == 0) - - uns ! Unregister(testObj) - val msg1 = receiveOne(Duration.create(500, "ms")) - assert(msg1.isInstanceOf[Success[_]]) - assert(!testObj.HasGUID) - assert(src.countUsed == 0) - } - } -} - -class UniqueNumberSystemTest7 extends ActorTest() { - class EntityTestClass extends IdentifiableEntity - - "UniqueNumberSystem" should { - "Unregister (failure; number not in system)" in { - val src: MaxNumberSource = MaxNumberSource(6000) - val guid: NumberPoolHub = new NumberPoolHub(src) - guid.AddPool("pool1", (1001 to 2000).toList).Selector = new RandomSelector - guid.AddPool("pool2", (3001 to 4000).toList).Selector = new RandomSelector - guid.AddPool("pool3", (5001 to 6000).toList).Selector = new RandomSelector - val uns = system.actorOf( - Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), - "uns" - ) - val testObj = new EntityTestClass() - testObj.GUID = PlanetSideGUID(6001) //fake registering; number too high - assert(testObj.HasGUID) - assert(src.countUsed == 0) - - uns ! Unregister(testObj) - val msg1 = receiveOne(Duration.create(500, "ms")) - assert(msg1.isInstanceOf[Failure[_]]) - assert(testObj.HasGUID) - assert(src.countUsed == 0) - } - } -} - -class UniqueNumberSystemTest8 extends ActorTest() { - class EntityTestClass extends IdentifiableEntity - - "UniqueNumberSystem" should { - "Unregister (failure; object is not registered to that number)" in { - val src: MaxNumberSource = MaxNumberSource(6000) - val guid: NumberPoolHub = new NumberPoolHub(src) - guid.AddPool("pool1", (1001 to 2000).toList).Selector = new RandomSelector - guid.AddPool("pool2", (3001 to 4000).toList).Selector = new RandomSelector - guid.AddPool("pool3", (5001 to 6000).toList).Selector = new RandomSelector - val uns = system.actorOf( - Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), - "uns" - ) - val testObj = new EntityTestClass() - testObj.GUID = PlanetSideGUID(3500) //fake registering - assert(testObj.HasGUID) - assert(src.countUsed == 0) - - uns ! Unregister(testObj) - val msg1 = receiveOne(Duration.create(500, "ms")) - assert(msg1.isInstanceOf[Failure[_]]) - assert(testObj.HasGUID) - assert(src.countUsed == 0) - } - } -} - -class UniqueNumberSystemTest9 extends ActorTest() { - class EntityTestClass extends IdentifiableEntity - - "UniqueNumberSystem" should { - "Failures (manually walking the failure cases)" in { - val src: MaxNumberSource = MaxNumberSource(6000) - val guid: NumberPoolHub = new NumberPoolHub(src) - guid.AddPool("pool1", (1001 to 2000).toList).Selector = new RandomSelector - guid.AddPool("pool2", (3001 to 4000).toList).Selector = new RandomSelector - guid.AddPool("pool3", (5001 to 6000).toList).Selector = new RandomSelector - val uns = system.actorOf( - Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), - "uns" - ) - val excp = new Exception("EXCEPTION MESSAGE") - expectNoMessage(Duration.create(200, "ms")) - - //GiveNumber - uns ! NumberPoolActor.GiveNumber(1001, Some("test")) //no task associated with id="test" - uns ! NumberPoolActor.GiveNumber(1000, Some("test")) //no task associated with id="test" and number is not pooled - uns ! NumberPoolActor.GiveNumber(1000, Some(1)) //the task could theoretically exist, but does not - //NoNumber - uns ! NumberPoolActor.NoNumber(excp, Some(1)) - uns ! NumberPoolActor.NoNumber(excp, None) - uns ! NumberPoolActor.NoNumber(excp, Some("test")) - //ReturnNumberResult A - uns ! NumberPoolActor.ReturnNumberResult(1001, None, Some("test")) - uns ! NumberPoolActor.ReturnNumberResult(1000, None, Some("test")) - uns ! NumberPoolActor.ReturnNumberResult(1001, None, Some(1)) - uns ! NumberPoolActor.ReturnNumberResult(1000, None, Some(1)) - //ReturnNumberResult B - uns ! NumberPoolActor.ReturnNumberResult(1001, Some(excp), Some("test")) - uns ! NumberPoolActor.ReturnNumberResult(1001, Some(excp), Some(1)) - } - } -} - -class UniqueNumberSystemTestA extends ActorTest { - class EntityTestClass extends IdentifiableEntity - - "UniqueNumberSystem" should { - "remain consistent between registrations" in { - val src: MaxNumberSource = MaxNumberSource(10) - val guid: NumberPoolHub = new NumberPoolHub(src) - guid.AddPool("pool1", (0 until 10).toList).Selector = new RandomSelector - val uns = system.actorOf( - Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), - "uns" - ) - expectNoMessage(Duration.create(200, "ms")) - - assert(src.countUsed == 0) - (0 to 4).foreach(i => { assert(guid.register(new EntityTestClass(), i).isSuccess) }) - assert(src.countUsed == 5) - - (0 to 5).foreach(_ => { uns ! Register(new EntityTestClass(), "pool1") }) - assert(receiveOne(200 milliseconds).isInstanceOf[Success[_]]) //6th - assert(receiveOne(200 milliseconds).isInstanceOf[Success[_]]) //7th - assert(receiveOne(200 milliseconds).isInstanceOf[Success[_]]) //8th - assert(receiveOne(200 milliseconds).isInstanceOf[Success[_]]) //9th - assert(receiveOne(200 milliseconds).isInstanceOf[Success[_]]) //10th - assert(receiveOne(200 milliseconds).isInstanceOf[Failure[_]]) //no more - assert(src.countUsed == 10) - } - } -} - -object UniqueNumberSystemTest { - - /** - * @see `UniqueNumberSystem.AllocateNumberPoolActors(NumberPoolHub)(implicit ActorContext)` - */ - def AllocateNumberPoolActors(poolSource: NumberPoolHub)(implicit system: ActorSystem): Map[String, ActorRef] = { - poolSource.Pools - .map({ - case (pname, pool) => - pname -> system.actorOf(Props(classOf[NumberPoolActor], pool), pname) - }) - .toMap - } -} diff --git a/src/test/scala/service/RemoverActorTest.scala b/src/test/scala/service/RemoverActorTest.scala deleted file mode 100644 index 643be63e..00000000 --- a/src/test/scala/service/RemoverActorTest.scala +++ /dev/null @@ -1,555 +0,0 @@ -// Copyright (c) 2017 PSForever -package service - -/* Temporary imports */ -import akka.actor.ActorRef -import net.psforever.objects.definition.EquipmentDefinition -import net.psforever.objects.equipment.Equipment -import net.psforever.types.PlanetSideGUID -import net.psforever.services.RemoverActor - -//import akka.actor.{ActorRef, Props} -//import akka.routing.RandomPool -//import akka.testkit.TestProbe -//import base.ActorTest -//import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Tool} -//import net.psforever.objects.definition.{EquipmentDefinition, ObjectDefinition} -//import net.psforever.objects.equipment.Equipment -//import net.psforever.objects.guid.TaskResolver -//import net.psforever.objects.zones.{Zone, ZoneMap} -//import net.psforever.types.PlanetSideGUID -//import net.psforever.services.{RemoverActor, ServiceManager} - -import scala.concurrent.duration._ -import scala.util.Success - -//class StandardRemoverActorTest extends ActorTest { -// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") -// -// "RemoverActor" should { -// "handle a simple task" in { -// expectNoMessage(500 milliseconds) -// val remover = system.actorOf( -// Props(classOf[ActorTest.SupportActorInterface], Props[RemoverActorTest.TestRemover], self), -// "test-remover" -// ) -// remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere) -// -// val reply1 = receiveOne(500 milliseconds) -// assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert]) -// val reply2 = receiveOne(500 milliseconds) -// assert(reply2.isInstanceOf[RemoverActorTest.InitialJobAlert]) -// expectNoMessage(1 seconds) //delay -// val reply3 = receiveOne(500 milliseconds) -// assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert]) -// val reply4 = receiveOne(500 milliseconds) -// assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert]) -// val reply5 = receiveOne(500 milliseconds) -// assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert]) -// val reply6 = receiveOne(500 milliseconds) -// assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert]) -// val reply7 = receiveOne(500 milliseconds) -// assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert]) -// } -// } -//} - -//class DelayedRemoverActorTest extends ActorTest { -// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") -// -// "RemoverActor" should { -// "handle a simple task (timed)" in { -// expectNoMessage(500 milliseconds) -// val remover = system.actorOf( -// Props(classOf[ActorTest.SupportActorInterface], Props[RemoverActorTest.TestRemover], self), -// "test-remover" -// ) -// remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(100 milliseconds)) -// -// val reply1 = receiveOne(500 milliseconds) -// assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert]) -// val reply2 = receiveOne(500 milliseconds) -// assert(reply2.isInstanceOf[RemoverActorTest.InitialJobAlert]) -// //no delay -// val reply3 = receiveOne(500 milliseconds) -// assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert]) -// val reply4 = receiveOne(500 milliseconds) -// assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert]) -// val reply5 = receiveOne(500 milliseconds) -// assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert]) -// val reply6 = receiveOne(500 milliseconds) -// assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert]) -// val reply7 = receiveOne(500 milliseconds) -// assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert]) -// } -// } -//} - -//class ExcludedRemoverActorTest extends ActorTest { -// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") -// val AlternateTestObject = new PlanetSideGameObject() { def Definition = new ObjectDefinition(0) { } } -// -// "RemoverActor" should { -// "allow only specific objects" in { -// expectNoMessage(500 milliseconds) -// val probe = TestProbe() -// val remover = system.actorOf( -// Props(classOf[ActorTest.SupportActorInterface], Props[RemoverActorTest.TestRemover], self), -// "test-remover" -// ) -// remover ! RemoverActor.AddTask(AlternateTestObject, Zone.Nowhere) -// -// val reply1 = probe.receiveOne(200 milliseconds) -// assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert]) -// expectNoMessage(2 seconds) -// //RemoverActor is stalled because it received an object that it was not allowed to act upon -// } -// } -//} - -//class MultipleRemoverActorTest extends ActorTest { -// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") -// final val TestObject2 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(2) } } -// -// "RemoverActor" should { -// "work on parallel tasks" in { -// expectNoMessage(500 milliseconds) -// val probe = TestProbe() -// val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover") -// remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere) -// remover ! RemoverActor.AddTask(TestObject2, Zone.Nowhere) -// -// val replies = probe.receiveN(14, 5 seconds) -// var ita : Int = 0 -// var ija : Int = 0 -// var fja : Int = 0 -// var cta : Int = 0 -// var sja : Int = 0 -// var dta : Int = 0 -// var dtr : Int = 0 -// replies.collect { -// case RemoverActorTest.InclusionTestAlert() => ita += 1 -// case RemoverActorTest.InitialJobAlert() => ija += 1 -// case RemoverActorTest.FirstJobAlert() => fja += 1 -// case RemoverActorTest.ClearanceTestAlert() => cta += 1 -// case RemoverActorTest.SecondJobAlert() => sja += 1 -// case RemoverActorTest.DeletionTaskAlert() => dta += 1 -// case RemoverActorTest.DeletionTaskRunAlert() => dtr += 1 -// case msg => assert(false, s"$msg") -// } -// assert(ita == 2 && ija == 2 && fja == 2 && cta == 2 && sja == 2 && dta == 2 && dtr == 2) -// } -// } -//} -// -//class HurrySpecificRemoverActorTest extends ActorTest { -// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") -// -// "RemoverActor" should { -// "be able to hurry certain tasks" in { -// expectNoMessage(500 milliseconds) -// val probe = TestProbe() -// val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover") -// remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(10 minutes)) //TEN MINUTE WAIT -// -// val reply1 = probe.receiveOne(200 milliseconds) -// assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert]) -// val reply2 = probe.receiveOne(200 milliseconds) -// assert(reply2.isInstanceOf[RemoverActorTest.InitialJobAlert]) -// probe.expectNoMessage(3 seconds) //long delay, longer than standard but not yet 10 minutes -// remover ! RemoverActor.HurrySpecific(List(RemoverActorTest.TestObject), Zone.Nowhere) //hurried -// val reply3 = probe.receiveOne(300 milliseconds) -// assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert]) -// val reply4 = probe.receiveOne(300 milliseconds) -// assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert]) -// val reply5 = probe.receiveOne(300 milliseconds) -// assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert]) -// val reply6 = probe.receiveOne(500 milliseconds) -// assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert]) -// val reply7 = probe.receiveOne(500 milliseconds) -// assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert]) -// } -// } -//} -// -//class HurrySelectionRemoverActorTest extends ActorTest { -// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") -// final val TestObject2 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(2) } } -// -// "RemoverActor" should { -// "be able to hurry certain tasks, but let others finish normally" in { -// expectNoMessage(500 milliseconds) -// val probe = TestProbe() -// val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover") -// remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(5 seconds)) -// remover ! RemoverActor.AddTask(TestObject2, Zone.Nowhere, Some(10 seconds)) -// -// val replies = probe.receiveN(4, 5 seconds) -// var ita : Int = 0 -// var ija : Int = 0 -// replies.collect { -// case RemoverActorTest.InclusionTestAlert() => ita += 1 -// case RemoverActorTest.InitialJobAlert() => ija += 1 -// case msg => assert(false, s"$msg") -// } -// assert(ita == 2 && ija == 2) -// probe.expectNoMessage(3 seconds) //long delay, longer than standard but not yet 5 seconds -// remover ! RemoverActor.HurrySpecific(List(RemoverActorTest.TestObject), Zone.Nowhere) //hurried -// //first -// val reply3a = probe.receiveOne(300 milliseconds) -// assert(reply3a.isInstanceOf[RemoverActorTest.FirstJobAlert]) -// val reply4a = probe.receiveOne(300 milliseconds) -// assert(reply4a.isInstanceOf[RemoverActorTest.ClearanceTestAlert]) -// val reply5a = probe.receiveOne(300 milliseconds) -// assert(reply5a.isInstanceOf[RemoverActorTest.SecondJobAlert]) -// val reply6a = probe.receiveOne(500 milliseconds) -// assert(reply6a.isInstanceOf[RemoverActorTest.DeletionTaskAlert]) -// val reply7a = probe.receiveOne(500 milliseconds) -// assert(reply7a.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert]) -// //second -// remover ! RemoverActor.HurrySpecific(List(TestObject2), Zone.Nowhere) //hurried -// val reply3b = probe.receiveOne(300 milliseconds) -// assert(reply3b.isInstanceOf[RemoverActorTest.FirstJobAlert]) -// val reply4b = probe.receiveOne(300 milliseconds) -// assert(reply4b.isInstanceOf[RemoverActorTest.ClearanceTestAlert]) -// val reply5b = probe.receiveOne(300 milliseconds) -// assert(reply5b.isInstanceOf[RemoverActorTest.SecondJobAlert]) -// val reply6b = probe.receiveOne(500 milliseconds) -// assert(reply6b.isInstanceOf[RemoverActorTest.DeletionTaskAlert]) -// val reply7b = probe.receiveOne(500 milliseconds) -// assert(reply7b.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert]) -// } -// } -//} -// -//class HurryMultipleRemoverActorTest extends ActorTest { -// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") -// final val TestObject2 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(2) } } -// final val TestObject3 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(3) } } -// -// "RemoverActor" should { -// "be able to hurry certain tasks, but only valid ones" in { -// expectNoMessage(500 milliseconds) -// val probe = TestProbe() -// val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover") -// remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(5 seconds)) -// remover ! RemoverActor.AddTask(TestObject2, Zone.Nowhere, Some(5 seconds)) -// -// val replies = probe.receiveN(4, 5 seconds) -// var ita : Int = 0 -// var ija : Int = 0 -// replies.collect { -// case RemoverActorTest.InclusionTestAlert() => ita += 1 -// case RemoverActorTest.InitialJobAlert() => ija += 1 -// case msg => assert(false, s"$msg") -// } -// assert(ita == 2 && ija == 2) -// probe.expectNoMessage(3 seconds) //long delay, longer than standard but not yet 5 seconds -// remover ! RemoverActor.HurrySpecific(List(RemoverActorTest.TestObject, TestObject3), Zone.Nowhere) //multiple hurried, only one valid -// //first -// val reply3a = probe.receiveOne(300 milliseconds) -// assert(reply3a.isInstanceOf[RemoverActorTest.FirstJobAlert]) -// val reply4a = probe.receiveOne(300 milliseconds) -// assert(reply4a.isInstanceOf[RemoverActorTest.ClearanceTestAlert]) -// val reply5a = probe.receiveOne(300 milliseconds) -// assert(reply5a.isInstanceOf[RemoverActorTest.SecondJobAlert]) -// val reply6a = probe.receiveOne(500 milliseconds) -// assert(reply6a.isInstanceOf[RemoverActorTest.DeletionTaskAlert]) -// val reply7a = probe.receiveOne(500 milliseconds) -// assert(reply7a.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert]) -// //second -// remover ! RemoverActor.HurrySpecific(List(TestObject2), Zone.Nowhere) //hurried -// val reply3b = probe.receiveOne(300 milliseconds) -// assert(reply3b.isInstanceOf[RemoverActorTest.FirstJobAlert]) -// val reply4b = probe.receiveOne(300 milliseconds) -// assert(reply4b.isInstanceOf[RemoverActorTest.ClearanceTestAlert]) -// val reply5b = probe.receiveOne(300 milliseconds) -// assert(reply5b.isInstanceOf[RemoverActorTest.SecondJobAlert]) -// val reply6b = probe.receiveOne(500 milliseconds) -// assert(reply6b.isInstanceOf[RemoverActorTest.DeletionTaskAlert]) -// val reply7b = probe.receiveOne(500 milliseconds) -// assert(reply7b.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert]) -// } -// } -//} -// -//class HurryByZoneRemoverActorTest extends ActorTest { -// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") -// final val TestObject2 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(2) } } -// final val TestObject3 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(3) } } -// final val zone = new Zone("test", new ZoneMap("test-map"), 11) -// -// "RemoverActor" should { -// "be able to hurry certain tasks by their zone" in { -// expectNoMessage(500 milliseconds) -// val probe = TestProbe() -// val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover") -// remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(5 seconds)) -// remover ! RemoverActor.AddTask(TestObject2, zone, Some(5 seconds)) -// remover ! RemoverActor.AddTask(TestObject3, Zone.Nowhere, Some(5 seconds)) -// -// val replies1 = probe.receiveN(6, 5 seconds) -// var ita : Int = 0 -// var ija : Int = 0 -// replies1.collect { -// case RemoverActorTest.InclusionTestAlert() => ita += 1 -// case RemoverActorTest.InitialJobAlert() => ija += 1 -// case msg => assert(false, s"$msg") -// } -// assert(ita == 3 && ija == 3) -// probe.expectNoMessage(3 seconds) //long delay, longer than standard but not yet 5 seconds -// remover ! RemoverActor.HurrySpecific(List(), Zone.Nowhere) //multiple hurried, only the two entries with Zone.Nowhere -// // -// val replies2 = probe.receiveN(10, 5 seconds) -// var fja : Int = 0 -// var cta : Int = 0 -// var sja : Int = 0 -// var dta : Int = 0 -// var dtr : Int = 0 -// replies2.collect { -// case RemoverActorTest.InclusionTestAlert() => ita += 1 -// case RemoverActorTest.InitialJobAlert() => ija += 1 -// case RemoverActorTest.FirstJobAlert() => fja += 1 -// case RemoverActorTest.ClearanceTestAlert() => cta += 1 -// case RemoverActorTest.SecondJobAlert() => sja += 1 -// case RemoverActorTest.DeletionTaskAlert() => dta += 1 -// case RemoverActorTest.DeletionTaskRunAlert() => dtr += 1 -// case msg => assert(false, s"$msg") -// } -// assert(fja == 2 && cta == 2 && sja == 2 && dta == 2 && dtr == 2) -// //final -// remover ! RemoverActor.HurrySpecific(List(), zone) //hurried -// val reply3b = probe.receiveOne(300 milliseconds) -// assert(reply3b.isInstanceOf[RemoverActorTest.FirstJobAlert]) -// val reply4b = probe.receiveOne(300 milliseconds) -// assert(reply4b.isInstanceOf[RemoverActorTest.ClearanceTestAlert]) -// val reply5b = probe.receiveOne(300 milliseconds) -// assert(reply5b.isInstanceOf[RemoverActorTest.SecondJobAlert]) -// val reply6b = probe.receiveOne(500 milliseconds) -// assert(reply6b.isInstanceOf[RemoverActorTest.DeletionTaskAlert]) -// val reply7b = probe.receiveOne(500 milliseconds) -// assert(reply7b.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert]) -// } -// } -//} -// -//class HurryAllRemoverActorTest extends ActorTest { -// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") -// final val TestObject2 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(2) } } -// final val TestObject3 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(3) } } -// -// "RemoverActor" should { -// "be able to hurry all tasks to completion" in { -// expectNoMessage(500 milliseconds) -// val probe = TestProbe() -// val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover") -// remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(20 seconds)) -// remover ! RemoverActor.AddTask(TestObject2, Zone.Nowhere, Some(15 seconds)) -// remover ! RemoverActor.AddTask(TestObject3, Zone.Nowhere, Some(10 seconds)) -// -// val replies1 = probe.receiveN(6, 5 seconds) -// var ita : Int = 0 -// var ija : Int = 0 -// replies1.collect { -// case RemoverActorTest.InclusionTestAlert() => ita += 1 -// case RemoverActorTest.InitialJobAlert() => ija += 1 -// case msg => assert(false, s"$msg") -// } -// assert(ita == 3 && ija == 3) -// probe.expectNoMessage(3 seconds) //long delay, longer than standard but not yet longer than any of the tasks -// remover ! RemoverActor.HurryAll() //all hurried -// // -// val replies2 = probe.receiveN(15, 5 seconds) -// var fja : Int = 0 -// var cta : Int = 0 -// var sja : Int = 0 -// var dta : Int = 0 -// var dtr : Int = 0 -// replies2.collect { -// case RemoverActorTest.InclusionTestAlert() => ita += 1 -// case RemoverActorTest.InitialJobAlert() => ija += 1 -// case RemoverActorTest.FirstJobAlert() => fja += 1 -// case RemoverActorTest.ClearanceTestAlert() => cta += 1 -// case RemoverActorTest.SecondJobAlert() => sja += 1 -// case RemoverActorTest.DeletionTaskAlert() => dta += 1 -// case RemoverActorTest.DeletionTaskRunAlert() => dtr += 1 -// case msg => assert(false, s"$msg") -// } -// assert(fja == 3 && cta == 3 && sja == 3 && dta == 3 && dtr == 3) -// } -// } -//} -// -//class ClearSelectionRemoverActorTest extends ActorTest { -// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") -// final val TestObject2 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(2) } } -// -// "RemoverActor" should { -// "be able to clear certain tasks" in { -// expectNoMessage(500 milliseconds) -// val probe = TestProbe() -// val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover") -// remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(5 seconds)) -// remover ! RemoverActor.AddTask(TestObject2, Zone.Nowhere, Some(5 seconds)) -// -// val replies = probe.receiveN(4, 5 seconds) -// var ita : Int = 0 -// var ija : Int = 0 -// replies.collect { -// case RemoverActorTest.InclusionTestAlert() => ita += 1 -// case RemoverActorTest.InitialJobAlert() => ija += 1 -// case msg => assert(false, s"$msg") -// } -// assert(ita == 2 && ija == 2) -// probe.expectNoMessage(4 seconds) //long delay, longer than standard but not yet 5 seconds -// remover ! RemoverActor.ClearSpecific(List(RemoverActorTest.TestObject), Zone.Nowhere) //cleared -// // -// val reply3 = probe.receiveOne(2 seconds) -// assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert]) -// val reply4 = probe.receiveOne(300 milliseconds) -// assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert]) -// val reply5 = probe.receiveOne(300 milliseconds) -// assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert]) -// val reply6 = probe.receiveOne(500 milliseconds) -// assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert]) -// val reply7 = probe.receiveOne(500 milliseconds) -// assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert]) -// //wait -// probe.expectNoMessage(2 seconds) //nothing more to do -// } -// } -//} -// -//class ClearAllRemoverActorTest extends ActorTest { -// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") -// final val TestObject2 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(2) } } -// -// "RemoverActor" should { -// "be able to clear all tasks, with no more work on them" in { -// expectNoMessage(500 milliseconds) -// val probe = TestProbe() -// val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover") -// remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(5 seconds)) -// remover ! RemoverActor.AddTask(TestObject2, Zone.Nowhere, Some(5 seconds)) -// -// val replies = probe.receiveN(4, 5 seconds) -// var ita : Int = 0 -// var ija : Int = 0 -// replies.collect { -// case RemoverActorTest.InclusionTestAlert() => ita += 1 -// case RemoverActorTest.InitialJobAlert() => ija += 1 -// case msg => assert(false, s"$msg") -// } -// assert(ita == 2 && ija == 2) -// probe.expectNoMessage(4 seconds) //long delay, longer than standard but not yet 5 seconds -// remover ! RemoverActor.ClearAll() //cleared -// //wait -// probe.expectNoMessage(3 seconds) //nothing more to do -// } -// } -//} -// -//class EarlyDeathRemoverActorTest extends ActorTest { -// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") -// final val TestObject2 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(2) } } -// -// "RemoverActor" should { -// "be able to hurry certain tasks" in { -// expectNoMessage(500 milliseconds) -// val probe = TestProbe() -// val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover") -// remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(5 seconds)) -// remover ! RemoverActor.AddTask(TestObject2, Zone.Nowhere, Some(5 seconds)) -// -// val replies = probe.receiveN(4, 5 seconds) -// var ita : Int = 0 -// var ija : Int = 0 -// replies.collect { -// case RemoverActorTest.InclusionTestAlert() => ita += 1 -// case RemoverActorTest.InitialJobAlert() => ija += 1 -// case msg => assert(false, s"$msg") -// } -// assert(ita == 2 && ija == 2) -// probe.expectNoMessage(2 seconds) -// remover ! akka.actor.PoisonPill -// // -// val replies2 = probe.receiveN(8, 5 seconds) -// var fja : Int = 0 -// var cta : Int = 0 -// var sja : Int = 0 -// var dta : Int = 0 -// var dtr : Int = 0 -// replies2.collect { -// case RemoverActorTest.FirstJobAlert() => fja += 1 -// case RemoverActorTest.ClearanceTestAlert() => cta += 1 -// case RemoverActorTest.SecondJobAlert() => sja += 1 -// case RemoverActorTest.DeletionTaskAlert() => dta += 1 -// case RemoverActorTest.DeletionTaskRunAlert() => dtr += 1 -// case msg => assert(false, s"$msg") -// } -// assert(fja == 2 && cta == 0 && sja == 2 && dta == 2 && dtr == 2) //no clearance tests -// } -// } -//} - -object RemoverActorTest { - final val TestObject = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(1) } } - - final case class InclusionTestAlert() - - final case class InitialJobAlert() - - final case class FirstJobAlert() - - final case class SecondJobAlert() - - final case class ClearanceTestAlert() - - final case class DeletionTaskAlert() - - final case class DeletionTaskRunAlert() - - class TestRemover(taskResolver: ActorRef) extends RemoverActor(taskResolver) { - import net.psforever.objects.guid.{Task, TaskResolver} - val FirstStandardDuration = 1 seconds - - val SecondStandardDuration = 100 milliseconds - - def InclusionTest(entry: RemoverActor.Entry): Boolean = { - context.parent ! InclusionTestAlert() - true - } - - def InitialJob(entry: RemoverActor.Entry): Unit = { - context.parent ! InitialJobAlert() - } - - def FirstJob(entry: RemoverActor.Entry): Unit = { - context.parent ! FirstJobAlert() - } - - override def SecondJob(entry: RemoverActor.Entry): Unit = { - context.parent ! SecondJobAlert() - super.SecondJob(entry) - } - - def ClearanceTest(entry: RemoverActor.Entry): Boolean = { - context.parent ! ClearanceTestAlert() - true - } - - def DeletionTask(entry: RemoverActor.Entry): TaskResolver.GiveTask = { - context.parent ! DeletionTaskAlert() - TaskResolver.GiveTask(new Task() { - private val localProbe = context.parent - - override def isComplete = Task.Resolution.Success - - def Execute(resolver: ActorRef): Unit = { - context.parent ! DeletionTaskRunAlert() - resolver ! Success(this) - } - }) - } - } -}